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) & 0xFF; myArray[1] = (bigNum >> 16) & 0xFF; myArray[2] = (bigNum >> 8) & 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) & 0xFF; myArray[1] = bigNum & 0xFF; Wire.write(myArray, 2); }
thank you you saved me, was searching internet for similar solution on found yours.
im sending unix timestamps over i2c
You just helped me in my quest to make a .GIF encoder, thankyou 🙂
heey bro can i contact with u cuz im working on shaft incoder tooo
Thank you! Was wondering where the issue was sending UIDs through I2C and getting FF as a second byte =)
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);
I guess, you can’t send via I2C-bus a buffer longer than 255 bytes…
This code does not work, it seems like #include <Wire.h> isnt a library, unless its wire.h
but all i got was just -1s lol
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?
This code continuously prints 255. I copied and pasted it into arduino ide
actually, this code just prints 0 for the 16 bit one, dont know why but this isnt working 😐
you missed array declaration
byte myArray[]={};
put this code at the beginning and it works fine!
thanks!
You are correct! In the 32bit slave I missed that but have fixed it now. Thanks Mihir.
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.
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.
‘amp’ was not declared in this scope
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.
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?
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.
Am I the only having problem compiling my code because of the &? My Arduino is having error because of that & line
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.
What is an ampersand? Sorry do you mind emailing me the Arduino code? Really need it. Thanks.
Email: pohboonpin12345@gmail.com
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.
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]; };
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
Thank you very much for sharing this.