Sending 16 bit and 32 bit numbers with Arduino I2C

I’ve been using I2C a lot lately and something that keeps popping up is the need to send large numbers.  I2C normally only sends a single byte at a time so you are limited to 255 as the largest number.  This post will show you how I break large numbers apart to send them over I2C and reassemble them on the other side.

Before beginning, it’s important to know that on most Arduinos and ATmega chips, have a 16 bit integer, meaning that the integer data type takes up 2 bytes.  There are several other data types that you may find useful as they are more specific about their size and sign.  If you are ever confused about the size of a particular data type, you can use sizeOf(data type) function to get the size.

Data Type Size Range
int 2 bytes -32,768 to 32,767
int8_t 1 byte -128 to 128
uint8_t 1 byte 0 to 255
int16_t 2 bytes -32,768 to 32,767
uint16_t 2 bytes 0 to 65,535
int32_t 4 bytes -2,147,483,648 to 2,147,483,647
uint32_t 4 bytes 0 to 4,294,967,295

As you can see, most of these are more than one byte so sending them over I2C will be a bit of a problem.  What you have to do is split the number into individual bytes, put them into an array, and send it over.  On the other end, you read the array in as individual bytes and rebuild it into the appropriately sized number.

To do this, we will be using a few things you might not have come across before.  The datatype byte is pretty much the same as char and uint_8 and as you would imagine, it stores a single byte.  The & and | characters are used for bitwise addition.  You can find more info with a quick google search but an example would be that 11111111 & 10101010 = 10101010 and 10101010 | 01010101 = 11111111.  Finally, we will also be using << which shifts bits to the left and >> which shifts bits to the right.  The finally thing is that I will use the hex number 0xFF, which is the same as binary 11111111 because it is shorter.

I will have full examples down at the bottom, but I want to quickly go over an example for sending 16 bit numbers.

First we declare an integer and assign it a value
int bigNum = 12345;

Then we declare an array of type byte with two elements
byte myArray[2];

We need to set the first element to the first byte of bigNum and the second element to the second byte of bigNum.  We get the second element by anding it with 0xFF (11111111), which essentially just isolates the last byte.  We get the first element by shifting bigNum 8 bits to the right and anding it with 0xFF.  Shifting bigNum by 8 bits to the right essentially moves the second byte into the first bytes place and then isolates that byte.
myArray[0] = (bigNum >> 8) & 0xFF;
myArray[1] = bigNum & 0xFF;

Next we send that array over
Wire.write(myArray, 2);

On the master side, first declare our integer and bytes
int bigNum;
byte a, b;

Then we request the bytes
Wire.request(54, 2);

Then read in the bytes;
a = Wire.read();
b = Wire.read();

Next, we do the opposite shifting that we did on sending side. This time we know that a is the most significant bit so we assign it to bigNum.  Then we shift it 8 bits to the left so that it is in the correct place and add in the least significant bit by or-ing it with b.
bigNum = a;
bigNum = (bigNum << 8) | b;

Here are the master and slave code for sending 16 bit and 32 bit numbers.  Note that I’m using int16_t and int32_t to be as explicit as possible.  You’ll rarely get it trouble for being too correct.

#include <Wire.h>;

void setup()
{
Wire.begin();
Serial.begin(9600);
}

void loop()
{
delay(2000);

int32_t bigNum;

byte a,b,c,d;

Wire.requestFrom(54,4);

a = Wire.read();
b = Wire.read();
c = Wire.read();
d = Wire.read();

bigNum = a;
bigNum = (bigNum << 8) | b;
bigNum = (bigNum << 8) | c;
bigNum = (bigNum << 8) | d;

Serial.print(bigNum);
Serial.print("\n");
}
#include <Wire.h>;

void setup()
{
Wire.begin(54);
Wire.onRequest(requestEvent);
}

void loop()
{
delay(100);
}

void requestEvent()
{
int32_t bigNum = 12345678;
byte myArray[4];

myArray[0] = (bigNum >> 24) &amp; 0xFF;
myArray[1] = (bigNum >> 16) &amp; 0xFF;
myArray[2] = (bigNum >> 8) &amp; 0xFF;
myArray[3] = bigNum & 0xFF;

Wire.write(myArray, 4);
}
#include <Wire.h>

