sending multiple floats over i2c

Hi, I’m using two slave arduinos (pro mini /mega) to read analog input voltages and send these to a master due using i2c. I’ve got an idea for the basics, and am able to send floats to 1 decimal precision using by converting them to int first, then converting back to float.

Max. voltage is 5V.

The problem with my method is that either I can go up to 2.55V if I want 2decimal precision, or go higher but loose a decimal precision (which I don’t want to do).

Could someone please help me with the code to accomplish this task? I’ve tried various different methods like union, i2c_anything, but cannot get a handle on it.

My code is provided below (for one slave and one master, for simplicity):

SLAVE (send)

#include <Wire.h>
float dacValue0TX = 2.39;
long dacValue0intTX = dacValue0TX*10;

void setup()
{
  // Join i2c bus with address #10
  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()
{
  // Respond with value reported from dac0
  Wire.write(dacValue0intTX);
}

MASTER (read)

// Wire Master Reader
// I2C Protocol

#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(11, 1);
  while (Wire.available())
  {
    int dacValue0int_RX = Wire.read();
    float dacValue0_RX = dacValue0int_RX / 10.0;
    Serial.print("dac0: ");
    Serial.print(dacValue0_RX);
    Serial.println(" V");
  }
  // ~~ END Inquire value from SlaveID#11 ~~ //
  
  // ~~ START Inquire value from SlaveID#9 ~~ //
  Wire.requestFrom(9, 1);
  while (Wire.available())
  {
    int dacValue1int_RX = Wire.read();
    float dacValue1_RX = dacValue1int_RX / 10.0;
    Serial.print("dac1: ");
    Serial.print(dacValue1_RX);
    Serial.println(" V");
  }
  // ~~ END Inquire value from SlaveID#9 ~~ //
delay(500);
}

The most efficient way to transfer floats is to do it in binary. Floats are 4-bytes in size so you have to break down your float variable into 4 parts that can be shifted out your I2C module. We can do this using type casting and pointers to refer to each byte in the float. Here is what the code could look like:

unsigned int i = 0;
float dacValue0TX = 2.39;

for(i=0; i<4; i++)
{
	Output_I2C_Byte( *((unsigned char*)&dacValue0TX + i) );
}

This little bit of code basically points to the memory address of dacValue0TX and looks at it as a bunch of 8-bit CHARS. The FOR loop adds an offset to the pointer which increments by 1 after every pass through the loop. After 4 passes, all 4 bytes of the float are transferred individually with no time consuming conversions being needed. I just made up the function Output_I2C_Byte() so it can be anything you want or even an array if you want to separate the bytes into other variables.

In memory, dacValue0TX=2.39 looks like this: 0x4018F5C3

Pass #1 transmits: 0xC3

Pass #2 transmits: 0xF5

Pass #3 transmits: 0x18

Pass #4 transmits: 0x40

You can then reverse the process on the receiving end to put the float value back together.

Keep in mind that there are multiple ways to accomplish what I just showed you. This is the method I prefer since the compiler overhead to implement it is pretty low.

-Bill

I’m a big believer in human-readable protocols. I’d convert to text and send it that way. Otherwise, use an existing protocol such as Modbus or Firmata that has the bugs wrung out of it.

I don’t disagree with you about human-readable protocols however this is being done over I2C so you don’t have a means of readily reading the data in a terminal window anyway. The back and forth conversion between float and int is a huge resource waste and isn’t necessary. The OP’s concern over precision and significant figures is handled with no losses by leaving everything binary.

-Bill

Thanks for the help!

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

For 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.

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
    }