← index #13536PR #11255
Related · medium · value 1.913
QUERY · ISSUE

RP2040 I2C bus breaks if pins are changed at runtime

openby desultoryopened 2024-01-27updated 2024-02-12

MicroPython v1.22.1 on 2024-01-05; Raspberry Pi Pico with RP2040

If I attempt to re-init the bus on another set of pins, things break and cannot recover until I do a full hardware reset:

>>> from machine import Pin, I2C
>>> a = I2C(1, freq=400000, scl=27, sda=26)
>>> a.readfrom_mem(118, 0xA1, 1)
b'K'
>>> a = I2C(1, freq=400000, scl=3, sda=2)
>>> a.readfrom_mem(118, 0xA1, 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 5] EIO
>>> a = I2C(1, freq=400000, scl=27, sda=26)
>>> a.readfrom_mem(118, 0xA1, 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 5] EIO

I'm not sure how else I can recover from this other than unplugging it and plugging it back in, soft resets do not correct the issue.

CANDIDATE · PULL REQUEST

rp2: Add timeout parameter for machine.I2C()

closedby DavidEGraysonopened 2023-04-13updated 2023-04-21
port-rp2

We noticed that sometimes our MicroPython examples on the Pololu 3pi+ 2040 Robot that use I2C would crash, causing all interfaces of the MicroPython interpreter to become unresponsive, including the USB mass storage interface. This is because the user might reset the RP2040 while it is in the middle of reading data from an I2C device. The device would then be stuck in a state where it holds its SDA line low. This caused an infinite loop in i2c_write_blocking or i2c_read_blocking (apparently the RP2040 I2C hardware wants to wait for SDA to go high).

You can reproduce this problem on a Raspberry Pi Pico by tying pin 4 to GND and running the following simple code:

from machine import Pin, I2C; i2c = I2C(id=0, scl=Pin(5), sda=Pin(4))
i2c.readfrom_mem(0x6B, 0x22, 6)

Here is a stack trace from GDB showing where the infinite loop is happening:

(gdb) where
#0  0x1002e4e8 in i2c_write_blocking_internal ()
#1  0x1002e6f8 in i2c_write_blocking ()
#2  0x100271ae in machine_i2c_transfer_single ()
#3  0x10019bda in mp_machine_i2c_transfer_adaptor ()
#4  0x100192c6 in mp_machine_i2c_writeto ()
#5  0x100194b4 in read_mem ()
#6  0x10019522 in machine_i2c_readfrom_mem ()
#7  0x1000f9ee in fun_builtin_var_call ()
#8  0x10017430 in mp_call_function_n_kw ()
#9  0x10017500 in mp_call_method_n_kw ()
#10 0x20002c4c in mp_execute_bytecode ()
#11 0x1000fb24 in fun_bc_call ()
#12 0x10017430 in mp_call_function_n_kw ()
#13 0x10017448 in mp_call_function_0 ()
#14 0x10026430 in parse_compile_execute ()
#15 0x100266ce in pyexec_friendly_repl ()
#16 0x1002949c in main ()

An infinite loop like that is unpleasant and hard to debug. It would be much better to have some kind of exception get raised when the problem happens. The solution in this pull request is to call i2c_write_timeout_us and i2c_read_timeout_us instead of their blocking counterparts. Now we get an OSError (ETIMEDOUT) instead of an infinite loop.

Adding this timeout might be a breaking change for some users doing really slow I2C transactions, so I think it's best to make the timeout configurable, like it is on the esp32 and stm32 ports.

@dpgeorge has set a precedent for using 50 ms timeouts in commit a707fe50, so I chose that as the default.

I also fixed the default timeout on the esp32 port to be consistent with the rp2 and stm32, which makes the parameter easier to document in the documentation I added.

Maybe @dhalbert or @peterhinch could review this.

P.S. To fix the root cause of the issue, we will be calling i2c.writeto(0, "") 10 times in our example code, which generates enough pulses on SCL to get the device back to its normal state.

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied