Float I2c

Is there a nice simple way to Pass Float Value over I2C -to a second arduino- (example 3.12 for Volts). All ive seen is elaborate unions and pointers ,, Seems WAY over complexe so i skipped reading further. Any thoughts/ideas /Examples/referances :slight_smile: ?

Wow thers a first, never had one with out some kind of responce :slight_smile:

You need a union. You said you don't want to use one. Not much left to say. They are not particularly elaborate BTW.

Nick Gammon , you sem to the goto for I2C ,, have your had the chance to run "unions" before ?

OK, here's how to do it. Based on the EEPROM_Anything idea I did this ...


I2C_Anything.h

Make a new tab in the IDE called "I2C_Anything.h". Put this in it:

// Written by Nick Gammon
// May 2012

#include <Arduino.h>
#include <Wire.h>

template <typename T> int I2C_writeAnything (const T& value)
  {
    const byte * p = (const byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          Wire.write(*p++);
    return i;
  }  // end of I2C_writeAnything

template <typename T> int I2C_readAnything(T& value)
  {
    byte * p = (byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          *p++ = Wire.read();
    return i;
  }  // end of I2C_readAnything

Now here is an example of sending a float and a long to a slave:


Master

// Written by Nick Gammon
// May 2012

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

const byte SLAVE_ADDRESS = 42;

void setup() 
{
  Wire.begin ();
}  // end of setup

void loop() 
{

 long foo = 42;
 
 for (float fnum = 1; fnum <= 10; fnum += 0.015)
    {  
    Wire.beginTransmission (SLAVE_ADDRESS);
    I2C_writeAnything (fnum);
    I2C_writeAnything (foo++);
    Wire.endTransmission ();
      
    delay (200);
    }  // end of for

}  // end of loop

And here is the slave:

Slave

// Written by Nick Gammon
// May 2012

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

const byte MY_ADDRESS = 42;

void setup() 
{
  Wire.begin (MY_ADDRESS);
  Serial.begin (115200);
  Wire.onReceive (receiveEvent);
}  // end of setup

volatile boolean haveData = false;
volatile float fnum;
volatile long foo;

void loop() 
{
  if (haveData)
    {
    Serial.print ("Received fnum = ");
    Serial.println (fnum);  
    Serial.print ("Received foo = ");
    Serial.println (foo);  
    haveData = false;  
    }  // end if haveData

}  // end of loop

// called by interrupt service routine when incoming data arrives
void receiveEvent (int howMany)
 {
 if (howMany >= (sizeof fnum) + (sizeof foo))
   {
   I2C_readAnything (fnum);   
   I2C_readAnything (foo);   
   haveData = true;     
   }  // end if have enough data
 
 }  // end of receiveEvent

The template in the .h file will convert any data type (eg. float, int, long) into a pointer to a series of bytes and send (or receive) those one at a time via I2C.

Example output:

Received fnum = 6.52
Received foo = 410
Received fnum = 6.53
Received foo = 411
Received fnum = 6.55
Received foo = 412
Received fnum = 6.56
Received foo = 413
Received fnum = 6.58
Received foo = 414
Received fnum = 6.59
Received foo = 415
Received fnum = 6.61
Received foo = 416
Received fnum = 6.62
Received foo = 417
Received fnum = 6.64
Received foo = 418
Received fnum = 6.65
Received foo = 419
Received fnum = 6.67
Received foo = 420

Holy Crap, Ty for the info -I would have never expected a complete sketch tyvm :slight_smile: . Now i have to break it down to get the idea of how its works, then to incorporate into my increasingly growing sketch. thank you again :slight_smile:

I'm having trouble using I2C_Anything with Wire.requestFrom, and any help would be greatly appreciated.

I'd like the master to request data from the slave, but all my floats arrive as nan . I've tried sending individual bytes and it works. I'm confident in my wiring because I can get I2C_Anything to work as written in this post. There's something about how I'm using requestFrom which is breaking something!

Here are the files:

Master, an Uno which uses Wire.requestFrom to ask for the data

// Eric Rombokas
// Jan 3 2013
// Arduino is i2c master, arduIMU is i2c slave .  arduino dumps requested i2c data to serial
// Using gammon I2C_readAnything.h for translating I2C bytes to other data types 

#include <math.h>
#include <Wire.h>
#include "I2C_writeAnything.h"

const int SLAVE_ADDRESS = 10;

volatile float volatileYaw;

void setup() {
  Serial.begin(115200);
  Wire.begin(); // begin with no address specified joins the bus as a master
}

void loop() {
  
  delay(1000);  
  
  if (Wire.requestFrom(SLAVE_ADDRESS, 4, true) == 4) {
    I2C_readAnything(volatileYaw);
    Serial.println(volatileYaw);
  }  
 
 
}

And slave, which uses I2C_writeAnything in its requestEvent()

// Eric Rombokas
// Jan 3 2013
// ArduIMU runs as an i2c slave, querying freeIMU for yaw, pitch, roll when asked to by the i2c master
// Adapted from Fabio Varesano's FreeIMU_Yaw_pitch_roll RIP
// i2c float/byte stuff adapted from "WanaGo" on arduino forum

#include <ADXL345.h>
#include <bma180.h>
#include <HMC58X3.h>
#include <ITG3200.h>
#include <MS561101BA.h>
#include <I2Cdev.h>
#include <MPU60X0.h>
#include <EEPROM.h>

//#define DEBUG
#include "DebugUtils.h"
#include "CommunicationUtils.h"
#include "FreeIMU.h"
#include "I2C_writeAnything.h"
#include <Wire.h>
#include <SPI.h>
#include <math.h>

volatile float volatileYaw = 42.42;

const int I2CSlaveAddress = 10;

void setup() { 
 
  
  Wire.begin(I2CSlaveAddress);
  Wire.onRequest( requestEvent ) ; // declare function handle to be called when i2c master requests
  
}

void loop() { 
  

}

// called by onRequest interrupt
void requestEvent() {

  I2C_writeAnything(volatileYaw);
    
}

Any ideas?

Thanks,
Eric R

(edited to remove superfluous stuff)

An update. Sending multiple bytes at all during a requestEvent results in error.

I'm running on two Uno R3 arduinos.

Master uses requestFrom to ask for bytes. If I ask for one, it works, but if I ever ask for more than 1, they come back corrupted.

Master code:

#include <Wire.h>

int numReceived = 0;

void setup()
{
Wire.begin();        // join i2c bus (address optional for master)
Serial.begin(115200); 
}

void loop()
{
  numReceived = 0;
  Wire.requestFrom(2, 2);

  while(Wire.available())
  {
  byte c = Wire.read(); 
  Serial.print("Received: ");
  Serial.print(c);         
  Serial.println(" . ");
  numReceived++;
  }

  delay(500);
  Serial.print(" Received "); 
  Serial.print(numReceived);
  Serial.println(" bytes.  ");
}

Slave code:

#include <Wire.h>

byte val1 = 0;
byte val2 = 1;

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

void loop()
{
delay(100);
}

void requestEvent()
{
  Wire.write(val1); 
  Wire.write(val2);
}

Output is :

Received: 1 .
Received: 255 .
Received 2 bytes.

What is happening here?

-Eric

Try this:

byte vals[2];

void requestEvent()
{
  vals[0] = 14;
  vals[1] = 172;

  Wire.write(vals, 2);
}

Thanks for the reply!

It works correctly using the single call to Wire.write(). If I split it into two calls it fails:

eg

Wire.write(vals[0]);
Wire.write(vals[1]);

Produces

Received 2 bytes.
Received: 172 .
Received: 255 .

Is there something about the Wire library that means only a single write() should be used in an interrupt? I2C_writeAnything() uses multiple Wire.write() calls in a for-loop, so that would mean it can't be used in an onRequest handler.

Perhaps I could modify it to fill a byte array, then send using a single write() call, like this:

float someFloat = 1.2;
byte vals[4];

byte * p = (const byte*) someFloat;
unsigned int i;
for (i = 0; i < sizeof someFloat; i++)
  vals[i] = *p++;

Wire.write(vals, 4);

Or something like that?

-Eric

That worked.

I added the following function to I2C_Anything.h

template <typename T> int I2C_singleWriteAnything (const T& value) {
  int size = sizeof value;
  byte vals[size];
  const byte* p = (const byte*) &value;
  unsigned int i;
  for (i = 0; i < sizeof value; i++) {
    vals[i] = *p++;
  }
  
  Wire.write(vals, size);
  return i;
}

And it works perfectly, even in the onRequest handler.

Thanks 4.2 zillions!

Eric Rombokas

Thanks a lot! it works perfectly with the last addition. MasterWrite works great even with Due. But requestEvent is not working with Due :frowning: it always prints only "0.00", it doesn't matter if you connect a slave to the master..help please! =(

thannks for respon, :slight_smile:

Sorry to dig this thread back up after been dead for do long as I was searching how to send a float over I2C.
I've got the code working posted by Nick in post for just sending 2 example values of 6.75 (fnum) & 29.31(foo) and I would like to add another slave to send example values of 13.32(fnum) & 3.25(foo).
I know I would have to alter the MY_ADDRESS say to 43 but the part I'm not sure of is how to get the master to read more than one slave unit.

Is this possible ??
if so how could I do it

Steveiboy:
Sorry to dig this thread back up after been dead for do long as I was searching how to send a float over I2C.
I've got the code working posted by Nick in post for just sending 2 example values of 6.75 (fnum) & 29.31(foo) and I would like to add another slave to send example values of 13.32(fnum) & 3.25(foo).
I know I would have to alter the MY_ADDRESS say to 43 but the part I'm not sure of is how to get the master to read more than one slave unit.

Is this possible ??
if so how could I do it

Wire.requestFrom (SLAVE_ADDRESS,number_bytes,TRUE);

Controls which slave is being communicated with. Just create multiple request with different Slave Addresses. Of course you need 'slave' Arduinos with the same addresses.

Example Master Code Fragment

#define SLAVE_ADDRESS_1 0x58

#define SLAVE_ADDRESS_2 0x59

#define SLAVE_ADDRESS_3 0x5A

   float foo;
   long fnum;

   uint8_t datalen = (sizeof foo)+(sizeof fnum);

   // get info from the First Slave
   Wire.requestFrom (SLAVE_ADDRESS_1,datalen,true);
   if(Wire.available()==datalen){ // got correct size of packet
     I2C_readAnything (fnum);
     I2C_readAnything (foo);
     }
  
 // get info from the second Slave
   Wire.requestFrom (SLAVE_ADDRESS_2,datalen,true);
   if(Wire.available()==datalen){ // got correct size of packet
     I2C_readAnything (fnum);
     I2C_readAnything (foo);
     }
   // get info from the Third Slave
   Wire.requestFrom (SLAVE_ADDRESS_3,datalen,true);
   if(Wire.available()==datalen){ // got correct size of packet
     I2C_readAnything (fnum);
     I2C_readAnything (foo);
     }

The order you call I2C_readAnything(fnum), I2C_readAnything(foo) needs to match the order in the Slaves onRequest event handler.

Chuck.


Check out my Kickstarter Project Memory Panes an expansion RAM Shield for Mega2560's. It adds 1MB of RAM for those projects where 8KB is not enough.

Thanks chuck that should get me going, I will try it in the week

Once again for your support

I think I need to read up more, I've tried to add a second slave but it receives nothing from both salves
So I went back to basic with one salve
If I use this Wire.begin (SLAVE_ADDRESS_1); only with one slave it works
but if I just use this Wire.begin (); then nothing happens
tried to add this but same nothing comes through
Wire.begin (SLAVE_ADDRESS_1);
Wire.begin (SLAVE_ADDRESS_2);
This is what I've tried

void loop()
{
  uint8_t datalen = (sizeof foo) + (sizeof fnum); // get info from the First Slave
  Wire.requestFrom (SLAVE_ADDRESS_1, datalen, true);
  if (Wire.available() == datalen) { // got correct size of packet
    I2C_readAnything (fnum);
    I2C_readAnything (foo);
  }

  Wire.requestFrom (SLAVE_ADDRESS_2, datalen, true);
  if (Wire.available() == datalen) { // got correct size of packet
    I2C_readAnything (fnum);
    I2C_readAnything (foo);
  }



  if (haveData)
  {
    Serial.print ("Received 0x58 = ");
    Serial.println (fnum);
    Serial.print ("Received 0x58 = ");
    Serial.println (foo);
   Serial.print ("Received 0x59 = ");
    Serial.println (fnum);
    Serial.print ("Received 0x59 = ");
    Serial.println (foo);
  haveData = false;

  }  // end if haveData
}  // end of loop

The other thing I'm struggling to understand is how to display the 4 floats are displayed via the serial terminal as there are only one fnum and foo to send.

Steveiboy:
I think I need to read up more, I've tried to add a second slave but it receives nothing from both salves
So I went back to basic with one salve
If I use this Wire.begin (SLAVE_ADDRESS_1); only with one slave it works

The other thing I'm struggling to understand is how to display the 4 floats are displayed via the serial terminal as there are only one fnum and foo to send.

Steveiboy,

The Wire library has a problem. When the onRequestevent() is triggered (inside ISR), the code only transmits the last Wire.write() data. if your onRequestEvent() is like this:

void onRequestEvent(){
   Wire.write(1);
   Wire.write(2);
   Wire.write(3);
   Wire.write(4);
   Wire.write(5);
   Wire.write(6);
   Wire.write(7);
}

and the Master's code was

Wire.requestFrom(slave,7);
  while(Wire.available()){
     Serial.print(Wire.read(),HEX);
     Serial.print(' ');
     }

the Monitor would print:

7 FF FF FF FF FF FF

Not what you expected right?

I changed the code in my library to fix this and another Issue.

C:\Program Files\Arduino\hardware\arduino\avr\libraries\Wire\utility\twi.c

/* 
 * Function twi_transmit
 * Desc     fills slave tx buffer with data
 *          must be called in slave tx event callback
 * Input    data: pointer to byte array
 *          length: number of bytes in array
 * Output   1 length too long for buffer
 *          2 not slave transmitter
 *          0 ok
 */
uint8_t twi_transmit(const uint8_t* data, uint8_t length)
{
  uint8_t i;

  // ensure data will fit into buffer
  //if(TWI_BUFFER_LENGTH < length){ // original
  if(TWI_BUFFER_LENGTH < (length+twi_txBufferLength)){ //chuck 15JUN2015
    return 1;
  }
  
  // ensure we are currently a slave transmitter
  if(TWI_STX != twi_state){
    return 2;
  }
  
  // set length and copy data into tx buffer
  //twi_txBufferLength = length; original
  for(i = 0; i < length; ++i){ 
  //  twi_txBuffer[i] = data[i]; original
    twi_txBuffer[twi_txBufferLength++] = data[i]; //chuck 15Jun2015
    }
   return 0;
}

Appended are my update twi.c and two test Sketches that work.

Chuck.


Check out my Kickstarter Project Memory Panes an expansion RAM Shield for Mega2560's. It adds 1MB of RAM for those projects where 8KB is not enough.

twi.c (17.5 KB)

slave.ino (1.69 KB)

master.ino (3.01 KB)

I2C_anyThing.zip (547 Bytes)

Wow, Thanks chuck this is a massive help, I've compiled and upload I now can send and receive data from both slaves, I will also learn loads more by reading through your code and playing with it.
I shall now study and play around with the code.
Hopefully I can get it working. I see when I ground A0 it just receives data from slave one only I will try and get it to read both slaves and display both slaves with different values.
many thanks for your time and help
Steve

Steveiboy:
Hopefully I can get it working. I see when I ground A0 it just receives data from slave one only I will try and get it to read both slaves and display both slaves with different values.
many thanks for your time and help
Steve

Now for one more complication. You do realize that an arduino can be both a Master and a Slave at the same time?

just add the onRequest, onReceive events to the Master Sketch, change the Wire.begin() in the Master to Wire.begin(thirdslaveaddress);add the Wire.onReceive(),onRequest(). One more thing, put the volatile prefix on fnum, and foo. They will now be access in a ISR.

Now one proviso, The master CANNOT talk to itself successfully. if you put this new sketch into multiple Arduino's you could have them access each other.

If you added this complexity you would need to start monitoring the results from endTransmission(), requestFrom(). your transfers might get aborted if the transaction looses arbitration. you could no longer assume the master will always suceed.

multiple Master can be on the bus at the same time. Two masters can talk to the same slave at the same time. The master that wins is the one that transfers a byte with a low bit first.

Here is a good reference on the I2C buss Philips Electronics I2C Specs

Chuck.


Check out my Kickstarter Project Memory Panes an expansion RAM Shield for Mega2560's. It adds 1MB of RAM for those projects where 8KB is not enough.