Wire as target, sequential read from controller?

Hi, I'm running into slightly unexpected behavior with Wire and an ATMEGA328. The atmega should act as an I2C target (slave).

The protocol of in-hardware external controller(s) is that it/they write an 8-bit base memory address to the target first, switch themselves to reading, and then clock in a varying number of bytes that the target should place on the bus. When the controller has received enough, it terminates the I2C transfer with No ACK + Stop. Essentially, a "sequential read" with implicit "auto increment" of the address.

The issue I run into is, the Wire onRequest() handler gets invoked only for the first byte of the sequential read. That is, controllers see one byte of valid data, and then blank.

How to get an onRequest() also for the subsequent bytes that the controller tries to clock in?

The code is:

void i2cReqHandler(void);
void i2cRecvHandler(int);

static volatile byte i2c_offset = 0;
static volatile byte shmem[256];

void setup()
{
  Wire.begin(0x50);
  Wire.setClock(30000); // 30 kHz
  Wire.onRequest(i2cReqHandler);
  Wire.onReceive(i2cRecvHandler);
}

void i2cReqHandler(void)
{
    Wire.write(shmem[i2c_offset]);
    i2c_offset++;
}

void i2cRecvHandler(int nbytes)
{
    int n = 1;
    i2c_offset = Wire.read();
    while (n < nbytes) {
        // I2C Controller "Write": [base address] [byte0] [byte1] [...]
        shmem[i2c_offset] = Wire.read();
        i2c_offset++;
        n++;
    } else {
        // I2C Controller "Read": i2cwrite:[base address] + onRequest:i2cread:[byte0] [byte1] [...]
    }
}

void loop()
{
}

That triggers the onRequest handler

But they don't send another request between bytes.

If you want to respond with multiple bytes, then write them all in your handler. Not one at a time.

The Wire library does not support the mode where an I2C Slave continues to send data bytes (typically from sequential memory / register locations) until the Master signals the end of the operation with STOP.

Ah. Thanks. Well that's a pity! Is there perhaps another Arduino I2C library that would support that? Worst case I'll have to clone Wire and modify it.

Write them all as Delta_G wrote.
For ATmega chips, the buffer size (inside the Wire library) is 32 byte. You can Wire.write() 32 bytes, starting from 'i2c_offset'.

The Controller (Master) can read 1 up to 32 bytes, and will stop the I2C session when it has enough.
After that, the Controller can not continue to read data in a next I2C session, but has to send the offset first.

Many have tried to "improve" the Wire library, and many have failed.

Do you have a Logic Analyzer ? Then you have seen the "clock pulse stretching". When the Controller want to read data, then the Target needs some time to run the onRequest handler. It creates that time by keeping SCL low.

Question: If the Controller reads (for example) 10 bytes, and the Target has only 9 bytes or has 11 bytes. How does the Controller know that during that I2C session ?
Answer: The Controller does not know that. That is part of how the I2C bus works. The Controller will always read 10 bytes, the Target can not influence that. If the Target has less bytes, then most likely the rest of the bytes are 0xFF.

Wire is built off the functions in twi.c / twi.h. If this feature is really important for you, I'd say make a one-off implementation starting with that code.

Thanks, wondered what Delta_G meant with "all" bytes. The number of bytes expected by the controller is variable, i.e., the ATmega cannot know it in advance. Makes sense now, though, "all" = "worst-case maximal length", and counting on the unused part of the tx buffer getting flushed.

Tried it and the approach works :slight_smile: The 32-byte limit is not so great, but so far the controller doesn't appear to have requested more, at least, no error conditions reported over a few minutes of run time.

For "continued" send, I've now additionally modified a copy of Wire.cpp/.h + twi.c/.h; twi.c at TW_ST_DATA_ACK extended with an optional user callback to replenish the tx buffer through a new onRequestMore() callback with associated onRequest()-like plumbing.

    case TW_ST_DATA_ACK: // byte sent, ack returned
      // copy data to output register
      TWDR = twi_txBuffer[twi_txBufferIndex++];
+     // if the buffer emptied, request it to be topped up
+     if (twi_txBufferIndex >= twi_txBufferLength) {
+       twi_onSlaveTransmitMore();
+     }
      // if there is more to send, ack, otherwise nack
      if(twi_txBufferIndex < twi_txBufferLength){
        twi_reply(1);
      }else{
        twi_reply(0);
      }
      break;

This "request more" approach worked fine, too. Did a 1-byte initial send (vs. upfront 32-byte send) in the onRequest() handler, then trickled in the additional single bytes via a new onRequestMore(). The code in both handlers must be extremely short, though.

Btw, alas, I do not have a scope nor logic analyzer. Would help to see how marginal the timings are...

These inexpensive logic analyzers work very well, and are perfect for I2C and other Arduino serial protocols.

Available through many on line outlets.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.