[TUT][SOFT][HARD]Experimenting with TWI/I2C lines - Part 1

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

TWI/I2C hardware in AVR is byte oriented so only if master succeeded to transfer a byte and received an ack/no-ack from slave then 
a transaction is consider successfull/valid.

If you deal with AVR as slave device, only valid transaction will trigger slave interrupt routine.

 

TWI/I2C transaction depends on synchronization of SDA and SCL pulse. Once those pulse become un-sync then the TWI/I2C comms will "hang" or 
"freeze" which can cause your MCU to freeze if you aren't properly handle it.
Handling this situation commonly with disabling the TWI/I2C hardware and wait for some amount of time then restart over. 
So if you had experienced how to disable and restart TWI/I2C hardware then this experiment is not for your interest.

 

If you're electronics hobbyist and interested to know what's going on then follow this step by step experiment.
Usually engineer or hobbyist own Logic Analyser and Oscilloscope to trace their problem.
This is the simple and tricky way for them who don't own that expensive equipment to experiment.

 

MG

I don't know why I'm still doing this hobby

Last Edited: Wed. Aug 9, 2017 - 03:59 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Step 1. Preparation and Setup

 

 

FYI TWI/I2C lines are current sink hardware that's why it needs pull up resistor, so any lines should not be connected to VCC directly!

Below experiment is safe because shorting 2 current sink will do no harm to your MCU even with PORT pull up activated.
Don't do this if you don't had experienced in electronics devices! 
And do this at your own risk! Don't blame me if something happen to your MCU!

 

Testing a TWI/I2C line will be done in 3 steps:
1. Randomly cut off SDA line.
2. Randomly cut off SCL line.
3. Randomly short SDA and SCL line.

 

For that purposes we need 3 toggle switch or header with jumper cable to simulate above situation. You know what to do.
In case you don't:
Connect each TWI/I2C line from MCU to slave device thru switch (on position) or header with jumper connected then 
connect those 2 lines with switch (off position) or header without jumper.
Don't worry about debouncing, that's helpfull for our experiment.

 

First thing first. As TWI/I2C lines need pull up resistor so place those pull up at slave device. We will use internal pull up for the MCU.
So if we cut a line, that line won't become floating. (You can use this setup in your real design as it will save you if one of the line is 
accidentally get cut).

 

Here's the schematic:

(use external crystal 12Mhz if you have it, it's not timing critical so you can use internal 8Mhz for lower bitrate)

 

After done with hardware now the source code.

 

There are many good TWI/I2C library out there but we will use our library for testing. You can easily adapt with their libraries once you understand it.
We won't discussing about our library's inside, just use it for this experiment, you can examine it by yourself.

 

In this experiment I use mega8 MCU at 12 Mhz (you can use internal 8Mhz as this is not the timing critical, only lowering the bitrate) and EEPROM 24C32 as slave device, you can also use sensor or RTC.

 

Make I2C.c with following content:

#include <avr/io.h>
#include "I2C.h" <-- make it for this library

void I2C_master_init(void)
{
  TWCR |= (1<<TWEN);            // enable TWI hardware 

  TWBR= 0x15;                   // see datasheet for bitrate calculation
  TWSR= 0x00;                   // leave it at 0
}

uint8_t I2C_start(void)
{
  TWCR |= (1<<TWSTA)|(1<<TWINT);                                     // Put Start Condition on TWI Bus & clear flag

  while(!(TWCR & (1<<TWINT)));		                             // Wait Till Done

  if(((TWSR & 0xF8) == 0x08) || ((TWSR & 0xF8) == 0x10)) return 1;   // GOOD
  else return 0;                                                     // BAD
}

void I2C_stop(void)
{
  TWCR |= (1<<TWSTO)|(1<<TWINT); // Put Stop Condition on TWI bus & clear flag

  while(TWCR & (1<<TWSTO));      // Wait Till Done
}

uint8_t I2C_write_data (uint8_t data)
{
  TWDR=data;                     // Write data

  TWCR &= ~(1<<TWSTA);           // Clear start bit

  TWCR |= (1<<TWEA)|(1<<TWINT);  // Ack Transfer & clear flag

  while(!(TWCR & (1<<TWINT)));   // Wait Till Done

  if(((TWSR & 0xF8) == 0x18 || (TWSR & 0xF8) == 0x28) || ((TWSR & 0xF8) == 0x40))
  return 1;                      // GOOD
  else return 0;                 // BAD
}

uint8_t I2C_read_data(uint8_t ack)
{
  TWCR &= ~(1<<TWSTA);                                     // Clear start bit

  if(ack) TWCR |= (1<<TWEA)|(1<<TWINT);                    // Ack Transfer & clear flag
  else                                                     // Nack Transfer
  {
   TWCR &= ~(1<<TWEA);                                     // Clear acknowlage bit
   TWCR |= (1<<TWINT);                                     // Clear flag
  }
  while(!(TWCR & (1<<TWINT)));                             // Wait Till Done

  if(((TWSR & 0xF8) == 0x58) || ((TWSR & 0xF8) == 0x50))
  return TWDR;                                             // Read data
  else return (TWSR & 0xF8);                               // Read status code to show the error
}

uint8_t master_read(uint8_t SLA_W, uint8_t data_addr)
{
  if(!I2C_start())                          return 0; //BAD
  if(!I2C_write_data(SLA_W))                return 0; //BAD
  if(!I2C_write_data(data_addr))            return 0; //BAD
  if(!I2C_start())                          return 0; //BAD
  if(!I2C_write_data(SLA_W | 0x01))         return 0; //BAD
  I2C_read_data(1);                                   //ACK first read - in this test don't care the received value
  I2C_read_data(0);                                   //NACK last read - in this test don't care the received value
  I2C_stop();
  return 1;                                           //don't care the result - GOOD
}

And setup your main.c

#include <avr\io.h>
#include <util\delay.h>
#include "I2C.h" <-- our library

#define slave_SLA_W     0xA0// EEPROM 24C32 (or you can use your own device)

void error_handling(void)
{
  while(1)              // forever here - reset MCU for start over
  {                     // you shouldn't try more because there's something's not right with slave device
    PORTC ^= (1<<0);
    _delay_ms(30);
  }
}

int main(void)
{
 DDRC=0x01;            // mega8 - SDA: PORTC4 - SCL: PORTC5 - connect LED to PORTC1 via resistor to VCC
 PORTC=0x30;           // pull up and turn on LED

 I2C_init();

 while(1)
 {
   /* TWI/I2C section */

   if(!master_read(slave_SLA_W, 0) && (h<13)) //10 restart iteration - adjust for your needs
   {
    if(++h > 11) error_handling();  // more than 10 tries, show error
    else
    {
     TWCR=0;                        // clear all bits
     TWCR |= (1<<TWINT);            // clear flag

     _delay_ms(300);                // wait for some times before restart - short / too less time makes it difficult to get out at high bitrate
	                            // or you can do your task here if you don't like delay

     TWCR |= (1<<TWEN);             // restart
    }
   }

   /* Debug here if necessary */

   PORTC ^= (1<<0);                 // toggle LED
   _delay_ms(100);
 }
}

You should see pretty fast blinking LED if transaction succeeded and slower if there's trouble but very fast if 10 tries exceeded.
You can disable TWI section to see how fast the LED blinks at succeeded transaction if you're not sure. Then re-enable it.

 

 

edit: - spacing problem

        - added schematics and more info

 

 

MG

I don't know why I'm still doing this hobby

Last Edited: Fri. Jul 14, 2017 - 06:23 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Step 2. Hardware Testing

After previous step was successfull then it's time to do hardware test.

 

As listed above, randomly turn off SDA lines and see that LED blinks slower than before. Which is the master is trying to restart the comms.
Now switch on again. The LED should blink faster again which shows comms is back to normal again.
Do that again for SCL lines and see if it can get out from unexpected cut off.

 

With high bitrate, switch on and off like that will cause bouncing which very bad for the comms, but this condition don't actually exist in real master-slave comms.
If the line is accidentally get cut it won't try to re-connect itself don't it? Or maybe the slave device behaves like that?
Surely you only can't get any data from the slave. That's it.

That condition will not freeze your MCU. You can asure that by seeing the blinking light.

So don't worry if it sometimes can't get out from this error. Could be because of the debouncing.
If it can get out much more than it get freeze means that the code is reliable enough.

 

Now test for short connection between SDA and SCL lines and see if it can get out from there.

This time the LED will stay on or off which shows the MCU get freezed, but as soon as you release the short it will blink fast (normal) again.
This condition is like your device had short circuit between SDA and SCL. I hadn't experienced that so I guess this is not a common case.
Surely you don't want to run your MCU with broken device like that.

So there's nothing you can do if this short occured repeatedly / permanently except removing the broken device.
You can also test by shorting SDA or SCL line to ground which will have the same result as above short.

 

So the code can save you if you had temporary above conditions and it's up to you to take it or leave it.

While using sensors that vulnerable to break, use external hardware protection to make sure your MCU keep working while your sensors get shorted.

 

I can't test slave device which I don't own, so if you have the same interest with me please share your test here so others can get benefit from it.

 

edit: line spacing problem

 

MG

I don't know why I'm still doing this hobby

Last Edited: Fri. Jul 14, 2017 - 08:03 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your tutorial would be improved if you added some schematic diagrams, word descriptions are sometimes hard to follow. 

Thanks for the effort, I look forward to trying it out!

 

Jim

 

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

ki0bk wrote:

Your tutorial would be improved if you added some schematic diagrams, word descriptions are sometimes hard to follow. 

Thanks for the effort, I look forward to trying it out!

 

Jim

 

 

Done !

I don't know why I'm still doing this hobby

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

MicroGyro wrote:
With high bitrate, switch on and off like that will cause debouncing which very bad for the comms, but this condition don't actually exist in real master-slave comms.

Whilst english probably isn't your native language, the term 'debouncing' should be 'bouncing'. 'de' is to remove the condition.

Sure this condition may exist in the real world - just do a fast transient test. The core problem is that you have two state machines joined via i2c. If one gets out of step, it is 'fun' to resync them. I resorted to using timers and resetting the i2c peripherals. The other challenge is figuring out who is holding the bus - i used series resistors so I could 'see' the voltage drop on the 'scope and determine who was holding.

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

Kartman wrote:
Whilst english probably isn't your native language, the term 'debouncing' should be 'bouncing'. 'de' is to remove the condition.

Thank you for making it clear Sir. Edited.

 

Kartman wrote:
I resorted to using timers and resetting the i2c peripherals.

Is that reliable? as sometimes the MCU can get freezed if the line messed up? Haven't test it yet.

 

Kartman wrote:
The other challenge is figuring out who is holding the bus - i used series resistors so I could 'see' the voltage drop on the 'scope and determine who was holding.
 

Haven't experimenting like that.

If the slave that holding the bus, can SCL clocking release it? 

 

 

 

 

MG

I don't know why I'm still doing this hobby

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

Is it reliable - yes, as far as i know. I can randomly short the bus and the system will recover.

The slave can hold the clock, so SCL clocking won't fix that.

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

Kartman wrote:
The slave can hold the clock, so SCL clocking won't fix that.

Well that's new to me, I thought SCL is fully controlled by master.
I figured that in simulating the TWI master comms. That's what I plan to do in part 2.
I should dig deeper then.
.
MG

I don't know why I'm still doing this hobby

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

MicroGyro wrote:
Kartman wrote:
The slave can hold the clock, so SCL clocking won't fix that.
Well that's new to me, I thought SCL is fully controlled by master. I figured that in simulating the TWI master comms. That's what I plan to do in part 2. I should dig deeper then. . MG

 

Yes the slave can do "clock stretching" if it needs time to update its registers. 

 

Jim

 

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

ki0bk wrote:
Yes the slave can do "clock stretching" if it needs time to update its registers. 

Noted.

 

 

 

MG

I don't know why I'm still doing this hobby