rust-embedded / embedded-hal

A Hardware Abstraction Layer (HAL) for embedded systems
Apache License 2.0
1.95k stars 197 forks source link

Zero-length I2c transfers #570

Open G33KatWork opened 8 months ago

G33KatWork commented 8 months ago

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 commented 8 months ago

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?

Dirbaio commented 8 months ago

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

Yes! Can you send a PR?

sgoll commented 6 months ago

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.