0

The question is about how to have information/control of a variable length read for an Arduino acting as a slave device.

The concrete context is that I want to emulate an EEPROM IC. A byte write to the EEPROM's address sets the internal EEPROM address to start reading from. The subsequent read from the EEPROM's address, will start returning bytes from that internal address which is incremented for each byte read.

"Wire.onRequest(requestEvent);" allows us to set up a handler, but it seems to be called only once on the start of the read.

In the handler it is possible to provide a reply of multiple bytes. However I did not find the documentation allowing to know how many bytes were effectively sent to the master.

I got the initial code from the arduino forum . I've adapted it to this:

#include <Wire.h>
//#define DEBUG
byte XEEPROM[256] = {
  0x5D, 0x26, 0x31, 0x91, 0x3e, 0x12, 0x8e, 0x70, 0xa5, 0x57, 0x3d, 0xed, 0x91, 0x99, 0xb4, 0x11,
  0x38, 0xde, 0x1b, 0xd6, 0x4c, 0xd9, 0xf5, 0x13, 0x23, 0x94, 0xb8, 0x3d, 0x9c, 0xb0, 0xc4, 0x54,
  0xf0, 0x69, 0xca, 0xb3, 0x28, 0x84, 0xa5, 0xc1, 0x80, 0x53, 0x69, 0x0c, 0x38, 0x4f, 0x0c, 0x74,
  0xa1, 0x5b, 0x8c, 0x71, 0x34, 0x30, 0x38, 0x30, 0x30, 0x38, 0x31, 0x35, 0x33, 0x31, 0x30, 0x35,
  0x00, 0x12, 0x5a, 0x1e, 0xbd, 0x9e, 0x00, 0x00, 0x02, 0x82, 0x30, 0xaa, 0x10, 0xb7, 0x7f, 0x8d,
  0x0a, 0xcb, 0x2e, 0xc1, 0xeb, 0x52, 0x20, 0xdc, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x97, 0x55, 0x57, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x47, 0x4d, 0x54, 0x00, 0x42, 0x53, 0x54, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x05, 0x00, 0x02, 0x03, 0x05, 0x00, 0x01,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0xff, 0xff, 0xff,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
  0x68, 0xaa, 0xdd, 0x02, 0x46, 0x45, 0x8f, 0x8f, 0x78, 0x56, 0xbb, 0x69, 0x01, 0x04, 0x46, 0x45,
  0x8f, 0x8f, 0x78, 0x56, 0xbb, 0x69, 0x01, 0x04, 0x46, 0x45, 0x8f, 0x8f, 0x78, 0x56, 0xbb, 0x69,
  0x12, 0x03, 0x46, 0x45, 0x8f, 0x8f, 0x78, 0x56, 0xbb, 0x69, 0x02, 0x02, 0x46, 0x45, 0x8f, 0x8f,
  0x78, 0x56, 0xbb, 0x69, 0x12, 0x02, 0x84, 0x79, 0x80, 0x00, 0x00, 0x00, 0x16, 0x01, 0x00, 0x00
};
static volatile byte rQ;

void setup()
{
 Wire.begin(0x50);
 Wire.onReceive(receiveEvent);
 Wire.onRequest(requestEvent);
 Serial.begin(115200);
 Serial.println("I2C EEPROM EMULATION\n");
}

void loop()
{
}

void requestEvent(){ 
  //Wire.write(XEEPROM[rQ]); // Just 1
  Wire.write(XEEPROM+rQ,6); /// More than 1
  #ifdef DEBUG
  Serial.print("ADR:");
  Serial.print(rQ,HEX);
  Serial.print("R:");
  Serial.print(XEEPROM[rQ],HEX);  // print the character
  Serial.println();
  #endif
  rQ++; // Increment should depend on number of reads - not sure how to detect that.
}

void receiveEvent(int iData){
    rQ = Wire.read();
    Serial.print("ADR:");
    Serial.print(rQ,HEX);
    bool dwHeader=false;
    // loop through all but the last
    while(1 < Wire.available()) 
    {
      if(!dwHeader) {
          dwHeader=true;
          Serial.print("DATAW:");
      }
      XEEPROM[rQ]=Wire.read();;
      Serial.print(XEEPROM[rQ],HEX);  // print the character
    }
    Serial.println(); // new line
}

