pico-sdk
pico-sdk copied to clipboard
Possible lockup-issue with `i2c_write_blocking` versus `i2c_write_timeout_us` (maybe conflicting with USB serial?)
Hi, I am working on a Pimoroni RP2040 product that involves regular communication with some onboard I2C devices at 400KHz.
During my testing I was encountering frequent lock-ups after a call to i2c_write_blocking had completed. I checked this by setting a pin high before the call and to low afterwards. The pin would go low, suggesting the function had completed, then the lockup would occur before reaching my next line of code.
This was very strange to me, as I had successfully got the product working with a custom build of CircuitPython, which I had written C level drivers for. Checking how they implement I2C, I saw that i2c_write_timeout_us was used, with a 1 second timeout.
Switching to this call in my code the lock-ups went away, without any timeouts actually getting triggered. Checking the pico-sdk, I see that both functions call i2c_write_blocking_internal, with the latter having a callback that results in the below if statement getting executed.
https://github.com/raspberrypi/pico-sdk/blob/6a7db34ff63345a7badec79ebea3aaef1712f374/src/rp2_common/hardware_i2c/i2c.c#L166-L172
Suspecting that the longer loop time the timeout call was having a positive effect, I added a sleep_us(10) as an else condition, and sure enough the lock-ups from i2c_write_blocking_internal go away. Switching to a debug build also made the issue go away, thus ruling out SWD debugging.
Also, sending CTRL-C via the USB terminal would break the board out of the lock-up, though devices like PIO would not resume.
I am completely baffled by this!
For now I can switch my code to using the timeout function, and carry on. I just thought I would raise it in case there is an actual issue that needs resolving.
P.S: It may be inconsequential, but I have observed that my "debug" pin goes low before I2C comms complete, for the i2c_write_blocking code path:
Whereas my "debug" pin goes low after the I2C comms complete, for the i2c_write_timeout_us code path:
Okay, me just switching to i2c_write_timeout_us isn't enough to stop the lock-ups. Seems adding sleep_us(10) into both of those do...while loops works though. Putting a sleep after the loop (or even after the function call) has no effect.
I'm suspecting I2C may be a red herring though, as the lock-ups (without those added sleeps) seem more prevalent when there is more USB serial output, which is related to changes from the I2C devices. Just tested now and I don't get any lock-ups if I turn off the printouts after removing those sleeps.
Does the problem go away if you use a uart instead of usb? Do you have an example that demonstrates the issue?
I am also seeing frequent lockups on the i2c bus (both instances).
On i2c1, my code is polling four colour sensors, each on their own pair of SDA/SCL pins (as the sensors are fixed with a single i2c address). Disabling the i2c, reconfiguring the bus pins, and reenabling the i2c was often leaving the SDA pin latched low. Adding a sleep_us(100) between bus switches appears to have worked around the issue. Power cycling the colour sensors clears the bus which indicates it was latching the SDA line.
On the i2c0, I am talking to a single MPU6050. In this case again the SDA line is being latched low by the slave.
The logic analyser trace would indicate that under some circumstances the i2c may not be sending the clock cycle required to clock in the slave device ACK, which leaves the slave holding the SDA line low.
Bit-banging a single clock cycle on SDA causes the slave to release the SDA line.