Sending 512bytes over I2C

Hi everyone,

I am trying to send 512bytes over I2C without editing the Buffer Size of Wire.h.

Does anyone have a example script for the sender and receiver?

Thanks.

Leon

you need to build your own protocol were you push chunks of data fitting in the buffer

typically you would have a start frame where you describe the size of the payload and maybe the number of packets that will be sent
then you send the payload in chunks
then possibly there is a final frame with some sort of CRC / checksum

One the receiving end you need to decode what it is getting (identify the start frame, await the right number of payload frames and confirm the data is OK by checking with the CRC frame)

in I2C, isn't an address specified followed by 1+ data written sequentially starting at the specified address.

if so, couldn't the data be broken into chunks written to different starting addresses?

This is currently my test code. I afterall changed the buffersize to 33, which means I can send a Package count at the beginning and then following with 32 bytes of data. The issue is that it keeps dropping bytes and therefor the data does not allign at the receiver end.

I'm not sure if this is the best way to do it. I would love to hear your thoughts.

Thanks.

This is the send code:

#include <Wire.h>

int count = 1;

void setup() {
  // put your setup code here, to run once:
  Wire.begin(8);
  //Wire.setClock(400000);      // Set the I2C SCL to 400kHz
  Wire.onRequest(requestEvent);
  //Serial.begin(9600);
}


void requestEvent() {

  Wire.write(count);

  if (count == 1) {
    for (int i = 1; i <= 32; i++) {
      Wire.write(i);
    }
  }
  if (count == 2) {
    for (int i = 33; i <= 64; i++) {
      Wire.write(i);
    }
  }
  if (count == 3) {
    for (int i = 65; i <= 96; i++) {
      Wire.write(i);
    }
  }
  if (count == 4) {
    for (int i = 97; i <= 128; i++) {
      Wire.write(i);
    }
  }
  if (count == 5) {
    for (int i = 129; i <= 160; i++) {
      Wire.write(i);
    }
  }
  if (count == 6) {
    for (int i = 161; i <= 192; i++) {
      Wire.write(i);
    }
  }
  if (count == 7) {
    for (int i = 193; i <= 224; i++) {
      Wire.write(i);
    }
  }
  if (count == 8) {
    for (int i = 225; i <= 256; i++) {
      Wire.write(i);
    }
  }
  if (count == 9) {
    for (int i = 257; i <= 288; i++) {
      Wire.write(i);
    }
  }
  if (count == 10) {
    for (int i = 289; i <= 320; i++) {
      Wire.write(i);
    }
  }
  if (count == 11) {
    for (int i = 321; i <= 352; i++) {
      Wire.write(i);
    }
  }
  if (count == 12) {
    for (int i = 353; i <= 384; i++) {
      Wire.write(i);
    }
  }
  if (count == 13) {
    for (int i = 385; i <= 416; i++) {
      Wire.write(i);
    }
  }
  if (count == 14) {
    for (int i = 417; i <= 448; i++) {
      Wire.write(i);
    }
  }
  if (count == 15) {
    for (int i = 449; i <= 480; i++) {
      Wire.write(i);
    }
  }
  if (count == 16) {
    for (int i = 481; i <= 512; i++)
      Wire.write(i);
    }
    count = 0;
  }
  count = count + 1;
}


void loop() {
  // put your main code here, to run repeatedly
}

This is the receive code:

#include <Wire.h>


int FaderChannel;  // Channel that is beeing controlled by the Fader

byte channel1;
byte channel2;
byte channel3;
byte value;

int F1Velocity = 0;
int F1Wert = 0;
int F1Smooth = 0;
byte F1F = A1;
byte S9S = 9;
byte S10S = 10;

bool S9state = 0;
bool S10state = 0;
byte S11S = 11;
byte S12S = 12;
bool S11state = 0;
bool S12state = 0;
bool Relative = 0;
bool Absolute = 0;
bool Setup = 0;

int MaxValue = 0;

int Package = 1;
bool Count = 0;

int eeprom1001 = 1001;  // EEPROM Address for Fader Patch
int eeprom1002 = 1002;
int eeprom1003 = 1003;

byte Universe[513];


void setup() {
  // put your setup code here, to run once:
  Wire.begin();
  Wire.setClock(400000);  // Set the I2C SCL to 400kHz
  Serial.begin(2000000);
}