The question is about requestEvent and specifically:

void requestEvent(){ 
  //Wire.write(XEEPROM[rQ]); // Just 1
  Wire.write(XEEPROM+rQ,6); /// More than 1
  rQ++; // Increment should depend on number of reads - not sure how to 
}

I would like to send one at a time, or at least be able to increment rQ according to the number of bytes effectively sent.

If you want to look at a datasheet, you can look at the AT24C02 datasheet. With regards to the incrementing internal EEPROM address, the EEPROM specification says:

CURRENT ADDRESS READ: The internal data word address counter maintains the last address accessed during the last read or write operation, incremented by one. This address stays valid between operations as long as the chip power is maintained. The address “roll over” during read is from the last byte of the last memory page to the first byte of the first page. The address “roll over” during write is from the last byte of the current page to the first byte of the same page.

This is why the Arduino code emulating the EEPROM should know exactly how many bytes were read.

Thanks.

4
  • Please add more information to your question. Is the master device also an arduino board? If not, you are in trouble because the arduino as a slave is not the same as a eeprom. Which arduino board do you use for the slave and the master? The "loop through all but the last" is not normal, it is from an example but that is a bad example. If you use arduino unos, then the i2c buffer size is 32, and you may do a Wire.write with 32 bytes. When using the arduino wire libray, the slave does not know how many bytes will be read or are read by the master device.
    – Jot
    Nov 30, 2018 at 13:25
  • The master is also an Arduino for the test, but normally it isn't and that does not matter. * I agree that there are some other bugs, I just wanted a quick start * I am using Arduino Nanos * The Arduino should act as an EEPROM - this avoids getting one for the test, but I am surprised that there is no way to know how many bytes are sent at the end of the I2C Transaction * I looked at Wire.cpp and twi.c just now * The only way to know how many bytes are sent would be to fill the buffer completely, and then fill it constantly (in the loop) using twi_transmit() and count one when it returns 0.
    – le_top
    Nov 30, 2018 at 15:57
  • It matters a lot if the master is a arduino or not. The arduino as a slave uses clock pulse stretching and an eeprom does not. It is therefor impossible to act in the same way as an eeprom. Can you find an example of the arduino that simulates an eeprom in exactly the same way? That is not possible. For the software it is possible to change the library or use a library that generates interrupts for every single byte (I forgot the name of that library). The master often writes the register address before reading the data, then it is no problem to fill up the buffer with 32 bytes.
    – Jot
    Nov 30, 2018 at 17:09
  • Ok, I understand that it can not be perfect. An EEPROM does not do clock stretching because it is fast enough at the maximum I2C frequency. The Arduino emulating the EEPROM could still be functionnaly equivalent which is enough.
    – le_top
    Dec 3, 2018 at 15:03

2 Answers 2

1

Note, that I haven't tested the code below, since I currently don't have enough time for it. But it may help you to get, what you want to do.

The way the I2C interface works is, that after each byte read from the slave, the master will answer with ACK or NACK, depending on if he wants to read more data or not. So the slave doesn't know beforehand, how many bytes the master wants to read.

But you can change the Wire library, to call the onRequest callback for every single byte, instead of every request. The important code lies in the path of your Arduino installation at hardware/arduino/avr/libraries/Wire/src/utility/twi.c at line 575 (I refer to my installation of version 1.8.5; might be slightly different for you):

case TW_ST_SLA_ACK:          // addressed, returned ack
case TW_ST_ARB_LOST_SLA_ACK: // arbitration lost, returned ack
      // enter slave transmitter mode
      twi_state = TWI_STX;
      // ready the tx buffer index for iteration
      twi_txBufferIndex = 0;
      // set tx buffer length to be zero, to verify if user changes it
      twi_txBufferLength = 0;
      // request for txBuffer to be filled and length to be set
      // note: user must call twi_transmit(bytes, length) to do this
      twi_onSlaveTransmit();
      // if they didn't change buffer & length, initialize it
      if(0 == twi_txBufferLength){
        twi_txBufferLength = 1;
        twi_txBuffer[0] = 0x00;
      }
      // transmit first byte from buffer, fall
