i2c data transfer - limits on numbers (2^8)?

Hi,

I am having a peculiar problem sending numbers over i2c.

I'm using a Mega as a slave device to Tx, and a Due as a master device to Rx.

It all works fine if I send an integer less than or equal to 255, but starts wrapping itself if the value goes above this, so e.g. 260 becomes 4!

Clearly (I think atleast), I'm running into the problem where I am running over 2^8 restriction on numbers that can be transfered... but why is this the case? Or is something else the matter which I've failed to pick up on?

Code for slave sender:

#include <Wire.h>
int dacValue0_TX = 260; // CHANGE THIS VALUE TO e.g. 255, 123, and 260, 264...
int dacValue0int_TX = dacValue0_TX * 100;

void setup()
{
  // Join i2c bus with address #11
  Wire.begin(10);
  Wire.onRequest(requestEvent);      // register event
}

void loop()
{
  delay(500);
}

// Function that executes whenever data is requested by master
// This function is registered as an event, see setup()
void requestEvent()
{
  // Respond with value reported from dac0
  Wire.write(dacValue0_TX);
}

code for master receiver:

#include <Wire.h>

void setup()
{
  Wire.begin(); Wire1.begin();       // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
  
}

void loop()
{
  // ~~ START Inquire value from SlaveID#11 ~~ //
  Wire.requestFrom(10, 1);
  while (Wire.available())
  {
    int dacValue0int_RX = Wire.read();
    float dacValue0_RX = dacValue0int_RX / 100.0;
    Serial.print("dac0: "); Serial.print(dacValue0int_RX); Serial.println(" V"); 
  }
  delay(500);
}

A few points:-

wire.write() in the form that you use it only sends one byte. There is an overridden version that writes multiple bytes.

wire.read() only reads one byte at a time.

Your receiver only asks for one byte here:-

Wire.requestFrom(10, 1);

These are the definitions of the two 'write()' functions:-

size_t TwoWire::write(uint8_t data)
{
  if(transmitting){
  // in master transmitter mode
    // don't bother if buffer is full
    if(txBufferLength >= BUFFER_LENGTH){
      setWriteError();
      return 0;
    }
    // put byte in tx buffer
    txBuffer[txBufferIndex] = data;
    ++txBufferIndex;
    // update amount in buffer   
    txBufferLength = txBufferIndex;
  }else{
  // in slave send mode
    // reply to master
    twi_transmit(&data, 1);
  }
  return 1;
}

// must be called in:
// slave tx event callback
// or after beginTransmission(address)
size_t TwoWire::write(const uint8_t *data, size_t quantity)
{
  if(transmitting){
  // in master transmitter mode
    for(size_t i = 0; i < quantity; ++i){
      write(data[i]);
    }
  }else{
  // in slave send mode
    // reply to master
    twi_transmit(data, quantity);
  }
  return quantity;
}

It would be worth you having a good look at the library, and any reference material you can find.

Edit: Accurate comments help too:-

// ~~ START Inquire value from SlaveID#11 ~~ // *** Slave address 10, not 11 !
  Wire.requestFrom(10, 1);

To supplement what OldSteve said, what would you expect to see if an int data type had the value 260 assigned to it and the Arduino stored the two bytes as LoByte/HiByte and your code only reads one byte? (Hint: 260 decimal is 00000001 00000100 binary.)

Thanks :slight_smile:

OK, so these functions as I'd defined were only sending/receiving 1 byte - explains the 2^8 limitation.

If I use Wire.requestFrom(10, 2);

Then in the serial monitor, for Tx 260, I can see 4 & 255.

What though would be the way to transmit more than one bye. What I can see from the documentation is to split (I think) the data into an array... and then define Wire.write(data, 2), etc. - any pointers on how to do that?

See: I2C : Send and receive any data type.

Nick's answer is better, but since I've already typed it, here's one way of splitting an int into bytes, then putting it back together:-

void setup()
{
    Serial.begin(115200);
    
    int origVal=260;
    // Split it up into bytes:-
    byte hb=highByte(origVal);
    byte lb=lowByte(origVal);

    // Put it back together:-
    int rxVal=(hb<<8)+lb;
    Serial.println(rxVal);
}

void loop(){}

I am still having some problems with execution. I am getting

