I've been attempting to figure out what the timings will be for processing data writes (i.e. EEPROM address) and responding to reads, to see how much time the microcontroller will have.
From what I can see (from aforementioned 24C32 datasheet), there are three situations that need to be handled for EEPROM reads:
- Immediate read: slave responds with a single byte of data from wherever the internal address pointer is.
- Selective read: master writes two bytes giving a 16-bit address, then slave responds with a single byte of data from that address.
- Sequential read: slave responds with a single byte of data from wherever the internal address pointer is. If the master ACKs, respond with the next byte. This can continue indefinitely until the master NACKs indicating it doesn't want any more data.
To me, an immediate read and sequential read seem to be essentially the same logic - an immediate read can be considered just a single-byte sequential read. So basically I'm just dealing with an addressed read of one data byte or non-addressed read of one or more data bytes. And even an addressed read isn't really a separate case either, as there's a repeated start after the address, so the latter part of that is essentially another non-addressed read. If that makes sense!
As far as timings are concerned, I think if I am to avoid clock-stretching, it basically boils down to how long the ACK period is between receiving either the slave address or a data byte. That is just one SCL clock cycle. If the Raspberry Pi is using 100kHz, then that'll be 10us. Assuming I'm running the ATtiny at the default 20MHz, I'm getting 20 CPU cycles per microsecond, so I'll have 200 cycles total to do my processing. That
seems like it might be enough. I suppose a lot will depend on the interrupt latency of the ATtiny 1-series - I don't know how many cycles it is. Any ideas?
However what I did was push the received data buffer to some statemachine that processes and executes the command.
Yes, I think a simple state machine will indeed be the best way to handle things. I'm thinking I'll need to handle the following states within the I2C interrupt handler each time it's called.
Start:If APIF flag of SSTATUS register set, matching slave address received; automatically ACK-ed by hardware. Check DIR flag in SSTATUS register to see if it's a write. If so, set state to 'Address MSB'.
Address MSB:If DIF flag of SSTATUS register set, read SDATA register to get address MSB and store. With ATtiny's 'Smart Mode', ACK is automatic upon reading SDATA. Set state to 'Address LSB'.
Address LSB:Same as above, but for LSB of address. Set state to 'Begin Data'.
Begin Data:If APIF flag of SSTATUS register set, matching slave address received; automatically ACK-ed by hardware. Check DIR flag to see if it's a read. If so, set state to 'Transmit Data'.
Transmit Data:If DIF flag of SSTATUS register set, read a data byte from EEPROM contents in flash at previously stored address, increment the address pointer, and transmit data by writing byte to SDATA register. Set state to 'Transmit More Data'.
Transmit More Data:If DIF flag of SSTATUS register set, interrupt was fired on completion of previous transmission, check RXACK flag in SSTATUS register - if ACK, set state to 'Transmit More Data'; if NACK, we're done, set state to 'Start'.
Am I missing anything here?
Hopefully the code to handle each state will be able to be executed in the amount of time required before the next SCL cycle, so clock stretching won't happen.
While the command is still being processed, the slave device will NAK any new incoming I2c start+address headers. This is to prevent corruption of the received command buffer, but also as backpressure to the host to indicate the device is still busy. The host could poll the slave device till it's finished or some timeout value.
Hmm, I have no idea whether the Raspberry Pi would handle NACK-ing in response - that is, whether it would try again. So I don't know if this would be a feasible strategy.