6

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?

Cerin
  • 1,618
  • 2
  • 24
  • 41
  • 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 08: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 08:17
  • Do the two Arduinos share a common ground? – Code Gorilla Sep 20 '16 at 09: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
  • @Matt, Yes, the I2C connection includes the SCL/SDA pins as well as Vcc/Gnd. – Cerin Sep 20 '16 at 14:31
  • @Cerin - Had to check I assume you have pull up resistors too. I think the read function will return -1 if there is no data. Which is strange because the available function says there is 4 bytes of data. What happens if the 1ms timeout expires? Can you make the timeout stupidly large? You are getting -1 for 3 bytes not 2. The second byte (acount MSB is also -1). Should there be a Wire.available() call in your read function to wait for the second byte to become available? – Code Gorilla Sep 20 '16 at 14:49
  • @Matt, Yeah, it looks like maybe only one byte is making it over, and the other read() calls are returning -1. I tried changing the timeout to 1000ms, but that had no effect. I also changed the reads to wait for each byte individually, but that also had no effect. – Cerin Sep 20 '16 at 15:05
  • @Cerin - Have you looked at the write function in the same way? would adding a delay statement between the writes help? Does Write.write() return a value, is it possible that you are only writing one byte of data? – Code Gorilla Sep 20 '16 at 15:12

2 Answers2

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);
}
KIIV
  • 4,742
  • 1
  • 12
  • 21
2

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

}
Cerin
  • 1,618
  • 2
  • 24
  • 41