3

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);
}
  • Are both I2C lines low after the slave power supply disconnect? – KIIV Oct 19 '16 at 21:14
  • Show a code, a schematic, and a what modules are you using – Dat Ha Oct 19 '16 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 '16 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 '16 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 '16 at 22:20
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 here: Arduino I2C Master Library. That is written a bit differently so it times out if the transmission doesn't complete in time. The actual download to the library is currently 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
  • Indeed, a master that waits eternally for an ack from a slave will hang if that ack never comes. – Chris Stratton Oct 19 '16 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 '16 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 '16 at 21:43
  • @Nick - the pullup resistors have prevented the data becoming scrambled after a broken connection. I still get a hung script on my Master on a broken connection though, until its reconnected again. I've decided to give the alternate I2c library a go, but would like a few more examples to show a similar setup to replace my current requestFrom - Master and onRequest(requestEvent)-Slave setup. I take it that I would still have to include the Wire() library in the Slave? – Daryl Oct 21 '16 at 1:52
  • The slave would look the same. Doesn't the library come with examples? – Nick Gammon Oct 21 '16 at 2:05
0

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

0

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, privacy policy and cookie policy

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