case TW_ST_DATA_ACK: // byte sent, ack returned
      // copy data to output register
      TWDR = twi_txBuffer[twi_txBufferIndex++];
      // if there is more to send, ack, otherwise nack
      if(twi_txBufferIndex < twi_txBufferLength){
        twi_reply(1);
      }else{
        twi_reply(0);
      }
      break;

When receiving a request with correct address, the first case will be executed (of course after other stuff with receiving address and sending ACK). The twi_onSlaveTransmit() function will execute the onRequest callback. When that is done, the code falls directly into the next case statement, which sends the first byte. When the slave receives an ACK after that byte, it should come back to this case and send another byte.

We can now put the buffer filling code into the second case statement, so that the callback will be executed for every requested byte. In this case we also need to change the if statement after that, so that it only triggers and sends NACK, if the user didn't write a byte in the callback. The original library seems to write at minimum 1 byte (even it nothing is in the buffer; it is set to zero). So we will also do this by setting a flag in the first case statement. The code now looks like this:

case TW_ST_SLA_ACK:          // addressed, returned ack
case TW_ST_ARB_LOST_SLA_ACK: // arbitration lost, returned ack
      // enter slave transmitter mode
      twi_state = TWI_STX;
      twi_slave_transmit_first_byte = 1;
      // transmit first byte from buffer, fall
case TW_ST_DATA_ACK: // byte sent, ack returned
      // ready the tx buffer index for iteration
      twi_txBufferIndex = 0;
      // set tx buffer length to be zero, to verify if user changes it
      twi_txBufferLength = 0;
      // request for txBuffer to be filled and length to be set
      // note: user must call twi_transmit(bytes, length) to do this
      twi_onSlaveTransmit();
      if(twi_slave_transmit_first_byte == 1){
          twi_slave_transmit_first_byte = 0;
          // if they didn't change buffer & length, initialize it
          if(0 == twi_txBufferLength){
            twi_txBufferLength = 1;
            twi_txBuffer[0] = 0x00;
          }
      }
      // copy data to output register
      TWDR = twi_txBuffer[twi_txBufferIndex++];
      // if there is more to send, ack, otherwise nack
      if(twi_txBufferIndex < twi_txBufferLength){
        twi_reply(1);
      }else{
        twi_reply(0);
      }
      break;

And we have to add the flag declaration at the top of the file (where all the other static volatile variables are defined) as static volatile:

static volatile uint8_t twi_slave_transmit_first_byte = 0;

In the onRequest callback you can now write a single byte to the buffer and incrementing your counter.


I think, that should work. If someone knows better, please correct me.

1
  • 1
    Hi, changing the Wire library is one way to go about it - for portability this would have to be added as a new Wire library, possibly just extending the standard library. I'll try to check this out.
    – le_top
    Dec 2, 2018 at 14:49
0

This is exactly what I stumbled upon when trying to create a I2C slave with a memory-based interface.

You are right, the requestEvent callback is called only once. So you have to prepare it beforehand. What is positive, however, is that the write function will discard all the unused bytes.

So, what can you do? Well, first of all a sum up of the I2C "messages". You, as a master, can

  • Send a "write" command, with a payload
  • Send a "read" command, with no payload, and the slave will reply with bytes as long as you continue to send ACKs

In my mind there were two different approaches:

  • The "write" command should contain the initial register and the number of registers to read; this approach, however, complicates distinguishing between a "read from EEPROM" and "write to EEPROM" commands
  • The write command should send only the initial register for reading from EEPROM, and the initial register + content to be written for writing to EEPROM.

In my project I chose the second approach. For this reason, when the master writes something on the I2C bus, the first byte of the payload is the register number, and then if there is no other data the slave prepares for sending the contents of the register and the next ones, while if there is any data this is treated as a write command.

The only problem with this approach is that the slave has to prepare a buffer, and the buffer size limits the maximum number of registers returned. I set this limit to 8 in my code; if the master asks for more than only 0s are returned.

