Increase 32 byte I2C buffer size in Wire library

Hello,

I am sending display data from one Arduino to another via I2C. The display is an 8x8 RGB LED matrix having an 8 bit colour depth. A full frame of data is 3 bytes for each RGB pixel x 64 pixels = 192 bytes. I then add a start and end data marker byte which takes the total data size to 194 bytes. Ideally I want this all to fit in the I2C buffer so I can send it in one go.

I have tried upping the buffer size values in the following files in the Arduino IDE to 64 bytes as a test, and it worked.

Java/libraries/Wire/utility/twi.h
define TWI_BUFFER_LENGTH 64

and

Java/libraries/Wire/Wire.h
#define BUFFER_LENGTH 64

However if I up them to my ideal of 194 I get strange results - e.g. output to the serial console is truncated, as if I am writing into some other part of memory I should't be.

Is there a way to up these values to 194, or do I need to split the data into multiple I2C transmissions, e.g. for Red Green and Blue data?

Cheers
Nick

1 Like

I can't reproduce your problem. I made a copy of the SPI library and changed the two lines you mentioned. Then connected two Unos together, using Gnd, +5v, SCL, SDA.

Master:

#include <Wire.h>

#define ADDRESS 42

void setup () 
{
  Wire.begin ();
}  // end of setup

void loop () 
{

  Wire.beginTransmission (ADDRESS);
  for (int x = 0; x < 194; x++)
    Wire.send (x);
  Wire.endTransmission ();
    
   delay (2000);
}  // end of loop

Slave:

#include <Wire.h>

#define ADDRESS 42

void setup () 
{
  Serial.begin (9600);
  Wire.begin (ADDRESS);
  Wire.onReceive (receiveEvent);
}  // end of setup

volatile byte buf [200];
volatile byte pos = 0;

void loop() 
{
  // display when filled up
  if (pos >= 194)
   {
   for (int i = 0; i < 194; i++)
     Serial.println (buf [i], DEC);
   pos = 0;
   }  // end if
 }  // end loop


void receiveEvent (int howMany)
 {
  while (Wire.available () > 0)
  {
   byte c = Wire.receive ();
   if (pos < sizeof buf)
     buf [pos++] = c;  
  }  // end while available
  
}  // end of receiveEvent

Slave correctly displays the numbers 0 to 193. The whole I2C transaction took 18.6 ms.

Hey Nick,

Thanks so much for taking the time to look at this, it's interesting you saw no issue. After some more googling I found this post: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1294917906

This guy has a similar problems and thinks it's because he is running out of SRAM - he mentions the I2C buffers are defined multiple times in the Wire library. I think this may explain why I see problems, as I have a few other large arrays for display data and such like defined in my sketch.

At the moment I've compromised with 3 sends of data R, G and B. I hope the overhead of the extra sends isn't too much.

Cheers
Nick

Funny you should mention the SRAM. When I first saw your thread I thought "aha! - it works with 64 bytes but not more, that rings a bell.".

So I looked at length through the Atmel documentation for the magic number "64" because I was sure I had seen it as an I2C limit. But no. Then I looked at the Microchip documentation for their SRAM/EEPROM and indeed for those chips there is a limit of 64 in the I2C buffer.

So then I did a test bed to prove that it does work (I wasn't sure in advance what it would prove), but for my test program it did. So there is no hardware limit.

But of course your point of the multiple buffers is valid. For a chip that has maybe 1Kb SRAM (and you didn't mention your model of chip) then having multiple 194 byte buffers may be a problem for you. It certainly seems to have two buffers in Wire.cpp and another three buffers in twi.c. So it looks like 5 buffers is right. So 5 * 194 is 970 bytes, which on an Atmega138 is going to be almost all available RAM.

That's probably why they stuck to 32-byte buffers rather than 64-bye ones.

You know, even a small programming change reduces the need for big buffers? Change:

 Wire.beginTransmission (ADDRESS);
  for (int x = 0; x < 194; x++)
    Wire.send (x);
  Wire.endTransmission ();

to:

  for (int x = 0; x < 194; x++)
    {
    Wire.beginTransmission (ADDRESS);
    Wire.send (x);
    Wire.endTransmission ();
    }

It runs slightly slower because it sends the address in front of every byte, but perhaps in your case that doesn't matter too much.

In fact, as I had the test still set up, I changed the code to what I have above and re-timed it. It went from 18.6 ms to 45.0 ms. Over twice as long, as I would expect, since it sends the device address every time, but still, 45 ms isn't too bad.

If you leave it as how you describe (3 batches rather than 1) then the extra overhead would be neglible.

Ah cool - that's an interesting way of doing it, I hadn't thought of sending each byte separately.

Out of interest how are you timing the I2C transactions? Speed may become important as I might want to send frames of video to a number of matrices with different addresses.

I have an ATmega 328 (so 2K SRAM) but I don't really want to use a whole kilobyte for buffers!

Nick

With my trusty Saleae Logic analyzer. It's great for situations like that. Not only can you time stuff but you see what is there. For example, when I first did it I had it as in my most recent example, and I looked at the logic output and thought "there are too many address bytes there" so I moved the beginTransmission out of the loop. That's the sort of thing you miss otherwise, because it seems to work anyway.

By the way, it's about 30 times as fast to use SPI, and then you don't have buffer issues because each byte goes individually. That's only two more wires.

That's interesting to know about SPI, I didn't realise it was quicker. I'll see how I do with the I2C stuff to start with.

Thanks for all your help,

Nick

i could not, i'have an arduino due and im communicating with an i2c slave with sda,scl (not sda1,scl1). Master should send 64 bytes in one packet. So, i changed the library but nothing changed? How can i solve?
(Wire.h library)

Thanks.

So, i changed the library but nothing changed? How can i solve?

Change the library correctly. Without seeing what you did, and what the results are, you really can't expect more than that.

Thanks for your reply.

I'm sure that, i changed twi and wire libraries correctly, and i controlled i2c traffic with total phase beagle i2c analyzer but it sends only 32bytes per packet. (Not only in windows, and also i tryed it in archlinux os) However, i recognize that even i delete the whole wire library, arduino program compiling wire.h library & source code without wire.h library. How it can be? Can it be related with arduino due board?

Or are there any libraries instead of wire.h for due and high buffer sizes?

Just for future info for anyone who visit this thread... A better way and faster than nickgammon said, is to send in packages of less than 32 Bytes (instead of send one by one).
So it could be something like that:

String to_send = "something with a lot more than 32 bytes of info. Maybe several sensors feedback";
int size = to_send.length();

while(size>0)
{
   
   if (size>30)
   {
     packet = to_send.substring(0,30);
     to_send = to_send.substring(30);
     size = to_send.length();
   } else
   {
     packet = to_send;
     size = 0;
   }
   Wire.beginTransmission(slave_address); // transmit to device
   Wire.write(packet.c_str());
   Wire.endTransmission();    // stop transmitting
   
 }
1 Like

Thanks all.
I spend a full day before coming here to look it up. Thought my unsigned long was messing up the I2C transfer stream but it just happened to be byte 33 of 45. I never thought about buffer size in I2C.