void setup()
{
Wire.begin();
Serial.begin(9600);
}

void loop()
{
delay(2000);

int16_t bigNum;

byte a,b;
Wire.requestFrom(54,2);

a = Wire.read();
b = Wire.read();

smallNum = a;
smallNum = smallNum << 8 | b;

Serial.print(bigNum);
Serial.print("\n");
}
#include <Wire.h>

void setup()
{
Wire.begin(54);
Wire.onRequest(requestEvent);
}

void loop()
{
delay(100);
}

void requestEvent()
{
int16_t bigNum = 1234;
byte myArray[2];

myArray[0] = (bigNum >> 8) &amp; 0xFF;
myArray[1] = bigNum & 0xFF;
Wire.write(myArray, 2);
}

25 thoughts on “Sending 16 bit and 32 bit numbers with Arduino I2C

  1. How to send from slave to master more than 200 bytes?
    I’ve done this but no changes:
    #define TWI_BUFFER_LENGTH 300
    #define RAWBUF_BYTE TWI_BUFFER_LENGTH
    #define TWI_B_OVERRIDE TWI_BUFFER_LENGTH
    #define BUFFER_LENGTH TWI_BUFFER_LENGTH

    Wire.begin(2);
    byte toSend [300];
    for(int i=0;i<TWI_BUFFER_LENGTH;i++);
    toSend[i]=i;
    Wire.write(toSend,300);

    1. No, #include is correct. You can verify that part yourself by opening up an example by clicking File>Examples>Wire. Maybe something else in your code isn’t working?

  2. you missed array declaration

    byte myArray[]={};

    put this code at the beginning and it works fine!

    thanks!

  3. This is a common issue, which is solved easily by converting your large numbers to BCD (binary coded decimal), transmitting them as bytes, and reconstructing them on the other side. Works for all data sizes, even doubles, floats and long ints. So you’ll need two functions: NumToBCD() and BCDtoNum(), easily found on Google.

  4. In fact, come to think of it, I’d use the C construct of a union both sides. No conversions necessary.
    Come to think of it even more, this may also work (sorry I don’t have a test circuit lying around at the moment):
    If you declared your large number variable as

    uint32_t val1=9838321; // Whatever large 32-bit number you choose
    uint32_t *ptr = &val1; // ptr is the address of variable val1

    You can most probably call

    Wire.write(ptr, sizeof(uint32_t));

    and vice versa on the slave side.
    I’m not sure what the compiler and the wire library will make of this, but its legit C, and matches the prototypes, so it should be fine.

      1. That happens when you copy and paste code from web pages. “amp” is the HTML conversion of what should be the ampersand character, which means “address of” in C.

    1. Won’t
      Wire.write(ptr, sizeof(uint32_t));
      transmit the address of val1 itself (instead of its actual contents)?
      How is that useful (meaningful even) to the receiver?

  5. Yes, its supposed to be that way. Wire.write expects a pointer (address) in its construct, followed by how many. If you look in the header files for the library, you’ll see the prototype for the Wire.write method. So, it takes the memory address of the data, and a count, and transmits that many from that address.

  6. Am I the only having problem compiling my code because of the &amp? My Arduino is having error because of that &amp line

    1. Just replace it with an actual ampersand and you should be fine. About once a year I got through and fix all of these and then something happens on wordpress and they all get screwed up again, sorry.

    2. A little bit up, you’ll see the answer to this. Its a symptom of copying and pasting code without understanding what it does. That’s dangerous.

  7. I don’t have a setup to test this but…
    Instead of all that bitshifting, would this work using unions?
    Then just insert of retrieve the matching type at either end.
    e.g.
    union sixteenBits { uint16_t u16; uint8_t u8[2]; };
    or
    union thirtyTwoBits { float f32; uint32_t u32; uint16_t u16[2]; uint8_t u8[4]; };

  8. hi thank you for posting the code
    but here in 16 bit master code i felt something mistake i.e.
    smallNum = a;
    smallNum = smallNum << 8 | b;
    Serial.print(bigNum);

    i think here this should be like this
    bigNum = a;
    bigNum= bigNum<< 8 | b;
    Serial.print(bigNum);

    if my thought is correct make changes if I'm wrong let me know please reply

    thank you

Leave a comment