Here is the library I wrote for this project.

File I2CInterface.h:

#ifndef I2CINTERFACE_H
#define I2CINTERFACE_H

#include <stdint.h>

class I2CInterface
{
  public:
    static I2CInterface instance;

  public:
    void start();

  private:
    I2CInterface();

    static void requestEvent();
    static void receiveEvent(int howMany);
    void resetSendingBuffer();

  private:
    static const uint8_t Address = 0x46;

    static const uint8_t MaxBufferLength = 8; // (max) 8 byte
    uint8_t sendingBuffer[MaxBufferLength];
};

#endif // I2CINTERFACE_H

File I2CInterface.cpp

#include <Arduino.h>
#include <Wire.h>
#include "I2CInterface.h"

I2CInterface I2CInterface::instance;

I2CInterface::I2CInterface()
{

}

void I2CInterface::start()
{
  Wire.begin(Address);
  Wire.onRequest(requestEvent);
  Wire.onReceive(receiveEvent);
  resetSendingBuffer();
}

void I2CInterface::requestEvent()
{
  Wire.write(instance.sendingBuffer, MaxBufferLength);
  instance.resetSendingBuffer();
}

void I2CInterface::receiveEvent(int howMany)
{
  if (howMany <= 0)
    return;
  else if (howMany == 1)
  { // Reading request
    uint8_t regist = Wire.read();
    for (uint8_t i = 0; i < MaxBufferLength; i++)
      instance.sendingBuffer[i] = Configuration.getRegister(regist++);
  }
  else
  { // Writing request
    uint8_t regist = Wire.read();
    while (Wire.available())
    {
      Configuration.setRegister(regist++, Wire.read());
    }
    instance.resetSendingBuffer();
  }
}

void I2CInterface::resetSendingBuffer()
{
  for (uint8_t i = 0; i < MaxBufferLength; i++)
    sendingBuffer[i] = 0x00;
}

Please note that the Configuration.getRegister and Configuration.setRegister are the functions I used to get and set some registers; you can substitute them with a direct EEPROM access or your own functions.

You just need to add, in your main file,

#include "I2CInterface.h"
...

void setup()
{
  ...
  I2CInterface::instance.start();
  ...
}
5
  • Thank you for your reply. This is more or less what I do with "Wire.write(XEEPROM+rQ,6);", where 6 is currently increased to 16 which is all that I need in my setup. * However, the address counter does not get correctly updated on reads, so while 16 or more bytes may be prepared for sending to the master, I would have to know how many have been actually sent... Either during or after the transfer. Most importantly, you confirm that requestEvent is called only once for the read from the master.
    – le_top
    Nov 30, 2018 at 15:36
  • @le_top Well, in my case the new request (which is a write with one single byte) will have a new start address, so you should not increment rQ. The sequence to read, for instance, 20 bytes from byte 5 in your master should be: write address 5; read 6 bytes; write address 11; read 6 bytes; write address 17; read 4 bytes. It is the master who keeps track of the increment, not the slave. As for the only once, I detected it experimenting (I did not go deep in the Wire library to confirm this)
    – frarugi87
    Dec 3, 2018 at 11:17
  • By changing the protocol, the Slave will no longer behave as the EEPROM. I add an extract of the EEPROM specification to the question.
    – le_top
    Dec 3, 2018 at 15:05
  • @le_top sorry, I've always used the EEPROMs this way (by always asking for a particular address every read operation rather than relying on the internal counter), so I totally forgot the usage of that feature. If you want this feature, I think the library should be modified (I think like chrisl suggests), since the number of written bytes information is not available AFAIK
    – frarugi87
    Dec 4, 2018 at 12:56
  • I understand. For safety it is best to always send the address first. However, when emulating exactly the behavior, one woul hve to follow the specification. My main question is related to the right method to find how many bytes were replied or how to reply byte by byte - this is what basically triggered the question. The EEPROM example is the concrete application. As the core question seemed to be new, I decided to open this topic.
    – le_top
    Dec 5, 2018 at 15:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.