Hanging I2C on DUE, SDA Low SCL High permanent.

Have been doing some experiments with RTC's , type DS1307 and DS3231.
Tried both at 3.3V and tried both at 5V with a level converter. Both give the same result, they work (sometimes). When I reset the DUE a few times then the I2C hangs, the RTC is pulling the SDA low permanent and the SCL stays high. Monitoring the I2C lines with a oscilloscoop.

Resetting again does not have any effect. I can correct the situation most of the time by momentary disconnecting the SDA line but often I also have to remove the power from the RTC module. So it seems the RTC slave is pulling SDA low without doeing any communications. I suspect the wire library for this behaviour because this setup works fine except for DUE.

I work with linux64 and have the same result for:
IDE 1.5.7, IDE 1.5.8, IDE nightly-1.6.Orc1 and the IDE nightly1.6.0
Used the I2C on pins 20-21. Had a decoupling capacitor on the RTC power and used wires no longer than 20cm. (8 inch). In both cases the RTC's had a 3V CR2032 back-up battery installed.
My DUE is from china, looks genuine, it has a I2C pullup resistor network of 1k5.

I am out of more options to try.
This was the simple sketch i used:

#include <Wire.h>
const int DS1307 = 0x68;
byte second = 0;
byte minute = 0;
byte hour = 0;
byte weekday = 0;
byte monthday = 0;
byte month = 0;
byte year = 0;
byte lastMinute = 0;

void setup() {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("year,month,monthday,weekday,hr,min,sec");
}

void loop() {
  readTime();
  if (minute != lastMinute) {
    printTime();
    lastMinute = minute;
  }
}

byte bcdToDec(byte val) {
  return ((val/16*10) + (val%16));
}

void readTime() {
  Wire.beginTransmission(DS1307);
  Wire.write(byte(0));
  Wire.endTransmission();
  Wire.requestFrom(DS1307, 7);
  second = bcdToDec(Wire.read());
  minute = bcdToDec(Wire.read());
  hour = bcdToDec(Wire.read());
  weekday = bcdToDec(Wire.read());
  monthday = bcdToDec(Wire.read());
  month = bcdToDec(Wire.read());
  year = bcdToDec(Wire.read());
}

void printTime() {
  Serial.print(year);
  Serial.print("     ");
  Serial.print(month); 
  Serial.print("      ");
  Serial.print(monthday); 
  Serial.print("    ");
  Serial.print(weekday);  
  Serial.print("     ");
  Serial.print(hour);
  Serial.print("   ");
  Serial.print(minute); 
  Serial.print("   ");
  Serial.print(second);
  Serial.println(" ");
}

Anyone any suggestion how to change the sketch so it will work all of the time ?
Or could it be a wrong implementation of the battery back-up circuit on the RTC board ?

It's a long time since I read the DS1307 datasheet. I would not be hammering it that fast - it may need some time between requests. Try adding a delay(1) to your loop?

tnx for the respons MorganS.

Because there are so much postings about the I2C not working well, i was believing initially, it was a fault of the wire library.

Then I did the same testing with and without delays between the reads. With a delay the occurrence of letting the I2Cbus hang by a reset was reduced proportional to the amount of delay. This made me realise it was a problem with the moment the reset was done.

Looking with a logic analyzer i found out that the hanging of the bus occurred only when the reset was done during the Wire.read of the 7 bytes requested from the slave.

I am not deep enough into the I2C protocol to exactly understand why the slave freezes the bus lines, but the chance this happens is bigger when the requests from the RTC slave are more frequent.
Changing the setup of the RTC so that SQW gives a 1Hz heartbeat pulse and changing the program so that only once per heartbeat the time is requested reduces the chance of a hanging I2Cbus dramatically.
By counting the heartbeat up to 60 i can even limit the updaterate of the RTC to 1 per minute.

#include <Wire.h>
const int DS1307 = 0x68;
byte second = 0;
byte minute = 0;
byte hour = 0;
byte weekday = 0;
byte monthday = 0;
byte month = 0;
byte year = 0;
byte lastMinute = 0;
byte heart = 7;
byte lastHeartBeat = 0;
byte heartBeat =0;
byte secCount=60;
   
void setup() {
  pinMode(heart,INPUT_PULLUP);  // heartbeat input
  Wire.begin();
  Wire.beginTransmission(DS1307); //setting up heartbeat
  Wire.write(byte(0x07));  // control register DS1307
  Wire.write(byte(0x90));  // SQR 1 Hz 1001 0000 
  Wire.endTransmission();
  Serial.begin(9600);
  Serial.println("year,month,monthday,weekday,hr,min,sec");
}

void loop() {
 heartBeat = digitalRead(heart);
 if (heartBeat == 1 && lastHeartBeat == 0){  // eerste keer pollen dan
       secCount++;
     if (secCount>=60){readTime();secCount=0;}                                 // tijd uitlezen
       lastHeartBeat = 1;                          // tijd is gelezen 
     }
 else if (heartBeat == 0) {
     lastHeartBeat = 0;     // klaar om de volgende puls te lezen
  }
}

byte bcdToDec(byte val) {
  return ((val/16*10) + (val%16));
}

void readTime() {
  Wire.beginTransmission(DS1307);
  Wire.write(byte(0));
  Wire.endTransmission();
  Wire.requestFrom(DS1307, 7);
  second = bcdToDec(Wire.read());
  minute = bcdToDec(Wire.read());
  hour = bcdToDec(Wire.read());
  weekday = bcdToDec(Wire.read());
  monthday = bcdToDec(Wire.read());
  month = bcdToDec(Wire.read());
  year = bcdToDec(Wire.read());
  printTime();
}

void printTime() {
  Serial.print(year);
  Serial.print("     ");
  Serial.print(month); 
  Serial.print("      ");
  Serial.print(monthday); 
  Serial.print("    ");
  Serial.print(weekday);  
  Serial.print("     ");
  Serial.print(hour);
  Serial.print("   ");
  Serial.print(minute); 
  Serial.print("   ");
  Serial.print(second);
  Serial.println(" ");
}

So you think the wire library is somehow allowing the SAM3X to stop sending clock pulses part-way through the read? That would look like the RTC hanging the bus lines because it is just putting a data bit on the line and waiting for the clock line to change. You could let that sit "forever" and then toggle the clock line and get the next bit a month later.

The I2C sender also waits to see an ACK bit from the receiver, but at that point it should not be holding any of the lines.

MorganS:
So you think the wire library is somehow allowing the SAM3X to stop sending clock pulses part-way through the read? That would look like the RTC hanging the bus lines because it is just putting a data bit on the line and waiting for the clock line to change. You could let that sit "forever" and then toggle the clock line and get the next bit a month later.

The I2C sender also waits to see an ACK bit from the receiver, but at that point it should not be holding any of the lines.

Yes that is exactly what I observe, when I push reset while the DUE is in the middle of a read cycle, SDA is pulled LOW by the slave and SCL is HIGH by pullup resister. When the program is restarted no clock pulse comes on the SCL line. The I2C bus hangs.
I can remove the SCL line from the DUE and manually sent some clock pulses into the slave (at that moment there is no pullup resistor on SCL, but the logic analyzer is connected to SCL, giving it a weak pullup). The clock pulses make the SDA line high again (slave releases SDA) and when i reconnect the SCL to the DUE the program can read from the RTC again.

When I do the same sketch on the mega2560 board, I can push the reset many times and the bus will not hang, the Mega takes over the bus after 0.9 seconds.
After a reset SCL is pulled up HIGH always.
SDA can be LOW then the mega starts with one or a few clockpulses and the program starts again with setup the write(0x68). SDA can be HIGH then the SDA line go down after 0.9 s and after that on the first rising clock the setup write(0x68) follows.

So yes, the hanging SDA line seems DUE related, so apparently wire-library related.

I didn't understand that you're pressing the RESET button in the middle of an I2C data byte. The processor must reset immediately as soon as it sees that: it can't wait for the byte to finish. The library can't control this. I have no idea how the Mega gets away with it - maybe it sends some dummy clocks in Wire.begin() or it sends random garbage during the reset?

This is why adding a delay() works sometimes - it increases the likelihood that you are resetting during a delay. If you need to press reset as part of normal operations, then reset must also reset the DS1307. You shouldn't reset only half of your project.

If you have a spare output pin on the DUE (with enough current capability) then power the DS1307 from that. Treat it like an SPI chip-select. The DS1307 always runs its internal clock on its own battery but the I2C interface is powered from external power, precisely to allow you to do this kind of reset.

MorganS:
I have no idea how the Mega gets away with it - maybe it sends some dummy clocks in Wire.begin() or it sends random garbage during the reset?

Would'nt it be a good idea for the library to let the slaves know it is in control over the bus after a reset by sending a few clockpulses ?
If the master is waiting for the dataline to become free after a startup, that is not so smart, it should take control i believe.

Maybe there is a wire library function I can put in the setup that just does that .

Resetting the RTC would be OK if there was a special pin for that. DS1307 does not have one.
DS3231 does have a /RST input on the chip but usually it is not connected to one of the board-connection-pins.

Would'nt it be a good idea for the library to let the slaves know it is in control over the bus after a reset by sending a few clockpulses ?

I would give it a try ... what about something like this in setup, just before Wire.begin()?

 pinMode(21, OUTPUT);
  for (int i = 0; i < 8; i++) {
    digitalWrite(21, HIGH);
    delayMicroseconds(3);
    digitalWrite(21, LOW);
    delayMicroseconds(3);
  }
  pinMode(21, INPUT);
1 Like

That is a good workaround, did not encounter hangups anymore.

tnx dlloyd.

Many thanks dlloyd. This patch appears to work for me also (had same problem, mysterious i2c hangups with a frequently-rebooted Due). SDA low, SCL high, frozen bus.

Since incorporating your little "drain cleaner" into my setup function I have not seen this problem again.