How do you transmit and read a uint16_t over I2C?
I'm trying to read two uint16_t values from a slave device, and I'm seeing nonsensical readings.
This is the code on my slave Arduino Uno:
#include <Wire.h>
void send_wire_int(uint16_t num){
// Send low byte
Wire.write((uint8_t)num);
// Send high byte
num >>= 8;
Wire.write((uint8_t)num);
}
// This is called from the I2C host.
void I2C_Send()
{
// send_wire_int(acount_abs);// Send encoder A count.
// send_wire_int(bcount_abs);// Send encoder B count.
send_wire_int(0);
send_wire_int(0);
}
void setup(){
Wire.begin(30); // initialize I2C library and set slave address
Wire.onRequest(I2C_Send); // define I2C slave transmit ISR
Wire.flush();
Wire.setTimeout(1000L);
}
void loop(){
//stuff
}
All it does is wait for the master to request data. Originally, it was sending the values of two registers back, but these values were also garbled or non-changing, so I changed it to just send back 0s but I still get the identical garbled values.
This is the code on my master Arduino Uno:
#include <Wire.h>
uint16_t receive_wire_int(){
while(Wire.available() < 1){}
// Read low byte into rxnum
uint16_t rxnum = Wire.read();
while(Wire.available() < 1){}
// Read high byte into rxnum
rxnum += Wire.read() << 8;
return rxnum;
}
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Wire.setTimeout(1000L);
Serial.begin(9600); // start serial for output
}
void loop() {
Serial.println("Requesting data..."); Serial.flush();
Wire.requestFrom(30, 4);
uint16_t acount = receive_wire_int();
uint16_t bcount = receive_wire_int();
Serial.println(String("acount:")+String(acount)+String(" bcount:")+String(bcount));
delay(500);
}
As you can see, it simply requests the two uint16_t values in 4 bytes, and then prints them to the Serial (my laptop's terminal).
The output I'm seeing is:
acount:65280 bcount:65535
when I would expect:
acount:0 bcount:0
What I am I doing wrong?
2 Answers 2
So I've just tested your code and apparently you have to send all data at once. After some research: it sends characters into the function twi_transmit
and this function just fills the buffer and actual data are sent asynchronously. So, if you are fast enough, you will overwrite the buffer with another bytes and it's still like you have only 1 character to send.
For example this works (AVRs are little endian):
#include <Wire.h>
// This is called from the I2C host.
void I2C_Send()
{
uint16_t uints[2] = {0xDEAD,0xBEEF};
Wire.write((uint8_t*) uints, 4);
}
void setup(){
Wire.begin(30); // initialize I2C library and set slave address
Wire.onRequest(I2C_Send); // define I2C slave transmit ISR
Wire.flush();
Wire.setTimeout(1000L);
}
void loop(){
//stuff
}
And receiver with HEX
prints:
#include <Wire.h>
uint16_t receive_wire_int(){
while(Wire.available() < 1){}
// Read low byte into rxnum
uint16_t rxnum = Wire.read();
while(Wire.available() < 1){}
// Read high byte into rxnum
rxnum += Wire.read() << 8;
return rxnum;
}
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Wire.setTimeout(1000L);
Serial.begin(9600); // start serial for output
}
void loop() {
Serial.println("Requesting data..."); Serial.flush();
Wire.requestFrom(30, 4);
uint16_t acount = receive_wire_int();
uint16_t bcount = receive_wire_int();
Serial.print("acount: 0x");
Serial.print(acount,HEX);
Serial.print(" bcount: 0x");
Serial.println(bcount,HEX);
delay(500);
}
The problem is that I'm using the wrong type of write()
in my slave. As this blog explains, there are two overloaded write()
methods in the Wire library. One that accepts a single byte, and another that accepts a byte array. I was sending each byte separately, which doesn't guarantee they'll be received together.
It works if I rewrite my slave's to:
void I2C_Send()
{
byte myArray[4];
myArray[0] = 0;
myArray[1] = 0;
myArray[2] = 0;
myArray[3] = 0;
Wire.write(myArray, 4);
}
Wire.read()
, save for the first, return-1
, which becomes0xffff
when cast touint16_t
. Could you check for this condition?