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);
}
Advertisements

15 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. 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.

  3. 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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s