Possible lockup-issue with i2c_write_blocking versus i2c_write_timeout_us (maybe conflicting with USB serial?) #1471
Labels
No labels
bazel
bluetooth
board settings
boot_stage2
bug
build
c++
can't reproduce
clang
clib-interface
cmsis
code cleanliness
code size
compiler version issues
cyw43_driver
debug
discussion
documentation
duplicate
dynamic_clock
elf2uf2
enhancement
errata
filesystem
flash layout
floating point
FreeRTOS
hardware_adc
hardware_claim
hardware_clocks
hardware_dma
hardware_exception
hardware_flash
hardware_gpio
hardware_i2c
hardware_irq
hardware_pio
hardware_pwm
hardware_rcp
hardware_rtc
hardware_spi
hardware_sync
hardware_uart
hardware_vreg
hardware_watchdog
hardware_xosc
help wanted
host
IAR
installation
invalid
linker scripts
lto
lwip
mbedtls
missing API
needs reporter detail
networking
newlib
newlib-multicore
non-c
non-cmake
non-gcc
openocd
orphan PR
OS integration
pico_async_context
pico_base
pico_binary_info
pico_flash
picolibc
pico_lwip
pico_malloc
pico_multicore
pico_printf
pico_runtime
pico_stdio
pico_stdio_usb
pico_sync
pico_time
pico_util
pioasm
port
question
review
review2
rtos interop
runtime init
static library
svd
test
tinyusb
vscode
wifi integration
wontfix
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-raspberrypi/pico-sdk#1471
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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_blockinghad 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_uswas 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.github.com/raspberrypi/pico-sdk@6a7db34ff6/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 fromi2c_write_blocking_internalgo 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_blockingcode path:Whereas my "debug" pin goes low after the I2C comms complete, for the

i2c_write_timeout_uscode path:Okay, me just switching to
i2c_write_timeout_usisn't enough to stop the lock-ups. Seems addingsleep_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.
Wow! I was encountering I2C lockups, and it took me a full half day to start considering that something might be wrong other than my own hardware or code. Thank you all very much for bringing up this thread, it's been a big help. I'm attempting to work around the issue with the
i2c_write_timeout_ustechnique mentioned by @ZodiusInfuser for now. Hopefully this will be fixed soon.How does such a fundamental part of the pico-sdk get broken? I'm surprised that there hasn't been a bigger stink about I2C failing in a fatal way like this. Does this bug only impact certain devices under very particular conditions?
In case it helps, I'm using a M24C02 EEPROM at 100kHz, right up next to GPIO 2-3 on the RP2040. I have 2.2kOhm pullup resistors on both SDA and SCL. I have another device (MCP4017 digipot) on the same net, a few millimeters further from the RP2040 than the EEPROM, and haven't had any lockup issues with that device when writing to it over I2C.
I just updated my project to SDK 2.1.1 and began having these same issues. I implemented a wrapper function so I can use the SDK 1.5.1 version of the read/write I2C functions and the lockup issue went away completely.
Note that @ZodiusInfuser reported this problem prior to SDK 2.0.0 - I suspect it is entirely timing related
We're now seeing this on the GP2040-CE game controller project (https://github.com/OpenStickCommunity/GP2040-CE) after updating to SDK 2.1.1 from SDK 1.5.1. I was battling updating our TinyUSB / PICO-PIO-USB code as there are a few bugs in those libraries, but I finally got everything working as intended.
Weirdly it only happens when we use the rndis net driver, otherwise i2c works fine with standard TinyUSB tud_task code. The i2c read is getting locked in the exact same spot as others have reported.
@peterharperuk I have a GP2040-CE dev board I could ship you, I'm not sure if shipping would be crazy expensive from the USA but I can 100% replicate it on my end. Feel free to ping me on the GP2040-CE discord https://discord.gg/k2pxhke7q8
@arntsonl I'm not a discord user but I'm interested in getting some hardware if I can use it to reproduce the problem. My user name is peterharper_75037 and I've accepted your invite.
I made this speculative change you could try https://github.com/raspberrypi/pico-sdk/pull/2493
There's also this which you could try reverting https://github.com/raspberrypi/pico-sdk/pull/2026
Unfortunately the GP2040-CE i2c issue seems to be a different problem https://github.com/OpenStickCommunity/GP2040-CE/pull/1420
Still looking for a way to reproduce the "i2c is completely broken" problem.
I am still experiencing lockups of the
i2c_write_timeout_per_char_us()in my commercial RP2040 project and I think I found the reason in that routine calling thei2c_write_blocking_internal().The
i2c_write_blocking_internal()is called withinit_per_iteration_timeout_us()fromtimeout_helper.cas a parameter.Within the
i2c_write_blocking_internal()it is referenced astimeout_check()that should initialise the timeout for every character sent and also check the timeout after writing to the i2c hardware.The timeout will never occur !
The
timeout_check()should ONLY call thecheck_per_iteration_timeout_us()fromtimeout_helper.cinstead ofinit_per_iteration_timeout_us()init_per_iteration_timeout_us returns check_per_iteration_timeout_us?
Yes, you are right... I did not see, that after the first call of
init_per_iteration_timeout_us, it returns thecheck_timeout_fn-type, so it will call that pointer instead ofinit_per_iteration_timeout_usin succession.I'm sorry... must have been blind.
I found the real reason for my problem. My development board is working with 2.2.0 where everything is fine, but in production, the project was still built with 1.5.1, that does the timeout reset every call and sometimes leads to hanging i2c.
You can delete my comment.
No worries. Glad you found your problem.