IO port transitions

Go To Last Post
22 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Using an AT90USB162 I need to switch some I/O ports back and forth between input and output. Section 11.2.3 of the datasheet is called "Switching Between Input and Output" and mentions the intermediate state that occurs when switching from input to output. For example:

"Switching between input with pull-up and output low generates the same problem. The user must use either the tri-state ({DDxn, PORTxn} = 0b00) or the output high state ({DDxn, PORTxn} = 0b11) as an intermediate step."

That makes sense. What I don't understand is why there is no discussion about switching from Output to Input. If the current state is output low ({DDxn, PORTxn} = 0b10) and the target state is input with pull-up ({DDxn, PORTxn} = 0b01), won't a similar intermediate step occur (either output high, or input with no pullup)? Or does this not occur due to the synch latch described in section 11.2.4? It seems to me that the this latch just effects the timing of the PINxn registers states, and wouldn't eliminate an intermediate step.

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you are switching between input/output on the fly then I presume that the other device has a 3 state output.

So from your point of view, you will control the 3 state of your connected device. So if the target is sensitive to an input transition for example, you make sure that your output is stable before you enable the device. It is mostly a question of studying and complying with the timing diagrams in the device data sheet.

If however it is a open collector bus type of device, you use an external pullup resistor, set PORTx.y = 0 and use the DDR for input/output.

If you cannot use a chip enable type of 3 state control, I can only suggest that you toggle the DDR+PORT and watch for transients on an oscilloscope.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the feedback. I should have explained the reason for my question (since the answer may already be known). I have a board that was designed for SPI, but needs to use I2C for a newer version of the slave. The MCU is an AT90USB162, so I2C would be done in software.

Everything is pin compatible because the I2C pins on the new chip are in the same spots as MISO and SCLK on the old chip. But unfortunately the re-purposed SPI lines do not have pull-up resistors on the pcb.

I realize I2C is an open-drain circuit, so I'm wondering if I can make it work using the AVR's internal pull-ups. The main problem I see is the intermediate state when switching from output-low to input with pullup (or visa-versa).

During that transition the MCU would be set to output-high while the slave could be input-low (for just a few cycles). What I don't know is if that is enough for "bad things" to happen. I don't believe it would be a timing issue, but it might be an electrical (short) issue.

Anyone tried this before?

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't think there will be a problem, the undefined state will be very short and you change direction before giving the next clock pulse. You need to change direction after sending the eight bit to check the acknowledge bit of the slave which changes it output after the falling edge of the clock. In that time you change from output to input and then the clock is set high again. While the clock is low the data line doesn't matter.

But I could be wrong of course, I (almost) never use IIC :D

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What does the slave device do when it sees the line from the master change from output-low to input (high)? Unless the device immediately tries to drive the bus low, you could drive the AVR output high and immediately switch to input (with pullup). If the device immediately tries to drive the bus low, then just switch the AVR ouput to input, since it won't matter that the pullup is not enabled right away (the slave is driving low anyway).

