Skip to content

machine/esp32xx: wait for the I2C bus to be free before a transaction - #5978

Merged
deadprogram merged 1 commit into
tinygo-org:devfrom
davecheney:esp32-i2c-nack-busy
Oct 24, 2026
Merged

deadprogram merged 1 commit into
tinygo-org:devfrom
davecheney:esp32-i2c-nack-busy

Conversation

@davecheney

@davecheney davecheney commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Problem

After a NACK, runCommands returns errI2CAckExpected immediately. But the controller keeps SR.BUS_BUSY set for approximately 12 µs (at 100 kHz). If the next Tx starts in this time, it does not complete and gives i2c: timeout during read or i2c: timeout during write. This occurs also when the device is on the bus.

Thus an I2C scan with read only probes finds no devices, because each probe starts directly after a NACK from the previous address.

Fix

Wait for SR.BUS_BUSY to clear at the start of transmit. If the bus does not become free before the timeout, return errI2CBusReadyTimeout.

Test

Hardware: XIAO ESP32-C3 with a PCF8574 LCD backpack at 0x27. Address 0x26 is empty.

Test Before After
Read only scan 0x08 to 0x77 0 devices, 0x27 gives a read timeout 0x27 found
Write only scan 0x08 to 0x77 not tested 0x27 found
400 transactions to 0x27 directly after a NACK from 0x26 most give a timeout 0 failures

Before the fix, SR.BUS_BUSY was 1 directly after the NACK error and became 0 after approximately 12 µs.

Also built for xiao-esp32c6. I did not test the ESP32-S3, but it uses the same code.

Logic trace

XIAO ESP32-C3 with a device at 0x3C, I2C at 100 kHz. Each loop reads 0x3B, which has no device, then 0x3C. Captured with a DSLogic at 10 MS/s.

SCL and SDA on the XIAO ESP32-C3 before and after the fix

Before the fix, no START follows the NACK and the read of 0x3C times out after 40 ms. 100 of 100 reads failed. After the fix, the START comes 9.5 µs after the STOP and the read completes. 0 of 700 reads failed.

@davecheney
davecheney marked this pull request as ready for review October 21, 2026 07:22

@deadprogram deadprogram left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davecheney thank you for working on this. This is edited from an automated review.

I tested it on a XIAO ESP32-S3 with the Seeed XIAO expansion board, which has an SSD1306 at 0x3C and a PCF8563 at 0x51. I2C at 100 kHz, 3 rounds each.

Test dev This PR
Read only scan 0x08 to 0x77 every address after a NACK times out, 0x3C missed in round 1 0x3C and 0x51 found
1 byte write scan 0x08 to 0x77 same timeouts, 0x3C and 0x51 found 0x3C and 0x51 found
400 reads of 0x3C directly after a NACK from 0x3B 400 timeouts 0 failures
400 reads of 0x51 directly after a NACK from 0x3B 400 timeouts 0 failures

So the S3 has the same problem and this fixes it there too.

However, there is an issue if the bus stays busy. For example when a device holds SDA low, every Tx now waits 40 ms and returns errI2CBusReadyTimeout, and nothing gets it out of that state. ESP-IDF resets the FSM when the bus is busy at the start of a transaction, see https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/i2c.c#L1451. I think you should call i2c.resetMaster() before returning the timeout, so a later call can recover.

Any thoughts on that? Otherwise it looks and works as expected.

@deadprogram deadprogram added this to the 0.44 milestone Oct 21, 2026
@deadprogram

Copy link
Copy Markdown
Member

@davecheney did you see my last comment on this PR?

@davecheney

Copy link
Copy Markdown
Contributor Author

I did, sorry, i'll get to this tomorrow, that hardware is in a mess at the moment

@davecheney

Copy link
Copy Markdown
Contributor Author

Thanks. Added i2c.resetMaster() before returning errI2CBusReadyTimeout, same as ESP-IDF.

Tested on a XIAO ESP32-C3 by holding SDA low. While SDA is held, every call fails with a timeout. After release the next call succeeds. SCL held low behaved the same, tested without the reset. On the C3 the bus also recovered without the reset, but the reset matches ESP-IDF and costs nothing. The NACK test still has 0 failures in 1700 pairs.

ESP-IDF also sends up to 9 SCL pulses to free a device that holds SDA. I left that out. A device stuck in the middle of a read would still need it.

Builds for xiao-esp32c3, xiao-esp32c6 and xiao-esp32s3. Only the C3 was tested on hardware.

After a NACK, the controller keeps the bus busy for approximately 12 us.
If the next transaction starts in this time, it does not complete and
gives a timeout. Thus a read only I2C scan finds no devices.

Wait for SR.BUS_BUSY to clear before the transaction starts. If the bus
stays busy, reset the FSM before returning the timeout, as ESP-IDF does.
@davecheney

Copy link
Copy Markdown
Contributor Author

Stuck bus test on a XIAO ESP32-C3 with the squashed commit. A Tx to 0x3C runs every 100 ms. Each fault was held by hand, then released.

Tx results on the XIAO ESP32-C3 with SDA held low, SCL held low, and SDA shorted to SCL

While a fault is held, every Tx fails with a timeout. The first Tx after release succeeds. With SDA shorted to SCL, the controller clocks SCL at about 89 kHz until the short is removed.

@deadprogram

Copy link
Copy Markdown
Member

@davecheney your testing tools are quite impressive!

@davecheney

Copy link
Copy Markdown
Contributor Author

@davecheney your testing tools are quite impressive!

They’re shop made

@deadprogram deadprogram left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @davecheney looking good! Now merging.

@deadprogram
deadprogram merged commit 2997a4e into tinygo-org:dev Oct 24, 2026
31 checks passed
@davecheney
davecheney deleted the esp32-i2c-nack-busy branch October 24, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants