SpenceKonde / ATTinyCore

Arduino core for ATtiny 1634, 828, x313, x4, x41, x5, x61, x7 and x8
Other
1.53k stars 302 forks source link

ATtiny85 I2C slave mode multi-byte reads don't work #776

Closed dragoncoder047 closed 1 year ago

dragoncoder047 commented 1 year ago

I have an ATtiny85, and it's set up as an I2C slave on pins PB0 and PB2 using the built-in USI-based I2C implementation.

The I2C based code implements a typical register-based interface:

The onReceive() code works fine and I am able to write one register and write multiple registers successfully.

However, when I try to read more than one byte at once (i.e. Wire.requestFrom(XXX, 4) on the master side) only the first byte sends properly and the rest come back as just a bunch of useless 255's.

In case it's relevant, there are no other interrupts on the ATtiny85 that could be interfering the USI interrupt.

The other problem I see might be that the onRequest() handler is only called once per transaction, not once per byte as I assumed it is, and so it only ever gets 1 byte in the send buffer. If that is the case, how do I know how many bytes the master wants in advance and/or flush the buffer at the end of the transaction?

I am also using an ESP32 as the I2C master, so if you know of any bugs in the ESP32's I2C implementation (particularly w.r.t. clock stretching) please let me know.

SpenceKonde commented 1 year ago
  1. Are you using the 2.0.0-dev version?
  2. In I2C, the way a read works is that the master addresses the slave with the read bit set and that triggers the callback, during the callback you write all the data that the master might want (you have no idea how much it will read, it doesn't tell you) within the onRequest() handler on the slave, and the slave puts those into a buffer, stretching the clock while the ISR runs, then acking. Then the master sends 8 clock pulses. as the slave uses that to send one byte of data. An interrupt fires, but it doesn't trigger a callback, just something that puts a byte from the buffer into the wire register and increments the pointer offset. The master then either acks if it is going to read more, or nacks if it's not going to read more, This continues until the master nacks, The standard API does not provide any facility to know how many bytes the master read and the protocol doesn't tell the slave ahead of time. You cannot make the standard "register model" interface with the tools the Arduino API gives you. That is fixed in megaTinyCore and DxCore, but those changes are not being backported - they were a lot of work over there, and there there was only one library implementation to modify for both cores. This single core requires three separate and completely independent sets of changes to be made (and they're non-trivial) - because there re three completely different I2C slave methods - there's the USI slave, master/slave TWI slave (which is the standard library), and slave only TWI interfaces found on the 828 and 841.

Does this help to shed any light on the issue? it sounded like you ha misconceptions about how I2C and the wire API works and what the different parts know and tell eachother at various points in the process - if your onRequest only writes one byte, you'd get 1 byte of data and 3 255's

dragoncoder047 commented 1 year ago

In I2C, the way a read works is that the master addresses the slave with the read bit set and that triggers the callback, during the callback you write all the data that the master might want (you have no idea how much it will read, it doesn't tell you) within the onRequest() handler on the slave

So my suspicion was correct. I fixed it to send all of the registers until the register pointer ran off the end, and it works now.

It's a shame that I can't have the register pointer automatically wrap at the end, but then again, I don't really need it to wrap.