So it seems to me that in case (a) (slave doesn't go immediately low) you switch from ouput-low to output-high, and then switch to input, and in case (b) (slave immediately goes low), you switch from output-low to input (no pullup). Either way seems like it would be safe for the given slave behavior.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

snarflemike wrote:
So it seems to me that in case (a) (slave doesn't go immediately low) you switch from ouput-low to output-high, and then switch to input, and in case (b) (slave immediately goes low), you switch from output-low to input (no pullup). Either way seems like it would be safe for the given slave behavior.

Good thought. I'll have to study the I2C timing specs closer for the device. At first glance, however, it looks like it will be hard to tell for sure. Most of the spec is about what the slave expects to see from the master.

It will probably be easier to just try it and see what happens. As long as nothing blows if I hit a very short input-low connected to output-high. What would the AVR do in this case? Is there a current limit?

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Looking at Peter Fleury's I2C code, there is definitely a possibility of a transitional output-high connected to slave input-low. It can happen if the slave adds wait states like in the code below at the end of a write

i2c_get_ack:
   sbi   SCL_DDR,SCL  ;force SCL low
   cbi   SDA_DDR,SDA  ;release SDA
   rcall i2c_delay_T2 ;delay T/2 (slave could pull SCL low any time)
   cbi   SCL_DDR,SCL  ;release SCL (this would transition through output-high)
i2c_ack_wait:
   sbis  SCL_IN,SCL   ;wait SCL high (in case wait states are inserted)
   rjmp  i2c_ack_wait

   clr   r24          ;return 0
   sbic  SDA_IN,SDA   ;if SDA high -> return 1

Assuming the rcall i2c_delay_T2 and release SCL can't be reversed, I either hack my boards or determine that the AVR and slave can handle a few cycles of an output-high to input-low connection.

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The i2c bus must have an external pullup. However you only need one set of pullups for the whole bus. e.g. the pullups could be with the i2c devices on an external board.

As you can see from the above code, you never drive SDA or SCL high. The whole point of a open collector type of bus is that you have no drive conflicts.

When the DDR is altered from input to output, there is never a situation when the o/p is in a high state.

David.

So your original scenario is never achieved.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
The i2c bus must have an external pullup. However you only need one set of pullups for the whole bus. e.g. the pullups could be with the i2c devices on an external board

Well, in this specific case I'm hoping that it "should" have an external pullup not "must".

I sent an email to the vendor of the slave IC asking if their device ever does clock stretching. If not, then I actually don't see any problems using internal pullups. In the two (very short) race conditions where the slave will pull SDA low, the master can "release" SDA through the floating state.

In fact if the slave never pulls the clock, the master could just drive SCL high and low directly. Sure its a hack, but I can't see why it wouldn't work in this special case.

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Read the spec of your AVR. An internal pullup is of the order of 100k. i2c wants a pullup of the order of 4k7. I am sure that if you are communicating between one AVR and one top secret i2c slave in a quiet environment without much capacitance on the lines it may well work.

I would not trust operation in a commercial product, nor would I think that it is worth the cost saving of two resistors. You will have to ask the slave manufacturer whether their chip output will tolerate being effectively shorted to 5V.

Try it and see.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The IO pull-up are between 20-50 kΩ on the AT90USB162, but this definitely isn't for production. As I mentioned, this it to make use of already some prototype boards.

I haven't heard back from SiLabs (makers of the slave chip), but I went ahead and gave it a try. So far all is working! The devices are talking and no smoke has escaped.

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,
the clue about i2c SW implemetation is to never drive a '1' actively. Without external pullups (only internal) the previous state of the driver must be checked before setting the new state:

//
//  Functions to set level of SDA and SCL
//
//  A logical '0' is forced by setting the output pin
//  to '0'. A logical '1' is implemented as a weak '1',
//  meaning the port is configured to 'input' with
//  internal 'pullup' resistor.
//  Care hase been taken to properly switch between
//  the logic levels i.e. avoiding spikes.
//

// macros for ease of use
#define SDA(x)  sda_ ## x()
#define SCL(x)  scl_ ## x()

void
sda_1(void)
{
    if( DDR_SDA )  {    // coming from: force '0'
        DDR_SDA = 0;        // set to input
        PORT_SDA = 1;       // enable pullup
    }
}

void
sda_0(void)
{
    if( ! DDR_SDA )  {  // coming from: weak '1'
        PORT_SDA = 0;       // disable internal pullup
        DDR_SDA = 1;        // force '0'
    }
}

void
scl_1(void)
{
    unsigned char timeout = 0xff;

    if( DDR_SCL )  {    // coming from: force '0'
        DDR_SCL = 0;        // set to input
        PORT_SCL = 1;       // enable pullup
    }

    // I2C slaves are allowed
    // to stretch the SCLK low phase.
    // Check this and wait here (with timeout).
    while( ! PIN_SCL && timeout-- != 0 )
        ;
}

void
scl_0(void)
{
    if( ! DDR_SCL )  {  // coming from: weak '1'
        PORT_SCL = 0;       // disable internal pullup
        DDR_SCL = 1;        // force '0'
    }
}

Best regards,
HJ

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

heinrichs.hj wrote:
Hi all,
the clue about i2c SW implemetation is to never drive a '1' actively. Without external pullups (only internal) the previous state of the driver must be checked before setting the new state

Hmm thanks for the feedback, but I don't think it matters if you check the previous state or not. Without a external pullups, there is no way to transition from output-low to input-pullup without going through a middle state. You can chose if you want that middle state to be input-floating (as you have done) or out-high. Either middle state could cause problems. With Input-float on the clock, for example, it could go high and low on you due to noise. And the problem with out-high is obvious if the slave is pulling low.

Checking the previous state doesn't matter. What matters is the order you assign values to PORTx and DDRx. If you removed all those "if( DDR_x)" checks from your code, the electric signals would be the same, you're just avoid setting the register to the values they already have. But that is basically a nop anyway.

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am not surprised that you can communicate with your system. With the internal pullups, I can see you will have a risetime of 2 .. 5 us. If you are running your AVR at 8MHz, then your bus stray capacitance is probably going to act as a low pass filter. You should be able to move through your undefined state without your slave noticing.

You will be able to confirm this by just watching the SCL line on an oscilloscope.

I still think that soldering real pullups would have been quicker than the duration of this thread.

I would guess that you can probably achieve a 50kHz bus.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, here is everything I have gathered in case someone else wants to try this. I can't promise it will work for you, but it works for my devices.

1. Assume the slave does not perform clock stretching. If it does, you may need another approach (probably external pull-ups).

2. Always transition the clock through output-high since the slave's clock pin should just be reading the value (hi-z). You could probably just drive the clock output-low and output-high directly, but I decided to use output-low and input-pullup with and output-high intermediate step. Using an input-floating intermediate step seems like a bad choice for SCL since it could allow the clock line to fluctuate randomly (false pulses).

3. When the master is writing data (aka the clock is low), transition the data line through output-high also. Again the assumption is that the slave is reading the line with a hi-z input. You could go through input-float instead since the slave shouldn't notice fluctuations while the clock is low, but it seemed sloppier to me.

4. There are two cases where the slave can drive SDA low. When it acks a byte sent by the master and when it is writing data. In these two places when the master transitions the SDA line to input-pullup, it should go through the input-floating intermediate state. Using an input-high intermediate step seems like a bad choice here since it could connect input-high on the master to output-low on the slave.

Perhaps it would be simpler if the data line always used the input-float intermediate state. Then you don't need to make any special distinction for point #4, and just always do the following:

Notation: {DDxn, PORTxn}

SCL:
start,  trans,  final
{0,1}   {1,1}   {1,0}
{1,0}   {1,1}   {0,1}
{0,1}    nop    {0,1}
{1,0}    nop    {1,0}

SDA:
start,  trans,  final state
{0,1}   {0,0}   {1,0}
{1,0}   {0,0}   {0,1}
{0,1}    nop    {0,1}
{1,0}    nop    {1,0}

But I haven't tried that yet since I'm transitioning SDA through {1,1} except for the cases mentioned in point #4 above.

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
I still think that soldering real pullups would have been quicker than the duration of this thread

LOL, very true! My reason for not is a bit vain. These boards are going to be used for demo's and sales calls, and as a tiny little company I suspect our ability to have the electronics made will come into question. So I want the boards to look as professional and clean as possible. Hey, I said it was vain ;) Also I enjoyed learning more about the details of I2C.

Quote:
I would guess that you can probably achieve a 50kHz bus
I'm actually running at 100kHz. Maybe I'll slow that down a bit. I'm going to play with the scope today and see how it all looks.

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You should do better than 50kHz cos you are doing an intermediate drive high.

So I expect you will get a similar behaviour to a mcs51 output stage. Although the final o/p cannot source any current, drive assistors are turned on during the state change. The mcs51 has a reasonably fast risetime, at least better than the external bus pullups would achieve by themselves.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
So I expect you will get a similar behaviour to a mcs51 output stage. Although the final o/p cannot source any current, drive assistors are turned on during the state change. The mcs51 has a reasonably fast risetime, at least better than the external bus pullups would achieve by themselves.

I had the same thought in mind - he's emulating an 8051 output pin.

Mike

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I looked at the signals in a scope and all look good. So clean, in fact, that I wanted to see how fast I could push it before things started to go bad. The slave device is rated at max 400kHz. Some interesting results:

The initial clock target was 100kHz but the actual clock was was 80kHz since the delay loop in Peter's code doesn't account for the time spent in surrounding code.

When the data-line is transitions through input-float {0, 0} before reads, the highest possible speed is about 200kHz. If I change the data line to transition through output-high {1,1} before reads, the highest speed before errors is about 222kHz. With a weak 20-50k pullup, that makes sense since SDA is sure to be high when it is engaged.

In the scope I observed that the slave device always writes during the rising edge of the clock, so it should be very safe (in this specific case) to transition through output-high since SDA will long since have made it to input-pullup by the time the clock rises.

So I'm going to always use the {1,1} transition, and to be safe adjusting the delay to produce about ~100kHz clock. David what was that you were saying about time spent on this thread :)

Edit: Here is my final transition diagram. Basically always going through output-high.

Notation: {DDxn, PORTxn}

SCL:
start,  trans,  final
{0,1}   {1,1}   {1,0}
{1,0}   {1,1}   {0,1}
{0,1}    nop    {0,1}
{1,0}    nop    {1,0}

SDA:
start,  trans,  final state
{0,1}   {1,1}   {1,0}
{1,0}   {1,1}   {0,1}
{0,1}    nop    {0,1}
{1,0}    nop    {1,0}

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What speed is your AVR doing ? A 16MHz clock could do your "assisted" pullup in 250nS. A 400kHz bus has got quite a bit of spare time.

So what sort of risetime do you get ? My initial response was always that you would be limited by slew-rate.

When you say that 220kHz was your top bus speed, was that due to round edges or ringing edges ? As a general rule, a master should sample the SDA line only during SCL low, and after the Tsu:dat setup time.

Doh. You may be assisting the Master to make clean edges. Your slave meanwhile knows nothing about this, so consequently just provides regular "open collector" type of drive. So you can write to your slave at high speed. The slave will ACK with a clean falling edge. But when reading from your slave you will get slow risetimes and consequently need a long Tsu:dat before sampling the SDA line.

This would be easy to test with an eeprom. Write a whole page buffer at 400kHz, write the page and then try to read it back at 100kHz or 400kHz. I would guess that you can write at full speed with no problems.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The system clock is 8MHz.

As you said, the limiting factor is during reads when SDA is simply left as input-pullup from the MCU side. Since it is relying entirely on the pullup, its a lot slower to transition high. I could inject an output-high in there the help, but since its running fine at 100kHz I'm going to call it good enough.

Actually I think the slave may be holding the line low between clock cycles or doing something else that resists the pullup. Because during reads the raise time seems to fill the low cycle not matter what the frequency is (within the range I'm testing). Then there is a final jump up just as the clock goes high.

The rise and fall times for the "assisted" pullups are < 10ns. Its pushing 3us with just the pullup alone at 100kHz (but I as I said I think this is being effected by something).

Also I'm pretty sure all reads are supposed to happen while SCL is high.

Anyway... long enough spent on this. Its working and I'll add pullups to the next hardware rev. Thanks for all the input.

-Brad

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oops, I should have said read when SCL is high.

Your 3us risetime is about what my "off my head" calculation was. That rules out 400kHz, but shows 100kHz is possible.

David.