embedded-hal icon indicating copy to clipboard operation
embedded-hal copied to clipboard

Zero-length I2c transfers

Open G33KatWork opened this issue 1 year ago • 3 comments
trafficstars

Hi,

I am having a bit of a fight here with the I2c trait in embedded-hal 1.0.0.

Let's suppose I have an I2C EEPROM that I just sent some data for writing to. The EEPROM needs some time after the STOP condition on the I2C bus to actually write the data into the memory page. During that time, it stops ACKing its address on the I2C bus.

Now in order to poll when the chip is done writing, I thought I would be smart and perform a read transaction to its address with an empty buffer, just to see if it ACKs its address. I assumed that the code I wrote which implements the transaction trait method would put the address on the bus, wait for an ACK or NACK, either return a NACK-Error in case the chip is still busy or try to read 0 bytes followed by putting a STOP condition on the bus in case the chip is idle again.

Now comes the roadblock: I do this on an ATtiny817 and its I2C hardware state machine is relatively clever and it tries to perform as much bus stuff in hardware as it can. As it stands, I can not get it to NOT read at least one byte from the bus after a successful ACK and my code gets stuck with an occupied bus, because I never ACK or NACK that byte. I tried handling a special case when the passed slice in the Read operation has a length of 0 where I just read one byte, discard it, NACK the transfer and issue a STOP condition, but even that doesn't work for some weird reason.

Long story short, before I debug this further: Are zero-length reads or writes on an I2C bus even a thing? Does it even make sense to support this? Should we support something like this as I2c trait implementers or should we just error out early or even ignore such a transaction when we encounter it? This edge case isn't specified in the trait description so I thought I'd ask here.

Also, I think even the i2cdetect utility in i2c-utils performs actual read or write transfers of at least one byte to detect devices on an I2C bus.

G33KatWork avatar Jan 18 '24 08:01 G33KatWork

Okay, so after actually understanding how that peripheral works and how to trigger certain state changes in the state machine and what transitions happen automatically as a side-effect on a read or write of certain registers, like for example the I2C data shift register or address register, I made it work.

Turns out, yes, zero-length transactions are a thing and they can be used for exactly what I wanted them to be used for.

Should still be clarified in the docs that this should be supported?

G33KatWork avatar Jan 20 '24 05:01 G33KatWork

Should still be clarified in the docs that this should be supported?

Yes! Can you send a PR?

Dirbaio avatar Jan 22 '24 00:01 Dirbaio

Turns out, yes, zero-length transactions are a thing and they can be used for exactly what I wanted them to be used for.

Is this correct? I was under the impression that the I2C specification does not allow for zero-length read transfers (as in it is not possible to do that on the wire).

After the R/W bit is transmitted, the slave device becomes the transmitting device on the bus for 9 bits (i.e. ACK to indicate its presence, followed by one byte transfer), and only afterwards can the master transmit ACK/NACK to tell the slave device to continue or not continue. During the ongoing transfer of one byte, even the master is not able to send a stop condition.[^1]

[^1]: Technically, it could if the slave device were transmitting high at the exact bit that the master device wants to stop at. If the slave device were transmitting low, however, the master device would not be able to do the necessary transition on SDA for the stop condition.

On the other hand, zero-length write transfers can be done because the master device becomes the transmitting device again right after the slave device has ACK'ed its presence, and it can thus send a stop condition without a single byte transfer.

sgoll avatar Mar 21 '24 23:03 sgoll

I tried looking for some official sources on the point made by @sgoll due to this topic coming up in https://github.com/Rahix/avr-hal/issues/622. I couldn't find definitive answers but it looks very much like zero-sized reads are not possible by the I2C specifications. Here is my reasoning:

Looking at UM10204 from NXP (I2C-bus specification and user manual), it states:

Master reads slave immediately after first byte (see Figure 12). At the moment of the first acknowledge, the master-transmitter becomes a master-receiver and the slave-receiver becomes a slave-transmitter. This first acknowledge is still generated by the slave. The master generates subsequent acknowledges. The STOP condition is generated by the master, which sends a not-acknowledge (A) just before the STOP condition.

Image

Importantly, in Figure 12, we can see that the initial acknowledge and the first byte of data immediately following it are sent by the slave after initiating a read transmission. As @sgoll stated, it seems that there is no way for the master to prevent this first byte of data. All it can do is fully clocking it out and then sending a NACK afterwards.


I wonder what implication this has for e-h? Right now, as far as I can tell, the contract does not forbid read-transactions with zero-length. But I think this should either a) be handled by not starting any read transmission or b) disallowing zero-length read buffers.

Rahix avatar Sep 24 '25 18:09 Rahix