4

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 I²C library and set slave address
    Wire.onRequest(I2C_Send);                                                   // define I²C 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?

  • What is the I2C clock rate on the master? You set it on the slave but not the master device. – Andrew Sep 20 '16 at 8:10
  • Seems like all the calls to Wire.read(), save for the first, return -1, which becomes 0xffff when cast to uint16_t. Could you check for this condition? – Edgar Bonet Sep 20 '16 at 8:17
  • Do the two Arduinos share a common ground? – Code Gorilla Sep 20 '16 at 9:09
  • @Andrew, The clock speeds are both the default/standard 100k. – Cerin Sep 20 '16 at 14:26
  • @EdgarBonet, You're correct that read() returns -1 if nothing is received, but why would that only be happening for 2 of the 4 bytes? – Cerin Sep 20 '16 at 14:30
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 I²C library and set slave address
    Wire.onRequest(I2C_Send); // define I²C 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);
}
1

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

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.