void loop() {
  // put your main code here, to run repeatedly:


  // Communication between Arduinos
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 1) {
      for (int i = 1; i <= 32; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 2) {
      for (int i = 33; i <= 64; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 3) {
      for (int i = 65; i <= 96; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 4) {
      for (int i = 97; i <= 128; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 5) {
      for (int i = 129; i <= 160; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 6) {
      for (int i = 161; i <= 192; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 7) {
      for (int i = 193; i <= 224; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 8) {
      for (int i = 225; i <= 256; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 9) {
      for (int i = 257; i <= 288; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 10) {
      for (int i = 289; i <= 320; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 11) {
      for (int i = 321; i <= 352; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 12) {
      for (int i = 353; i <= 384; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 13) {
      for (int i = 385; i <= 416; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 14) {
      for (int i = 417; i <= 448; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 15) {
      for (int i = 449; i <= 480; i++) {
        Universe[i] = Wire.read();
      }
    }
  }
  Wire.requestFrom(8, 33);
  while (Wire.available()) {
    Package = Wire.read();
    if (Package == 16) {
      for (int i = 481; i <= 512; i++) {
        Universe[i] = Wire.read();
      }
    }
  }

    for (int i = 1; i <= 512; i++) {
      Serial.print(i);
      Serial.print(" : ");
      Serial.println(Universe[i]);
    }
}

Which address are you referring to? The I2C address will never change for a device; if that is what you're referring to, you will address another device when incrementing the address.

within a device each register has an address that needs to be specified in addition to the device address to read/write the register

multiple registers can be read/written sequentially from one specified addres

That's not a required I2C feature. It's typical of memory type devices. Auto Increment.

Be careful with the test data, you are trying to send numbers from 1 through 512, a byte is limited to a maximum value of 255.

Can you give us a broader view of your project ?

Why do you need two Arduino boards and why do you want to send 512 bytes and what are those bytes ?

Have you heard about the XY-problem ? https://xyproblem.info/

You can do all this in 1 for loop.

for (int i = 1; i <= 32; i++) {
Wire.write(i + (count * 32));
}

@theboot
your math is wrong (would need to be (count-1)) , you don't control the count (what if it's not 1 or 2) and a multiplication is costly so depends what the goal is - fewer lines of code or faster code.

@lellellel
An else would be good in between the ifs and using a byte instead of an int for the for loop would be better.

In this case, a five-bit shift to the left will do, if the compiler is not already recognizing that.

That is something else I would change. Have the arduino that requests the data send the value for count before the requestFrom(), that way you do not have to restart both arduino's if they get out of sync. Also, add a 34th byte for a crc check character, that along with the ability to send the value for count will allow for resending in the event of a data error.

Not sure whether it is important or not, I have seen recommendations that there should only be a single Wire.write() in the interrupt handler for a request, so I tend to copy everything into a temporary buffer and write from that. < edit > I found a post stating that this problem with the Wire library was corrected several years ago.

I wrote a small piece of code that will cut a long message into smaller chunks on the master and on the slave, the chunks will be put back together

MASTER:

// I2C MASTER
#include <Wire.h>
const byte slaveAddress = 0x08;

enum FrameType : uint8_t { START_FRAME = 0x42,
                           DATA_FRAME = 0xFF };

struct __attribute__((packed)) Payload {
  FrameType frameType;
  uint16_t totalSize;
  uint16_t currentSize;
  uint8_t data[32 - 2 * sizeof(uint16_t) - sizeof(FrameType)];  // max 32 bytes for Wire
};

void printTransactionStatus(uint8_t status) {
  switch (status) {
    case 0: Serial.println(F("success")); break;
    case 1: Serial.println(F("data too long to fit in transmit buffer.")); break;
    case 2: Serial.println(F("received NACK on transmit of address.")); break;
    case 3: Serial.println(F("received NACK on transmit of data.")); break;
    case 4: Serial.println(F("other error.")); break;
    case 5: Serial.println(F("timeout")); break;
  }
}

bool send(void *data, uint16_t count) {
  Payload payload;
  uint16_t bytesSent = 0;
  uint16_t remainingCount = count;

  // send a start frame
  payload.frameType = START_FRAME;
  bytesSent = min(remainingCount, sizeof payload.data);
  remainingCount -= bytesSent;
  payload.totalSize = count;
  payload.currentSize = bytesSent;
  memcpy(payload.data, data, bytesSent);
  Wire.beginTransmission(slaveAddress);
  Wire.write((const char *)&payload, sizeof payload);
  uint8_t success = Wire.endTransmission();

  if (success != 0) {
    Serial.print(F("Error for start Frame => "));
    printTransactionStatus(success);
    return false;
  }

  data = (uint8_t *)data + bytesSent;

  // send the rest of the data frames by iterating through the data
  while (remainingCount != 0) {
    payload.frameType = DATA_FRAME;
    bytesSent = min(remainingCount, sizeof payload.data);
    remainingCount -= bytesSent;
    payload.totalSize = count;
    payload.currentSize = bytesSent;
    memcpy(payload.data, data, bytesSent);
    Wire.beginTransmission(slaveAddress);
    Wire.write((const char *)&payload, sizeof payload);
    uint8_t success = Wire.endTransmission();

    if (success != 0) {
      Serial.print(F("Error sending extra frame => "));
      printTransactionStatus(success);
      return false;
    }
    data = (uint8_t *)data + bytesSent;
  }
  return true;
}

void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("\nType 's' to send a message.");
}

void loop() {
  char aLongMessage[] = "The quick brown fox jumps over the lazy dog.\nLe renard brun rapide saute par-dessus le chien paresseux.\nDer schnelle braune Fuchs springt über den faulen Hund.\nEl rápido zorro marrón salta sobre el perro perezoso.\nLa volpe veloce e bruna salta sopra il cane pigro.\n";
  if (Serial.read() == 's') send(aLongMessage, sizeof aLongMessage);
}

SLAVE:

// ARDUINO UNO - I2C SLAVE
#include <Wire.h>
enum FrameType : uint8_t { START_FRAME = 0x42,
                           DATA_FRAME = 0xFF };

struct __attribute__((packed)) Payload {
  FrameType frameType;
  uint16_t totalSize;
  uint16_t currentSize;
  uint8_t data[32 - 2 * sizeof(uint16_t) - sizeof(FrameType)];  // max 32 bytes for Wire
};

const uint16_t maxMessageSize = 512;
struct Message {
  uint16_t messageExpectedSize;
  uint16_t messageCurrentSize;
  uint8_t message[maxMessageSize];
};


Message message;
volatile bool messageReceived = false;

// this is called within an ISR context
void receiveEvent(int howMany) {
  byte frame[sizeof(Payload)];
  Payload payload;

  if (howMany == sizeof(Payload)) {
    uint8_t cnt = 0;
    while (Wire.available()) frame[cnt++] = Wire.read();
    memcpy(&payload, frame, sizeof(Payload));

    if (payload.frameType == START_FRAME) {
      message.messageExpectedSize = payload.totalSize;
      message.messageCurrentSize = 0;
    }
    if (message.messageCurrentSize + payload.currentSize < maxMessageSize) {
      memcpy(&(message.message[message.messageCurrentSize]), &payload.data, payload.currentSize);
      message.messageCurrentSize += payload.currentSize;
      messageReceived = (message.messageCurrentSize == payload.totalSize);
    } else {
      // not enough memory left in the buffer. ignore the payload
    }
  } else {
    while (Wire.available()) Wire.read();  // empty the buffer, ignore this frame with wrong size
  }
}

void setup() {
  Wire.begin(0x08);
  Serial.begin(115200);
  Wire.onReceive(receiveEvent);
  Serial.println(F("Ready to receive data"));
}

void loop() {
  if (messageReceived) {
    noInterrupts();
    Serial.print(F("New message with "));
    Serial.print(message.messageExpectedSize);
    Serial.println(F(" bytes."));
    Serial.print((char*)message.message);  // here we assume it's a cString that was sent including the trailing null char
    Serial.println(F("\n-------"));
    messageReceived = false;
    interrupts();
  }
}

(the slave's receiveEvent probably would be better without first receiving the data in a Payload structure and then copying it over the message buffer, you could directly receive the bytes and move them to he message - but I was too lazy to do it)

it's largely untested and does not deal with exceptions etc

basically to test it you load the code on two arduinos that are connected with 3 wires (SDA, SCL and GND) and on the master's Serial monitor when you type s and enter a message is sent in chunks to the slave arduino. The slave concatenates the bytes and set a flag that a message is ready. This is picked up in the loop and the message is printed (assuming it was a c-string with the null terminating char that was sent).

I tried it only as is, so take this as food for thoughts. It needs to be made more robust in case something goes wrong.