On ESP32, machine.I2C0.Tx(addr, nil, buf) returns nil for every address, even when nothing is on the I2C bus. The hardware detects the address NACK, but the && !readLast check at
|
case mask&esp.I2C_INT_STATUS_ACK_ERR_INT_ST_Msk != 0 && !readLast: |
discards it.
I traced the issue, and this is the root cause:
-
The address byte of a read is sent by an i2cCMD_WRITE command, emitted at
|
case i2cCMD_READ: |
|
if needAddress { |
|
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) |
|
} |
-
i2cCMD_WRITE is the only constant that sets ack_check_en
|
const ( |
|
i2cCMD_RSTART i2cCommandType = 0 << 11 |
|
i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en |
|
i2cCMD_READ i2cCommandType = 2 << 11 |
|
i2cCMD_READLAST i2cCommandType = 2<<11 | 1<<10 // READ + NACK |
|
i2cCMD_STOP i2cCommandType = 3 << 11 |
|
i2cCMD_END i2cCommandType = 4 << 11 |
|
) |
so it is the only command that can set ACK_ERR.
-
i2cCMD_READLAST sets ack_value, which instead sends a NACK. ACK_ERR is in the other direction. In TRM section 21.3.7, it says:
I2C_ACK_ERR_INT: Triggered when the Master receives an ACK that is not as expected, or when the Slave receives an ACK whose value is 1.
-
The readLast flag is set when the command list is built
|
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 |
|
cmdIdx++ |
|
} else { |
and not when the error is checked. A read of 32 bytes or fewer fits in the controller's 32-byte RAM, and the whole transaction is run in one go. When the status register is checked, readLast is already true, and the address error is dropped.
Reads of 33 bytes or more won't have this issue because they are split with i2cCMD_END and the part carrying the address would be checked before the readLast flag is set.
Steps to Reproduce
Wire any I2C device to an ESP32 and probe every available address with a read:
for addr := uint16(0x08); addr <= 0x77; addr++ {
buf := make([]byte, 1)
if err := machine.I2C0.Tx(addr, nil, buf); err != nil {
continue
}
println("found", addr)
}
The expectation is that only the addresses that are on the bus would be "found." But actually, all 112 are reported as present.
If you probe with a write instead, Tx(addr, []byte{0x00}, nil), it correctly reports the devices that are present on the bus. A write does not emit a READLAST, so the readLast flag stays false.
Environment
- TinyGo 0.41.1 and dev at c421943
- ESP32-D0WD-V3
On ESP32,
machine.I2C0.Tx(addr, nil, buf)returnsnilfor every address, even when nothing is on the I2C bus. The hardware detects the address NACK, but the&& !readLastcheck attinygo/src/machine/machine_esp32_i2c.go
Line 345 in c421943
I traced the issue, and this is the root cause:
The address byte of a read is sent by an
i2cCMD_WRITEcommand, emitted attinygo/src/machine/machine_esp32_i2c.go
Lines 278 to 285 in c421943
i2cCMD_WRITEis the only constant that setsack_check_entinygo/src/machine/machine_esp32_i2c.go
Lines 210 to 217 in c421943
ACK_ERR.i2cCMD_READLASTsetsack_value, which instead sends a NACK.ACK_ERRis in the other direction. In TRM section 21.3.7, it says:The
readLastflag is set when the command list is builttinygo/src/machine/machine_esp32_i2c.go
Lines 312 to 318 in c421943
readLastis already true, and the address error is dropped.Reads of 33 bytes or more won't have this issue because they are split with
i2cCMD_ENDand the part carrying the address would be checked before thereadLastflag is set.Steps to Reproduce
Wire any I2C device to an ESP32 and probe every available address with a read:
The expectation is that only the addresses that are on the bus would be "found." But actually, all 112 are reported as present.
If you probe with a write instead,
Tx(addr, []byte{0x00}, nil), it correctly reports the devices that are present on the bus. A write does not emit aREADLAST, so thereadLastflag stays false.Environment