Hardware I2C doesn't work on ESP32-C5
Port, board and/or hardware
esp32 port, ESP32-C5-DevKitC-1 v1.2
MicroPython version
MicroPython v1.27.0 on 2025-12-09; ESP32C5 module with ESP32C5
Reproduction
import machine
i2c0 = machine.I2C(id=0)
i2c1 = machine.I2C(id=1)
i2c0.scan()
i2c1.scan()
Expected behaviour
i2c0 and i2c1 should be created with no errors, and both should be able to scan and perform read/write operations.
Observed behaviour
Attempting to create i2c0 causes a reboot:
MicroPython v1.27.0 on 2025-12-09; ESP32C5 module with ESP32C5
Type "help()" for more information.
>>> import machine
>>> i2c0 = machine.I2C(id=0)
ESP-ROM:esp32c5-eco2-20250121
Build:Jan 21 2025
rst:0x1a (CPU_LOCKUP),boot:0x5b (SPI_FAST_FLASH_BOOT)
Core0 Saved PC:0x4200757e
SPI mode:DIO, clock div:1
load:0x408556b0,len:0xdfc
load:0x4084bba0,len:0xc4c
load:0x4084e5a0,len:0x2ee8
entry 0x4084bba0
MicroPython v1.27.0 on 2025-12-09; ESP32C5 module with ESP32C5
Type "help()" for more information.
>>>
Attempting to create i2c1 results in error messages. The I2C scan (and read/write methods) complains that the driver is not installed:
MicroPython v1.27.0 on 2025-12-09; ESP32C5 module with ESP32C5
Type "help()" for more information.
>>> import machine
>>> i2c1 = machine.I2C(id=1)
E (22747) i2c: i2c_driver_install(305): LP_I2C is not supported via i2c_driver_intall()
>>> i2c1 = machine.I2C(id=1)
E (24887) i2c: i2c_driver_delete(484): i2c driver install error
E (24887) i2c: i2c_driver_install(305): LP_I2C is not supported via i2c_driver_intall()
>>> i2c1.scan()
E (29467) i2c: i2c_master_cmd_begin(1552): i2c driver not installed
E (29467) i2c: i2c_master_cmd_begin(1552): i2c driver not installed
E (29467) i2c: i2c_master_cmd_begin(1552): i2c driver not installed
...
E (30107) i2c: i2c_master_cmd_begin(1552): i2c driver not installed
E (30107) i2c: i2c_master_cmd_begin(1552): i2c driver not installed
E (30117) i2c: i2c_master_cmd_begin(1552): i2c driver not installed
[]
>>>
Additional Information
I also noticed i2c1 (which is presumably the LP I2C peripheral) uses scl=9, sda=8:
>>> i2c1
I2C(1, scl=9, sda=8, freq=400000, timeout=50000)
However LP I2C on the ESP32-C5 can only be used on pins 2 and 3 (see the ESP32-C5 datasheet, section 4.2.1.3, "Pin Assignment").
FWIW SoftI2C() seems to work fine.
Code of Conduct
Yes, I agree
feature/optional_i2c_bus
Summary
This pull request gives the option to not pass an I2C Bus ID when creating a machine I2C object on RP2 and ESP32. If the ID is not provided on RP2, the default bus ID (PICO_DEFAULT_I2C) is used. For ESP32, I2C_NUM_0 is used. This would allow users on both platforms to simply declare an I2C object with machine.I2C() without passing any arguments, thus creating an object with the default I2C ID, SCL, and SDA.
Our use case is a higher-level python driver that creates machine.I2C objects. We would like the driver to be portable to many different RP2 and ESP32 boards without having to explicitly specify these arguments.
Testing
Tested on RP2 with Raspberry Pi Pico W:
- Created machine.I2C object by passing no arguments to I2C(). Verified that it successfully could use the scan() method to detect a connected sensor.
- Exercised I2C object by using it with an example program performing reads and writes to a sensor.
Tested on ESP32 with Sparkfun Thing Plus - ESP32 WROOM (Micro-B):
- Created machine.I2C object by passing no arguments to I2C(). Verified that it successfully could use the scan() method to detect a connected sensor.
- Exercised I2C object by using it with an example program performing reads and writes to a sensor.
Trade-offs and Alternatives
- For ESP32, this required removal of the MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION() macro in machine_hw_i2c_make_new(). This macro would cause the I2C object to be created with an ID of -1 if not explicitly specified. This would cause a SoftwareI2C object to be created. Thus, this could break backwards compatibility for ESP32 if users have code where they don't specify the ID (or specify it as -1) and expect that to lead to creation of a SoftwareI2C object. However, this is a legacy construction and likely should be removed anyways. It is a more intuitive behavior for creation of an I2C object with no ID argument to get an I2C object with default ID rather than an entirely different SoftwareI2C object.
- As with any default argument that is optionally supplied by a user, making it optional means that it may be more frequently overlooked. The default may sometimes be used when the user actually should look into it deeply enough to decide which ID/Bus they want to use.