4

I am using I2C to successfully request critical data from a Slave device (I have to use requestFrom() in the Master) but if, for some reason, the power disconnects from the Slave device then the Master freezes altogether. I didn't realise I2C was so problematic in this regard and I have been a number of weeks trying all types of alternative setups to try and avoid this situation. I can't seem to find a way for example, to simply check if a Wire() connection exists before requesting data and freezing everything. Both devices are Arduino Uno r3. Any ideas I may not have tried yet? Any way to send a single value to the Master and update it every 30 secs would do. Actually the data comes from a capacitive sensor, so would it be possible to just have both arduinos read from the same sensor?

In response to Nick Gammons suggestions (below): I have edited the examples you gave to try and fit in with what I am trying to achieve. I would like to do the following. Catch a situation where the Master (Arduino Yun Shield) cannot communicate with the Slave ( Arduino r3 - with sensor) due to its power supply and/or sensor reading failing. In other words, an Arduino monitoring another Arduino. Here is my edited versions, however I am still not able to catch the "else" condition of the Slave being unreachable:

SLAVE

#include <Wire.h>

const byte MY_ADDRESS = 42;

// various commands we might get

enum {
    CMD_ID = 1,
    CMD_READ_A0  = 2
    };

char command;

void setup() 
  {
  command = 0;
  pinMode (A0, INPUT);

  Wire.begin (MY_ADDRESS);
  Wire.onReceive (receiveEvent);  // interrupt handler for incoming  messages
  Wire.onRequest (requestEvent);  // interrupt handler for when data is wanted

  }  // end of setup

void loop() 
  {
  // all done by interrupts
  }  // end of loop

void receiveEvent (int howMany)
  {
  command = Wire.read ();  // remember command for when we get request
  } // end of receiveEvent

void sendSensor (const byte which)
  {
  //int val = analogRead (which);
  int val = 125;//test only
  byte buf [2];
    buf [0] = val >> 8;
    buf [1] = val & 0xFF;
    Wire.write (buf, 2);
  }

void requestEvent ()
  {
  switch (command)
     {
     case CMD_ID:      Wire.write (0x55); break;   // send our ID 
     case CMD_READ_A0: sendSensor (A0); break;  // send A0 value
     }
  }

MASTER

#include <Process.h>
#include <I2C.h>
#include <Bridge.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C  lcd(0x27, 16, 2);

const int SLAVE_ADDRESS = 42;

// various commands we might send
enum {
  CMD_ID = 1,
  CMD_READ_A0  = 2,
};

void setup ()
{
  Bridge.begin();
  Console.begin();
  //while (!Console);
  I2c.begin ();
  I2c.timeOut(100);  // milliseconds

  lcd.init();
  lcd.backlight();
}

void loop()
{
  int value = getA0();
  Console.print("A0 value:");
  Console.println(value);
  lcd.setCursor(0, 0);
  lcd.print("A0 value:");
  lcd.print(value);//displays first '0' reading then freezes code
  delay (5000);
}

int getA0() {
  if (I2c.write (SLAVE_ADDRESS, CMD_READ_A0) == 0) {
    byte buf [2];
    int val;
    if (I2c.read (SLAVE_ADDRESS, sizeof buf, buf) == 0)
   {
      val = buf [0] << 8 | buf [1];
      return val;
    }
    else {
      return 0;
    }
  }
  else {
    return 0;
  }
  delay(500);
}
10
  • Are both I2C lines low after the slave power supply disconnect?
    – KIIV
    Oct 19, 2016 at 21:14
  • Show a code, a schematic, and a what modules are you using
    – Dat Ha
    Oct 19, 2016 at 21:20
  • I am still not able to catch the "else" condition of the Slave being unreachable - try testing the I2c.write function calls as well, to see if they return a zero (which they should).
    – Nick Gammon
    Oct 23, 2016 at 21:20
  • @NickGammon - I can confirm that it catches correctly even using the I2c.read (as above), however it only worked if the lcd I2C wasn't connected as well. There is no device address clash, but the code is frozen if the lcd is connected. Is there anything I should be aware of when connecting I2C lcd devices at the same time as using the Wire() and I2c() libraries?
    – Daryl
    Oct 24, 2016 at 1:17
  • 1
    Is the LCD an I2C one? I guess it is. You might need to delve into that library and make it use the I2c library, making changes as appropriate (and add the timeout line).
    – Nick Gammon
    Oct 24, 2016 at 22:20

4 Answers 4

3

There is code inside the standard library that loops waiting for certain interrupts to complete transmission. If the transmission fails in the middle it can hang.

See my page about I2C. On that page I mention that there is an alternative library. That is written a bit differently so it times out if the transmission doesn't complete in time. An alternative download to the library is here - I2C_Rev5.zip - 6.6 KB.


As hinted by KIIV in a comment, maybe you don't have pull-up resistors. Make sure that you have pull-up resistors from both SDA and SCL to the +5V line. A reasonable value would be 4.7k (each).


Example code

would like a few more examples to show a similar setup to replace my current requestFrom

Borrowed from my page about I2C I am using the same slave code as on that page.

Slave

#include <Wire.h>

const byte MY_ADDRESS = 42;

// various commands we might get

enum {
    CMD_ID = 1,
    CMD_READ_A0  = 2,
    CMD_READ_D8 = 3
    };

char command;

void setup() 
  {
  command = 0;
  
  pinMode (8, INPUT_PULLUP);
  pinMode (A0, INPUT);

  Wire.begin (MY_ADDRESS);
  Wire.onReceive (receiveEvent);  // interrupt handler for incoming messages
  Wire.onRequest (requestEvent);  // interrupt handler for when data is wanted

  }  // end of setup

void loop() 
  {
  // all done by interrupts
  }  // end of loop

void receiveEvent (int howMany)
  {
  command = Wire.read ();  // remember command for when we get request
  } // end of receiveEvent

void sendSensor (const byte which)
  {
  int val = analogRead (which);
  byte buf [2];
  
    buf [0] = val >> 8;
    buf [1] = val & 0xFF;
    Wire.write (buf, 2);
  }  // end of sendSensor

void requestEvent ()
  {
  switch (command)
     {
     case CMD_ID:      Wire.write (0x55); break;   // send our ID 
     case CMD_READ_A0: sendSensor (A0); break;  // send A0 value
     case CMD_READ_D8: Wire.write (digitalRead (8)); break;   // send D8 value
    
     }  // end of switch
  
  }  // end of requestEvent

The above code responds to requests to send one of:

  • Its ID (0x55)
  • An analog reading (A0)
  • A digital reading (D8)

Master using Wire library

The master from my page was this:

#include <Wire.h>

const int SLAVE_ADDRESS = 42;

// various commands we might send
enum {
    CMD_ID = 1,
    CMD_READ_A0  = 2,
    CMD_READ_D8 = 3
    };

void sendCommand (const byte cmd, const int responseSize)
  {
  Wire.beginTransmission (SLAVE_ADDRESS);
  Wire.write (cmd);
  Wire.endTransmission ();
  
  Wire.requestFrom (SLAVE_ADDRESS, responseSize);  
  }  // end of sendCommand
  
void setup ()
  {
  Wire.begin ();   
  Serial.begin (115200);  // start serial for output
  
  sendCommand (CMD_ID, 1);
  
  if (Wire.available ())
    {
    Serial.print ("Slave is ID: ");
    Serial.println (Wire.read (), DEC);
    }
  else
    Serial.println ("No response to ID request");
  
  }  // end of setup

void loop()
  {
  int val;
  
  sendCommand (CMD_READ_A0, 2);
  val = Wire.read ();
  val <<= 8;
  val |= Wire.read ();
  Serial.print ("Value of A0: ");
  Serial.println (val, DEC);

  sendCommand (CMD_READ_D8, 1);
  val = Wire.read ();
  Serial.print ("Value of D8: ");
  Serial.println (val, DEC);

  delay (500);   
  }  // end of loop

Master using I2C library

The beginTransmission() ... write() ... endTransmission() have been replaced by simply write() and the requestFrom() by read() like this:

#include <I2C.h>

const int SLAVE_ADDRESS = 42;

// various commands we might send
enum {
    CMD_ID = 1,
    CMD_READ_A0  = 2,
    CMD_READ_D8 = 3
    };

void setup ()
  {
  I2c.begin ();  
  I2c.timeOut(100);  // milliseconds
  Serial.begin (115200);  // start serial for output
  
  I2c.write (SLAVE_ADDRESS, CMD_ID);
  byte id;
    
  if (I2c.read (SLAVE_ADDRESS, sizeof id, &id) == 0)
    {
    Serial.print ("Slave is ID: ");
    Serial.println (id, DEC);
    }
  else
    Serial.println ("No response to ID request");
  
  }  // end of setup

