[RP2040] I2C read/write operations fail with EIO in v1.26.1 - works in v1.23.0
Port, board and/or hardware
Port: rp2 Board: Raspberry Pi Pico (RP2040) Hardware: VL53L1X Time-of-Flight sensor connected via I2C1 (SCL=GP15, SDA=GP14)
MicroPython version
MicroPython v1.26.1 on 2025-09-11; Raspberry Pi Pico with RP2040
Type "help()" for more information.
Firmware file: RPI_PICO-20250911-v1.26.1.uf2
Issue: I2C operations fail with OSError: [Errno 5] EIO
Tested also with v1.23.0 (working version):
MicroPython v1.23.0 on 2024-06-02; Raspberry Pi Pico with RP2040
Firmware file: rp2-pico-20240602-v1.23.0.uf2
Result: All I2C operations work correctly
Reproduction
Hardware setup:
VL53L1X sensor connected to Raspberry Pi Pico
- SDA to GP14 (I2C1)
- SCL to GP15 (I2C1)
- VIN to 3.3V
- GND to GND
from machine import Pin, I2C
import time
Initialize I2C
i2c = I2C(1, scl=Pin(15), sda=Pin(14), freq=100000)
print("=== Test Results ===")
Test 1: Scan (works in both versions)
devices = i2c.scan()
print(f"1. Scan: {[hex(d) for d in devices]}")
Test 2: ACK test with empty write (works in both versions)
try:
i2c.writeto(0x29, b'')
print("2. ACK test: OK")
except Exception as e:
print(f"2. ACK test: FAILED - {e}")
Test 3: Write 1 byte (FAILS in v1.26.1, works in v1.23.0)
try:
i2c.writeto(0x29, bytes([0x00]))
print("3. Write 1 byte: OK")
except Exception as e:
print(f"3. Write 1 byte: FAILED - {e}")
Test 4: Read data (FAILS in v1.26.1, works in v1.23.0)
try:
data = i2c.readfrom(0x29, 1)
print(f"4. Read 1 byte: OK - {hex(data[0])}")
except Exception as e:
print(f"4. Read 1 byte: FAILED - {e}")
Test 5: Read from memory (FAILS in v1.26.1, works in v1.23.0)
try:
data = i2c.readfrom_mem(0x29, 0xC0, 1)
print(f"5. Read from register: OK - {hex(data[0])}")
except Exception as e:
print(f"5. Read from register: FAILED - {e}")
Expected behaviour
All I2C operations should succeed as they do in v1.23.0:
=== Expected Output (v1.23.0) ===
- Scan: ['0x29']
- ACK test: OK
- Write 1 byte: OK
- Read 1 byte: OK - 0x1
- Read from register: OK - 0xdf
The sensor should respond to all read and write operations after being detected during scan.
Observed behaviour
With v1.26.1, any I2C operation that transfers data fails with EIO error:
=== Actual Output (v1.26.1) ===
- Scan: ['0x29']
- ACK test: OK
- Write 1 byte: FAILED - [Errno 5] EIO
- Read 1 byte: FAILED - [Errno 5] EIO
- Read from register: FAILED - [Errno 5] EIO
The sensor is detected during scan and responds to ACK (empty write), but any attempt to read or write actual data fails with OSError: [Errno 5] EIO.
This is a regression from v1.23.0 where all operations work correctly.
Additional Information
Testing details:
- Issue is 100% reproducible across multiple power cycles
- Clean firmware installation (BOOTSEL mode flash)
- Hardware connections verified with multimeter
- Same hardware setup works perfectly with v1.23.0
- Sensor confirmed working: VL53L1X with Model ID 0xEA (verified in v1.23.0)
Impact:
This regression breaks I2C communication with devices requiring register read/write operations, including:
- VL53L0X/VL53L1X Time-of-Flight sensors
- Potentially other I2C peripherals
Workaround:
Downgrade to MicroPython v1.23.0 until the issue is fixed.
Code of Conduct
Yes, I agree
rp2: Add timeout parameter for machine.I2C()
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.