Go Down

Topic: I2C state during sleep causes excessive power consumption (Read 3854 times) previous topic - next topic


There is an issue with the I2C bus state when using low power deep sleep.  The problem occurs when the MCU is sleeping.  When the I2C master goes to sleep, the SCL and SDA lines are pulled low.  This causes excessive power consumption during sleep because of the I2C pull up resistors.  The next time the master wakes up, the SCL and SDA lines are pulled high and stay that way until the next I2C transaction.  This cycle repeats.  Here is an example sketch attached below.  In this example, the rtc alarm event fires every 10 seconds and the heart beat timeout is 30 seconds.  In this case, on the first heartbeat timeout at t=30 seconds, the I2C message is sent and the SCL and SDA lines are pulled low.  At t=40 seconds, the MCU wakes up and the SCL and SDA lines are pulled high.  At t=50 seconds the SCL and SDA remain high (no I2C message sent).  At t=60 seconds, an I2C message is sent and the SDA and SCL are pulled low ...

Code: [Select]
void loop() {
  if ( rtcTick >= heartBeatTick ) {
    heartBeatTick += HEARTBEAT_PERIOD;

void rtcAlarmEvent() {
// RTC alarm wake interrupt callback
  rtcTick += RTC_ALARM_PERIOD;
  rtc.setAlarmEpoch( rtcTick );  //  Set the time for the next RTC Alarm

I have not determined why the SCL and SDA pins are being pulled low at the end of the I2C transaction.  If I insert a large delay(100) after the endTransmission() the SCL and SDA lines are pulled high properly at the end of the bus cycle.


I determined the source of this problem.  When the Wire tells the I2C to stop the bus cycle, the command is issued and synced to the peripheral.  The command to stop is non-blocking so program execution continues.  If you subsequently go to sleep right away before the stop sequence has not been completed by the peripheral, the master still owns the bus and keeps SCL and SDA low.  To fix this, you need to insert a short delay (about 20 us) after issuing the STOP command.

I'll submit a PR on Github for this issue.


Great work identifying the problem and finding a solution. Thanks for your contributions to the Arduino project sslupsky!


I've given this some thought and I believe I understand a way to resolve the issue without creating a race condition by using an arbitrary delay.

I've updated the reference GitHub issue above.

When a command is written (CTRLB CMD) to send a stop bit, the library sends the command but does not wait for the bus to return to the idle state.  This occurs in four places:

I believe the proper way to address this issue is to add the following line after each of the above statements:

while (!sercom->isBusIdleWIRE()) {}

I'll submit a PR for this.  Is there any feedback I should consider before I do so?

One concern I have is if there is any reason that the the bus would not return to idle after issuing the stop bit it would cause this condition to wait indefinitely.

Go Up