void loop()
  {

  I2c.write (SLAVE_ADDRESS, CMD_READ_A0);

  byte buf [2];
  int val;
  if (I2c.read (SLAVE_ADDRESS, sizeof buf, buf) == 0)
    {
    val = buf [0] << 8 | buf [1];
    Serial.print ("Value of A0: ");
    Serial.println (val, DEC);
    }
  else
    Serial.println ("No response to CMD_READ_A0 request");
    
    
  I2c.write (SLAVE_ADDRESS, CMD_READ_D8);
  uint8_t digitalVal;
  if (I2c.read (SLAVE_ADDRESS, sizeof digitalVal, &digitalVal) == 0)
    {
    Serial.print ("Value of D8: ");
    Serial.println (digitalVal, DEC);
    }
  else
    Serial.println ("No response to CMD_READ_D8 request");
    
  delay (500);   
  }  // end of loop
11
  • Indeed, a master that waits eternally for an ack from a slave will hang if that ack never comes. Oct 19, 2016 at 21:31
  • Thanks Nick, I initially avoided this Library due to the lack of slave functions (requestFrom) but i'll have another look this afternoon and update here if anything works.
    – Daryl
    Oct 19, 2016 at 21:39
  • Also if the device have protection diodes, it can be "powered" through I2C lines (and masters pull-ups) or it holds the lines under HIGH level threshold. Master can't start the communication then. Or it's recognized as clock stretching and master can't continue in communication.
    – KIIV
    Oct 19, 2016 at 21:43
  • 1
    @Pete I made a copy a while ago at gammon.com.au/Arduino/I2C_Rev5.zip
    – Nick Gammon
    May 26, 2021 at 6:24
  • 1
    Or see here on Github
    – Nick Gammon
    May 26, 2021 at 6:27
2

I ran into almost the same problem in an educational context. If the I2C bus has a line stuck low, the program hangs in the wire library. In my application everything is soldered together, but not always properly, so I can get by with a single test at startup. Before invoking Wire.begin. For the OP's issue, they will want to use this every time they restart communication after an idle period.

This is part of a device's self test on power up. When one thing is assembled wrong, frequently many things are wrong, so we want to continue on and test everything else. Then the user can fix all of the issues at once. Or perhaps they haven't even built the I2C part of the circuit yet, and they want to test other hardware. In any case, hanging is not an informative way of reporting an error.

// Written by Paul Haas for public use. This is public domain code.
// But please don't sue me if it doesn't work.
bool Would_I2C_Hang(int SDAPin, int SCLPin) {
  bool wouldHang = false;
  pinMode(SDAPin,INPUT);
  pinMode(SCLPin,INPUT);
  if ( digitalRead(SDAPin) != 1 ) {
    Serial.println("SDA pin is low");
    wouldHang = true;
  }
  if ( digitalRead(SCLPin) != 1 ) {
    Serial.println("SCL pin is low");
    wouldHang = true;
  }
  return wouldHang;
}

void setup() {
  Serial.begin(115200);
  for(int i=5;i>0;i--) {
    Serial.print(i);
    delay(1000); // I sometimes need a few seconds to restart the serial console.
  }
  Serial.println();
  if ( Would_I2C_Hang(20, 21) ) { // Adafruit Feather M0 sda and sdl arduino pins.
    Serial.println("The I2C bus will hang if we try to use it. Disabling it.");
  } else {
    Serial.println("Mark the I2C devices as ready to be tested.");
  }
}
void loop() {
  // put your main code here, to run repeatedly:
}
1

Regarding the TI I2C hot swappable buffer:

Apparently there is a similar one from Analog Devices, but I have not tested it: http://www.mouser.com/search/ProductDetail.aspx?r=584-ADUM1250SRZ

To integrate the SOIC-8 package onto a standard DIP breadboard for Arduinos, I used this adapter from Adafruit: https://www.adafruit.com/product/1212

Sorry for another post - limit of 2 hyperlinks per post due to lack of reputation :-)

Chris

1

Had this same issue when using two Megas communicating over I2C, along with other items running on the I2C bus. I needed the system to continue running if one Mega went down due to loss of power. The other Mega should detect it and execute some abort commands.

Initially I wired the Megas up on the same I2C bus, just as I would any other device. When I unplugged one Mega, the other would freeze the next time it tried to address another device on the bus.

The solution was to use a hot-swappable I2C buffer. I used this one, and everything worked immediately without issue after wiring it up: http://www.mouser.com/search/ProductDetail.aspx?r=595-TCA4311ADR

To integrate the SOIC-8 package onto a standard DIP breadboard for Arduinos, I used an adapter from Adafruit.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.