nan

when trying to read the data on the master. Using the simple example codes provided with the wire library, with added i2c_anything library functions.

MASTER RECEIVE CODE:

#include <Wire.h>
#include <I2C_Anything.h>

void setup()
{
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

volatile float x;

void loop()
{
  Wire.requestFrom(2, 1);    // request 1 bytes from slave device #2

  while (Wire.available())   // slave may send less than requested
  {
    I2C_readAnything(x);
    Serial.println(x);         // print the character
  }

  delay(500);
}

SLAVE SEND CODE:

#include <Wire.h>
#include <I2C_Anything.h>

float x = 20.0;
void setup()
{
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onRequest(requestEvent); // register event
}

void loop()
{
  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
  I2C_writeAnything(x);
}
  Wire.requestFrom(2, 1);    // request 6 bytes from slave device #2

Request 6, or 1?

1 byte - corrected in post.

CGH-M:
1 byte - corrected in post.

Wasn't the point of all the replies you got, that you need more than one byte?

Yes, but I am trying to get my head around using the i2c library by a float with value under 255 first. And if I can get that working then I can try to send / receive two bytes.

and using Using method suggested by user: OLDSTEVE,

I am getting 0 on the serial monitor - so nothing is being tx/rx ?

By the way, I am using the i2c protocol because I have used it before to send small numbers from one arduino to another - never had a large number so never encountered this problem. Is there a better way to send / receive from a slave to a master? I am going to be using two Arduino Pro Minis to read a voltage value (between -1 and +5) and send these to an Arduino Due to then print to an LCD (using serial monitor for now).

MASTER READ CODE:

#include <Wire.h>
#include <I2C_Anything.h>

void setup()
{
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

volatile int rxVal;
byte hb;
byte lb;



void loop()
{
  Wire.requestFrom(2, 2);    // request 2 bytes from slave device #2
  while (Wire.available())   // slave may send less than requested
  {
    Wire.read();
    rxVal=(hb<<8)+lb;
    Serial.println(rxVal);         // print the character
  }

  delay(500);
}

SLAVE SEND CODE:

#include <Wire.h>
#include <I2C_Anything.h>

int txVal = 260;
byte hb = highByte (txVal);
byte lb = lowByte (txVal);


void setup()
{
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onRequest(requestEvent); // register event
}

void loop()
{
  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
  Wire.write(hb); Wire.write(lb);
}

CGH-M:
Yes, but I am trying to get my head around using the i2c library by a float with value under 255 first.

Bad idea. You're completely on the wrong track.

Ah ok.

What I'd been doing previously is...

sending:
float a = 1.24;
int x = a * 10;
(this becomes 12 now and I loose the 0.4 precision that I need)

and then receive:
int x;
float y = x/10.0
(now I read 1.2 instead of 1.24)..

So as I mentioned in a preceeding post, the problem is now the values I need to transmit are larger than 2.55 -- which I completely did not realize earlier was a limiter.

You can't transmit or do anything with "part of a float". The nature of its representation in binary digits does not allow that. You have to use 4 bytes. That is all there is to it. If you want to send an int, it is 2 bytes, and that is all there is to that.

CGH-M:
SLAVE SEND CODE:

...

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
  Wire.write(hb); Wire.write(lb);
}

See: Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino

In particular:

Warning - because of the way the Wire library is written, the requestEvent handler can only (successfully) do a single send. The reason is that each attempt to send a reply resets the internal buffer back to the start. Thus in your requestEvent, if you need to send multiple bytes, you should assemble them into a temporary buffer, and then send that buffer using a single Wire.write.

On top of what Nick says, you interpreted things completely wrong on the receiving end as well.
This could never work:-

#include <Wire.h>
#include <I2C_Anything.h>

void setup()
{
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

volatile int rxVal;
byte hb;
byte lb;

void loop()
{
  Wire.requestFrom(2, 2);    // request 2 bytes from slave device #2
  while (Wire.available())   // slave may send less than requested
  {
    Wire.read();
    rxVal=(hb<<8)+lb;
    Serial.println(rxVal);         // print the character
  }

  delay(500);
}

You do a Wire.read() and don't do anything with the return value(s) and expect it to magically appear in both 'hb' and 'lb'.
What you really did was create two uninitialised variables, 'hb' and 'lb', then without altering their uninitialised state you put them together into a 16-bit variable, (rxVal), and expected it to hold your float that was sent by the slave.
The 0 that you mentioned was exactly what I would expect you to end up with under those circumstances.

You really should go back to the beginning and spend more time learning the basics of the language and how things work, then put on your thinking cap and try again. (This is a good opportunity to learn some things.)

You need to send the high byte and the low byte (separately) as mentioned by Nick, using the version of Wire.write() that sends multiple bytes, then also receive them separately using 'Wire.read()' for each - the high byte into 'hb' and the low byte into 'lb'.

Edit: You could maybe use a two element byte array for the high and low bytes and increment an index on each Wire.read().

OK so I have managed to send ONE int from slave to master, using the code below:
Instead of converting voltages to floats, I will retain the int from analogRead, transfer that to master, and then convert to float.

Slave-Send

#include <Wire.h>
#include <I2C_Anything.h>

int test1 = 260;

void setup()
{
  // Join i2c bus with address #11
  Wire.begin(11);      
  Wire.onRequest(requestEvent);      // register event
  Wire.onRequest(requestEvent2);
}

void loop()
{
  delay(500);
}

void requestEvent()
{
  I2C_writeAnything(test1);
}

Master-Read

#include <Wire.h>
#include <I2C_Anything.h>

void setup()
{
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

volatile int test1;

void loop()
{ // ~~ START Inquire value from SlaveID#11 ~~ //
  Wire.requestFrom(11, 2);
  {
    I2C_readAnything (test1);
    Serial.println(test1); 
  }
delay(500);
}

BUT if I try now to send two floats.
on the slave code, adding:

int test2 = 56;
.
.

I2C_writeAnything(test2);

and on the master code, adding:
volatile int test2;
.
.
Wire.requestFrom(11, 4);
{
(from before) I2C_readAnything (test1);
I2C_readAnything (test1);
Serial.print(test1); Serial.println(test2);
}

I don't get the second int correctly...

Don't do serial prints inside an ISR.

UPADATE: got the code working using pointers, but only halfway... See the code works like a charm if I'm using a DUE as a slave, but not when I use the DUE as a master (which is my end goal...)

the other arduino, I've tried UNO, MEGA, PRO mini 5V and for all cases, if this arduino is a master, my code works fine, but not if this arduino is the slave...

Can someone see where this discrepancy might stem from? Codes posted below.

( Next iteration, I will try to put the print commands outside the request function, Nick :slight_smile: )

Master - Read

// Wire Master Reader
// I2C Protocol
#include <Wire.h>

void setup()
{
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

volatile int val_a; volatile int val_b; volatile int val;
volatile int hel_a; volatile int hel_b; volatile int hel;

void loop()
{

  // ~~ START Inquire value from SlaveID#11 ~~ //
  Wire.requestFrom(11, 4);
  while (Wire.available())
  {
    val_a = Wire.read(); val_b = Wire.read();
    val = (val_b << 8) | val_a;
    Serial.print("val:"); Serial.println(val);

    hel_a = Wire.read(); hel_b = Wire.read();
    hel = (hel_b << 8) | hel_a;
    Serial.print("hel:"); Serial.println(hel);
    Serial.println("");
  }
  delay(500);
}

Slave - Send

// Wire Slave Sender
// I2C Protocol
// Slave ID 11
#include <Wire.h>

int val = 1245;
int hel = 9876;

void setup()
{
  // Join i2c bus with address #11
  Wire.begin(11);      
  Wire.onRequest(requestEvent);      // register event
}

void loop()
{
  delay(500);

}

// Function that executes whenever data is requested by master
// This function is registered as an event, see setup()
void requestEvent()
{
  Wire.write(val); // lower byte
  Wire.write(val>>8); // upper byte

  Wire.write(hel); // lower byte
  Wire.write(hel>>8); // upper byte
}

See Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino

In particular:

Warning - because of the way the Wire library is written, the requestEvent handler can only (successfully) do a single send. The reason is that each attempt to send a reply resets the internal buffer back to the start. Thus in your requestEvent, if you need to send multiple bytes, you should assemble them into a temporary buffer, and then send that buffer using a single Wire.write.