How To Debug I²C Read And Write Problems
The imp API provides users of I²C peripheral devices (‘peripherals’) with debugging information following failed attempts to read or write data to any I²C peripheral connected to an imp. This information comes in the form of an integer constant returned by either i2c.write() or i2c.readerror(), depending on the type of operation being performed. If either method returns any value other than 0, an error has occurred. The return value indicates the nature of the error, and these errors are listed in the i2c.readerror() documentation. Should an error occur, the I²C transaction is abandoned.
This document describes these errors in more detail. They are presented in the order in which they are likely to be encountered, with those relating to write operations appearing first, followed by those issued during read operations.
The imp API makes sending and receiving data via I²C very straightforward, but ‘under the hood’ the process as is complex. As such, a single imp API I²C access may result in any one of a series of errors, depending on which stage of the process the underlying impOS™ I²C code has reached.
The I²C Protocol
Developers working with imp005-based devices should note that the following applies primarily to other imp devices. The imp005’s Broadcom SoC will report only two I²C error values: -13 and -9. The former is as described below, but the latter covers almost all of the remaining I²C errors: unfortunately, some error conditions aren’t reported at all (success is falsely indicated). We hope to improve imp005 I²C error reporting in a future version of impOS.
This is a straightforward error that is issued when you have attempted to access an imp I²C bus that has not yet been configured. Check your code and, if necessary, call i2c.configure() with a supported speed constant passed as a parameter.
The imp signals its intention to begin an I²C transaction by establishing the standard I²C start condition: it attempts to pull the SDA line low (so the waveform has a falling edge) while the SCL line remains high. If the imp is unable to pull SDA low, this error will be issued.
This error may arise if another I²C master is operating on the same bus and has taken control of it. If the imp is the only master on the bus, this error may result from poorly chosen pull-up resistors. I²C ports are open drain so can only pull the SDA and SCL lines low; pull-up resistors are required to drive the lines high when they are released by bus devices.
After the imp has signalled start and is ready to write data to it, it sends the 7-bit address of the I²C peripheral it wants to communicate with, followed by a single bit indicating whether the transaction is a write (the imp pulls SDA low) or a read (the imp leaves SDA high). These eight bits should be acknowledged by a single-bit ACK signal from the peripheral at the transmitted address; it pulls SDA low. If the acknowledgement doesn’t occur during the ninth clock pulse, then the imp will issue this error.
If this error is encountered, check the value of the peripheral’s address that you are passing. Some devices have multiple addresses, selectable by setting an address pin to one of three states (high, low or floating). The imp API takes addresses in 8-bit form; 7-bit addresses will need to be bit-shifted left one place.
Once the peripheral has acknowledged its readiness, the imp can begin to send the data it wants to write. Data is sent byte by byte, each one written to the imp’s data register from which it is passed to a shift register and from there out onto the bus one bit at a time. Once the peripheral has clocked in a byte of data, it should acknowledged receipt. If it fails to do so while the imp is processing any byte of the data but the last, a transmit error will be issued. If the error occurs sending the last byte, a BTF error is reported instead.
A transmit error may also be reported if the bit value clocked out is not seen on the bus, ie. the imp sends a 1, ie. it leaves the SDA line high, but the SDA line is pulled low. This could indicate bus contention — there is another master on the bus — or, more likely, that the I²C circuit’s pull-up resistors are too weak to keep the lines high.
Once the imp has successfully written all the data it wants to send to the peripheral (or read back all the data that it requested), it signals the completion of the transaction with a stop condition: while SCL is high, SDA is released to go high too (the waveform has a rising edge). This releases the bus for other devices to make use of it. Again, the imp checks that this stop signal has been sent correctly. If it has not, then this error will be issued.
During a read operation, the imp places on the bus the I²C address of the peripheral it wants to read data from. This event should be acknowledged by the peripheral. If it is not, the imp will return this error.
All of these errors indicate a failure to read a byte from the peripheral at some point during the read operation. This is often caused by the peripheral holding the SCL line low while it retrieves the requested byte(s) — a technique called ‘clock stretching’.
Which error you get will depend on the number of bytes you have requested to be read and on the exact point where the receive error occurred.
If the imp wishes to read data from a specific peripheral register, it must first write that register’s address to the peripheral as a data value. After the register address has successfully been written, the imp initiates a read operation, to pick up the value the peripheral returns from the register. The imp switches from write mode to read mode by issuing a second start signal. Once again, it checks that it can proceed. If it is unable to do so within the timeout period, this error will be issued.