From 181dd97794088285ba032308da44eeb41d658de9 Mon Sep 17 00:00:00 2001 From: 0hJonny Date: Mon, 28 Sep 2026 08:43:09 +0000 Subject: [PATCH 1/5] machine/esp32xx: do not check ACK on I2C read commands 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 (#5767). ESP-IDF does not set ack_check_en on reads. Do the same and report every NACK. Fixes #5767 Signed-off-by: 0hJonny --- src/machine/machine_esp32xx_i2c.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/machine/machine_esp32xx_i2c.go b/src/machine/machine_esp32xx_i2c.go index ed0e686d69..2361420b94 100644 --- a/src/machine/machine_esp32xx_i2c.go +++ b/src/machine/machine_esp32xx_i2c.go @@ -154,11 +154,13 @@ func (i2c *I2C) resetMaster() { type i2cCommandType = uint32 type i2cAck = uint32 +// READ commands have no ack_check_en, with it a successful read raises NACK_INT. +// Same as ESP-IDF https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/i2c.c#L1227 const ( i2cCMD_RSTART i2cCommandType = 6 << 11 - i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en - i2cCMD_READ i2cCommandType = 3<<11 | 1<<8 // READ + ack_check_en - i2cCMD_READLAST i2cCommandType = 3<<11 | 5<<8 // READ + ack_check_en + NACK + i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en + i2cCMD_READ i2cCommandType = 3 << 11 // READ + i2cCMD_READLAST i2cCommandType = 3<<11 | 1<<10 // READ + NACK i2cCMD_STOP i2cCommandType = 2 << 11 i2cCMD_END i2cCommandType = 4 << 11 ) @@ -186,7 +188,6 @@ func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { timeoutNS := int64(timeoutMS) * 1000000 needAddress := true needRestart := false - readLast := false var readTo []byte for cmdIdx, reg := 0, &i2c.Bus.COMD0; cmdIdx < len(cmd); { c := &cmd[cmdIdx] @@ -254,7 +255,6 @@ func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { reg = nextAddress(reg) } if split { - readLast = true reg.Set(i2cCMD_READLAST | 1) reg = nextAddress(reg) readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte @@ -285,7 +285,7 @@ func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { } } switch { - case mask&esp.I2C_INT_STATUS_NACK_INT_ST_Msk != 0 && !readLast: + case mask&esp.I2C_INT_STATUS_NACK_INT_ST_Msk != 0: return errI2CAckExpected case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: if readTo != nil { From c27fb300ce0c18ad64e584d31a84327a98c0c1ae Mon Sep 17 00:00:00 2001 From: 0hJonny Date: Mon, 28 Sep 2026 08:43:09 +0000 Subject: [PATCH 2/5] machine/esp32xx: send the I2C address in its own WRITE command 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 --- src/machine/machine_esp32xx_i2c.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/machine/machine_esp32xx_i2c.go b/src/machine/machine_esp32xx_i2c.go index 2361420b94..1378e80cb6 100644 --- a/src/machine/machine_esp32xx_i2c.go +++ b/src/machine/machine_esp32xx_i2c.go @@ -201,11 +201,16 @@ func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { case i2cCMD_WRITE: count := 32 if needAddress { + // Own WRITE plus END for the address, else an address NACK hangs the controller. + // Same as ESP-IDF https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/i2c.c#L1296 needAddress = false i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr) & 0x7f) << 1) - count-- i2c.Bus.SLAVE_ADDR.Set(uint32(addr)) - i2c.Bus.SetCTR_CONF_UPGATE(1) + reg.Set(i2cCMD_WRITE | 1) + reg = nextAddress(reg) + reg.Set(i2cCMD_END) + reg = nil + break } for ; count > 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { i2c.Bus.SetDATA_FIFO_RDATA(uint32(c.data[c.head])) @@ -223,11 +228,15 @@ func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { case i2cCMD_READ: if needAddress { + // Own WRITE plus END for the address, see i2cCMD_WRITE above. needAddress = false i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) i2c.Bus.SLAVE_ADDR.Set(uint32(addr)) reg.Set(i2cCMD_WRITE | 1) reg = nextAddress(reg) + reg.Set(i2cCMD_END) + reg = nil + break } if needRestart { // We need to send RESTART again after i2cCMD_WRITE. From 4f11705d1ab184772b7ec2793585624c92300830 Mon Sep 17 00:00:00 2001 From: 0hJonny Date: Mon, 28 Sep 2026 08:43:09 +0000 Subject: [PATCH 3/5] machine/esp32xx: do not clock SCL when configuring I2C 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 --- src/machine/machine_esp32xx_i2c.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/machine/machine_esp32xx_i2c.go b/src/machine/machine_esp32xx_i2c.go index 1378e80cb6..46759f3a87 100644 --- a/src/machine/machine_esp32xx_i2c.go +++ b/src/machine/machine_esp32xx_i2c.go @@ -139,16 +139,11 @@ func (i2c *I2C) startMaster() { func (i2c *I2C) resetMaster() { // reset FSM i2c.Bus.SetCTR_FSM_RST(1) - // clear the bus - i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_NUM(9) - i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_EN(1) + // No bus clear (SCL_RST_SLV), a GT911 did not ACK the next transaction after it. + // ESP-IDF does not clear the bus on init either https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/i2c.c#L673 i2c.Bus.SetSCL_STRETCH_CONF_SLAVE_SCL_STRETCH_EN(1) i2c.Bus.SetCTR_CONF_UPGATE(1) i2c.Bus.FILTER_CFG.Set(0x377) - // wait for SCL_RST_SLV_EN - for i2c.Bus.GetSCL_SP_CONF_SCL_RST_SLV_EN() != 0 { - } - i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_NUM(0) } type i2cCommandType = uint32 From 588bb6bebbb100dc083a937e6f3b1cd21097b63a Mon Sep 17 00:00:00 2001 From: 0hJonny Date: Mon, 28 Sep 2026 08:43:09 +0000 Subject: [PATCH 4/5] machine/esp32xx: read the last I2C byte in its own segment 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 --- src/machine/machine_esp32xx_i2c.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/machine/machine_esp32xx_i2c.go b/src/machine/machine_esp32xx_i2c.go index 46759f3a87..e6d7002f4a 100644 --- a/src/machine/machine_esp32xx_i2c.go +++ b/src/machine/machine_esp32xx_i2c.go @@ -254,6 +254,16 @@ func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { if bytes > 32 { bytes = 32 } + if split && bytes > 0 { + // Last byte in its own segment, else 64 and 96 byte reads time out. + // Same as ESP-IDF https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/i2c.c#L1259 + reg.Set(i2cCMD_READ | uint32(bytes)) + reg = nextAddress(reg) + reg.Set(i2cCMD_END) + readTo = c.data[c.head : c.head+bytes] + reg = nil + break + } if bytes > 0 { reg.Set(i2cCMD_READ | uint32(bytes)) reg = nextAddress(reg) From 7ce61721ad42aa8e4067b6b277a00708772de24e Mon Sep 17 00:00:00 2001 From: 0hJonny Date: Mon, 28 Sep 2026 15:38:40 +0000 Subject: [PATCH 5/5] machine/esp32xx: reset the I2C FIFOs at the start of each transaction 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 --- src/machine/machine_esp32xx_i2c.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/machine/machine_esp32xx_i2c.go b/src/machine/machine_esp32xx_i2c.go index e6d7002f4a..ab62cd063b 100644 --- a/src/machine/machine_esp32xx_i2c.go +++ b/src/machine/machine_esp32xx_i2c.go @@ -171,6 +171,12 @@ func nanotime() int64 func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { const intMask = esp.I2C_INT_STATUS_END_DETECT_INT_ST_Msk | esp.I2C_INT_STATUS_TRANS_COMPLETE_INT_ST_Msk | esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk | esp.I2C_INT_STATUS_NACK_INT_ST_Msk + // Reset the FIFOs, bytes left after a NACK would go out in the next transaction. + // Same as ESP-IDF https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/i2c.c#L1456 + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(0) + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(0) i2c.Bus.INT_CLR.SetBits(intMask) i2c.Bus.INT_ENA.SetBits(intMask) i2c.Bus.SetCTR_CONF_UPGATE(1)