Advamation-logo (print version)

Raspberry Pi I2C clock-stretching bug

Date: 2013-08-17

Summary

The Broadcomm BCM2835, which is used on the Raspberry Pi, has a serious bug in its I2C implementation, which can (a) prevent I2C communication with some devices and (b) lead to data corruption (both in read and write direction).

As a result, do not use I2C-devices which use clock-stretching directly with the Raspberry Pi directly or any other Broadcomm BCM2835-based device.

Fix:
None
Workarounds:
see below

Details

The I2C-specification allows slaves to stretch the clock (=hold SCL low during a communication to slow down the communication and to make the master wait until they are done). After an I2C-slave releases the clock again, the master must continue to clock SCL.

The bug of the Raspberry Pi (or actually of its ARM-processor Broadcomm BCM2835) is, that its I2C-clock is only "masked" when the slave pulls SCL low, and the Raspberry Pi does not assure that the next clock afterwards has full length. This can lead to invalid SCL-"spikes", which are too short to be recognized by the slave, and so the master and the slave can get "out of sync". In addition, the Raspberry Pi seems to read the SDA value while SCL is still pulled low by the slave, so even very very short stretches make the Raspberry Pi read wrong data.

RPi I2C bug

This can result in corrupted data and/or stuck devices. The only case where clock-stretching works correctly with the Raspberry Pi is, when the slave stretches the clock directly after the I2C-read-ACK-phase, and then only when the slave stretches the clock by more than 0.5 I2C clock periods!

So, I2C with the Raspberry Pi only works if

  • the slave does not use clock-stretching at all, or
  • the slave stretches the clock only at the end/directly after an I2C-read-ACK-phase (after reading ACK/NACK), but then by more than 0.5 clock periods.

I2C may fail and may corrupt data etc., if:

  • the slave stretches SCL at the beginning of an I2C-write-ACK-phase,
  • the slave stretches SCL at the beginning of an I2C-read-ACK-phase,
  • the slave stretches SCL at the end of an I2C-read-ACK-phase by less then 0.5 clock periods (even very very short stretches lead to corrupted data), or
  • the slave stretches SCL in non-ACK-phases.

Workarounds:

  • Do not use I2C-devices which use clock-stretching with the Raspberry Pi. Fortunately, many I2C-sensors do not use clock-stretching.
  • If you know how fast your I2C-device is, you could chose a slower I2C-clock-frequency, so that the device does not stretch the clock.
  • Use I2C-/SMBus-devices which support a CRC.
    (Our I2C-boards/sensors which contain a microcontroller use a CRC.)
  • If you use the AdvaBoard RPi1, you can configure it to extend too short clock-stretches during an I2C-read-acknowledge-phase, so that I2C-sensors with arbitrary I2C-read-ACK-stretches work.
  • If you use the AdvaBoard RPi1, a microcontroller on the AdvaBoard RPi1 could be used as "I2C-proxy" and so completely work around the problem. This way, all I2C-devices should work.
  • If you use self-programmed microcontrollers as I2C-slaves, add a CRC; or use a fast interrupt-service-routine for I2C-write which never stretches the clock, and add a delay to I2C-read which always stretches the I2C-read-ACK-phase by more than 0.5 I2C clock periods.
  • Use a SPI->I2C, RS232->I2C or RS485->I2C adapter.
    (We'll offer such adapters, soon.)

Don't expect help from the Raspberry Pi creators or from Broadcomm. They know the problem (at least partially), but they unfortunately did neither document nor solve it.

Measurement data

Here are some measurements with the Raspberry Pi as I2C master and a microcontroller (SiLabs C8051F353) as I2C-slave. The microcontroller was fast enough to even catch the SCL spikes. But since the Raspberry Pi reads SDA too early, the data got corrupted, anyway.

I have used different clock-stretching delays and measured SCL and SDA with an Oscilloscope. But note that I've only tested clock-stretching delays in the ACK-phase.

I2C Read

Configuration:

  • I2C read
  • I2C slave address: 0x4F
  • read 2 bytes: 0x00 0x00
  • varying ACK/NACK-delays

Results:

  • I2C clock-stretching only work at the end of I2C-read-ACK-phases (after reading ACK/NACK), not at the beginning.
  • I2C clock-stretching at the end/directly after an I2C-read-ACK-phase only works correctly, if the slave stretches SCL by more than 0.5 I2C clock periods. (see delays/picture-numbers >= 11 below)
  • Delays which are shorter than 0.5 I2C clock periods corrupt the data, even when the delay is very short and even when the slave catches the too-short clock pulse. (see delays/picture-numbers 1..10 below)
Data:
OK (0x4F 0x00 0x00) Error (0x4F 0x00 0x80)
OK (0x4F 0x00 0x00) (click to enlarge) Error (0x4F 0x00 0x80) (click to enlarge)
Details:

Now, the part where the read-error occurs is shown in detail:

relevant part
relevant part (click to enlarge)

Animated sequence of the relevant part with delays 1..42: read_01-42.gif (GIF, 2.8 MB)
Some interesting pictures of the sequence; the relevant SCL-pulse, which follows the delay, is marked (red=error, green=OK):

SDA/SCL at delay 1 SDA/SCL at delay 7 SDA/SCL at delay 10 SDA/SCL at delay 13 SDA/SCL at delay 25 SDA/SCL at delay 26

(click to enlarge)

Additional animated sequence, which illustrates the bug:
complete data (GIF, 1.4 MB), relevant part (GIF, 1.1 MB)

I2C Write

Configuration:

  • I2C write
  • I2C slave address: 0x4F
  • write 1 byte: 0xAA
  • varying ACK/NACK-delays

Result:

  • I2C clock-stretching does not work at the beginning of an I2C-write-acknowlege-phase. But this would be the relevant point, since the I2C-device here has to decide whether to send an ACK or NACK.
  • If clock-stretching works at the end/directly after an I2C-write-acknowlege-phase was not yet tested.
Data:

Animated sequence with delays 0..45: write_00-45.gif (GIF, 5.1 MB)
Some interesting pictures of the sequence (incl. red marks at the erroneous SCL-pulses, which follows the clock-stretching:

SDA/SCL at delay 0 SDA/SCL at delay 7 SDA/SCL at delay 39

(click to enlarge)