machine/esp32xx: fix I2C NACK handling and controller hang after an address NACK - #5774
Conversation
READ commands had ack_check_en set. With it, a successful read raises NACK_INT together with TRANS_COMPLETE. transmit() ignored NACK in reads (`&& !readLast`), and with that also a real address NACK: a read from an absent device returned nil (tinygo-org#5767). ESP-IDF does not set ack_check_en on reads. Do the same and report every NACK. Fixes tinygo-org#5767 Signed-off-by: 0hJonny
When the address and the data were one WRITE command and the device did not ACK the address, the controller hung. Every following transaction ran only START and failed until Configure was called again. Send the address in its own WRITE command followed by END, then the rest of the transaction. This is how ESP-IDF runs a transaction. After an address NACK the next transaction now works without a reset. Signed-off-by: 0hJonny
resetMaster() gave 9 SCL clocks (SCL_RST_SLV) on every Configure. After them a GT911 did not ACK the next transaction. A PCF8563 on the same bus was not affected. Without the clocks the first transaction works. ESP-IDF does not clear the bus in i2c_param_config either. It clears the bus only in i2c_hw_fsm_reset, after a timeout, a busy bus or 10 NACKs. Signed-off-by: 0hJonny
A read ended with one segment of READ n-1, READLAST and STOP. With a GT911, reads of 64 and 96 bytes timed out in that segment every time and left the bus hung. Reads of 4, 32, 33, 63, 65, 128 and 184 bytes worked. Why only these lengths fail is not known. Read the bytes before the last one in their own segment followed by END, then the last byte. This is how ESP-IDF reads. All these lengths now read the same data as the ESP-IDF driver. Signed-off-by: 0hJonny
|
Thanks for the careful work and the detailed test notes @0hJonny. The following is edited from an automated review:
What do you think? |
|
Thank you for testing on the C3 and C6, that covers what I couldn't. Both points make sense. I'll add a FIFO reset at the start of transmit() and test it, including a NACK on a data byte in a longer write followed by a read from another device. I'll also test your STOP idea on the GT911: the 9 SCL clocks followed by a STOP, then the first transaction after Configure. I'll post the results here. |
transmit() returns on NACK or timeout, and the FIFOs were only reset in startMaster(). Bytes left in the TX FIFO went out in the following transactions. After a NACK on a data byte (made on purpose with ack_exp), every following PCF8563 read failed with NACK and TXFIFO_CNT kept growing. Reset both FIFOs at the start of transmit(), as ESP-IDF does. After such a NACK the reads work again from the second one on. The first read right after the NACK still failed. A Go port of the ESP-IDF v4.4.8 driver gave the same result there. Signed-off-by: 0hJonny
|
Thanks for the review, @deadprogram. Here are the results for both points, plus one more finding. FIFO reset. You were right. With a NACK on a data byte (a PCF8563 WRITE with ack_exp = 1), every following PCF8563 read failed and TXFIFO_CNT kept growing. With both FIFOs reset at the start of transmit(), the reads work again. Added as commit 7ce6172. The first read right after such a NACK still fails; a Go port of the ESP-IDF v4.4.8 driver does the same. Bus clear. You were right about the STOP. I added the old 9 clocks after Configure on the PR code (3 runs after power off, 12 tries per variant):
I would keep this PR without the clocks and add a bus clear with a GPIO STOP in a follow-up PR with its own test. Does that work for you? 100 kHz. A read started right after an address NACK timed out 19/20 (BUS_BUSY still set); with a 20 us pause, 0/20. On dev the same read returns wrong data without an error at both speeds. ESP-IDF resets the controller when BUS_BUSY is set, so I would handle that in the follow-up too. |
|
Thanks for testing all of those variants @0hJonny, that makes the bus clear question much clearer. The following is edited from an automated review:
|
|
Thanks for merging, @deadprogram! On point 1, I checked it (3 runs after power off, 30 cases per variant). At 400 kHz the next read fails also when BUS_BUSY is 0 before it: with a 20 us or 1 ms pause it still fails with a NACK (60/60), and the read after it works. The ESP-IDF port fails that first read too, without a reset. At 100 kHz the PR code fails both reads after the NACK, while the ESP-IDF port passes the first read in 27/30, each time after its reset on BUS_BUSY. So the BUS_BUSY reset looks right for 100 kHz, but not for this 400 kHz case. One caveat: the NACK is made with ack_exp = 1, so the PCF8563 actually ACKs the byte. A device that really NACKs could behave differently. I'll look into it in the follow-up. |
|
Apparently I should have run another hardware test before merge @0hJonny Please see this failure on our hardware in the loop server: https://github.com/tinygo-org/tinygo/runs/109145110032 This is the test code itself: https://github.com/tinygo-org/tinyhci/blob/main/xiao-esp32c3/main.go So looks like something needs to be done about this case sooner rather than later. |
|
@deadprogram Sorry about that, I saw it too. I don't know yet which of the 5 commits causes it, and I don't have an MPU6050 here to reproduce it. Would it help if I open a draft PR that reverts the commits one at a time, so tinyhci shows which one breaks it? Or would you prefer to revert the whole PR first, and I'll send it again once it passes on tinyhci? |
Let me bisect it real quick here... stand by... |
Thank you! Let me know which one it is, and I'll look into the fix. |
|
@0hJonny I bisected it on a XIAO ESP32-C3 with the Seeed expansion board and an MPU-6050, running the
Perhaps the bus clear with the GPIO STOP from your follow-up could go in now, since that made the GT911 happy too? Or we could put the 9 clocks back for now and do the full fix in the follow-up. What do you think? |
|
@deadprogram Thank you for bisecting it! Since dev with ea7f352 reverted already passes, I think putting the 9 clocks back now is the safer quick fix. It only brings back the old GT911 behaviour (a NACK on the first transaction after Configure), and a driver can retry that. Then I'll do the bus clear with the GPIO STOP in the follow-up, test it with the GT911 and the PCF8563 here, and wait for tinyhci to pass before merging. Should I open the revert PR, or would you prefer to do it? |
|
I will open the revert PR so that it can be run by the HCI. |
|
Thanks! I'll start on the bus clear with the GPIO STOP for the follow-up. |
|
actually I am making a run at the GPIO STOP myself right now, since I have the hardware setup. more shortly... |
|
Great, thank you! I can test it on the GT911 on my ESP32-S3 board once you have a branch. |
|
Please see #5783 and thanks! |
Fixes #5767.
This is the esp32xx part of #5585. Its direct copy was reverted in #5602. Tested on ESP32-S3 (LILYGO T5-4.7-S3) with a GT911 at 0x5D and a PCF8563 at 0x51 on I2C0 at 400 kHz.
Changes
No ack_check_en on READ commands. With it, a successful read raised NACK_INT together with TRANS_COMPLETE (seen with the same commands written directly to the registers: all commands done, correct data, INT_RAW 0x8692).
&& !readLastignored NACK in reads, so a read from an absent device returned nil. ESP-IDF does not set ack_check_en on reads (i2c_master_read_staticindriver/i2c.c).The address byte in its own WRITE command, followed by END. When the address and the data were one WRITE command and the address got a NACK, every following transaction ran only START and failed until the next Configure. With the address in its own WRITE plus END, the next transaction works without a reset. ESP-IDF runs transactions this way (
i2c_master_cmd_begin_static).No 9 SCL clocks (SCL_RST_SLV) in resetMaster(). After them the GT911 did not ACK the first transaction after Configure. The PCF8563 was not affected. ESP-IDF does not clear the bus in
i2c_param_config.The last byte of a read in its own segment. Reads of 64 and 96 bytes timed out every time in the last segment (READ n-1, READLAST, STOP) and left the bus hung. 4, 32, 33, 63, 65, 128 and 184 bytes worked. Why only these lengths fail is not known. ESP-IDF reads the last byte separately (
i2c_master_read). With that, all these lengths work.Both FIFOs reset at the start of transmit(). Bytes left in the TX FIFO after a NACK went out in the following transactions. After a NACK on a data byte (made on purpose with ack_exp = 1), every following PCF8563 read failed and TXFIFO_CNT kept growing. With the reset, as ESP-IDF does (
i2c_master_cmd_begin), the reads work again. The first read right after such a NACK still fails. A Go port of the ESP-IDF v4.4.8 driver gives the same result there.Each change is a separate commit, so they can be reviewed one by one.
Test
One program using only
machine.I2C0, run after full power cycles. The program enables the internal pull-ups after Configure. With this PR:"911".expected ACK not NACK. With 0.42.0 the read returnednil.machine.I2C0: touches before sleep, NACKs while asleep, touches after wake-up with no errors. With only change 1 applied there were no touches after wake-up.Changes 2 and 3 were found by switching single parts of that ESP-IDF port to the TinyGo behaviour. Writing the whole transaction at once made transactions after an address NACK fail: all of them when the address and the data were one WRITE, one of them when the address was its own WRITE without END. The 9 SCL clocks made the GT911 NACK the first transaction. Timing values, drive strength, FSM_RST, the reset pulse on enable and SLAVE_SCL_STRETCH_EN made no difference in that test.
Tested only on ESP32-S3. The file is shared with ESP32-C3 and ESP32-C6. It builds for
esp32c3-genericandxiao-esp32c6, but I have no hardware to test them.make testwas not run.