Casting Variables for I2C

Hi guys,
I am currently trying to transport two floats with approx 7 significant figures(to right of decimal) using i2C but am getting the errors. I was able to successfully transfer a string of chars over from an electron to an arduino, but an having issues with a float.

My Slave Electron complies fine but when I go to read my float from the serial on the Arduino Master, I am getting odd results. Any help would be appreciated! Here are samples of my code with the results:
Electron slave:

void setup() {
    Serial.begin(9600);
    Wire.begin(8); // i2c bus #8
    Wire.onRequest(requestEvent);  // register event
}

void loop() {
    delay(500);
    requestEvent();
}

void requestEvent() {
   
  // generate some simulated data
  float val_1 = 5566;// (random(200000) - 1000) / 100000.0;
  float val_2 = (random(200000) - 1000) / 100000.0;
  char* val_3 = NULL;
  if ((val_1 + val_2) >= 1) {
    val_3 = "EN \0";
  } else {
    val_3 = "DIS\0";
  }
  
  // transmit the simulated data
  Wire.write((char*)&val_1); // respond with message of 6 bytes
  Wire.write((char*)&val_2);
}

Arduino Master:

#include <Wire.h>

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

void loop() {
  // variables
  int packet_size = sizeof(float) + sizeof(float) + 4;
  //Serial.print("packetsz is ");  //mtp feedback
  //Serial.println(packet_size);  // mtp feedback
  // send READ request to slave
  Wire.requestFrom(8, packet_size);  // request 6 bytes from slave device #8
  int i = 0;
  char msg[32];
  float* val_1 = NULL;
  float* val_2 = NULL;
  char* status_msg = NULL;
  
  // flush data buffers
  i = 0;
  
  // collect all data from slave (note: this may fail if master reads faster than slave writes)
  while (Wire.available()) { // slave may send less than requested
    msg[i] = Wire.read(); // read msg packet byte by byte
    //Serial.println(msg[i]);  //mtp feedback
    i++;
    
    if (i >= packet_size) {
      break;
    }
  }
  
  // typecast the apppropiate sections on the packet
  val_1 = (float*)(&msg[0]);          // extract first float
  val_2 = (float*)(&msg[4]);          // extract second float
  status_msg = (char*)(&msg[8]);      // extract string of text at the end
  
  // perform all printouts
  Serial.print(*val_1);         
  Serial.print(", ");
  Serial.println(*val_2,10);
  //Serial.print(", ");
  //Serial.print(status_msg);
  //Serial.print("\n");

  delay(500);
}

Arduino Serial Result:

setup complete.
ovf, 0.7470588207
ovf, 0.7470588207
-0.77, 0.7470588207
-0.00, 0.7470588207

Endianness on the two platforms is different.
Try reversing the order of received bytes.

BTW, how would your Wire.write((char*)&val_1); know to send 6 bytes?
Iā€™d assume you only send one or till it encounters the first 0x00 byte unless you also add the length of the ā€œbufferā€ you provide.
https://docs.particle.io/reference/device-os/firmware/photon/#write--2

1 Like

How would you reverse the order of received bytes? (as well as what the syntax would be). I would normally include the number of bytes- 6 but it errors out. Also, would I need a ā€œBegintransmissionā€ statement or is this unneccessary?

This sounds similar to the problem Iā€™m experiencing. See my post at
https://community.particle.io/t/i2c-slave-mode-clock-stretching-intermittant-failure/48815/2

Sound similar?

Providing the error message would help.
But I'd assume it's something about incompatible datatypes and that would be solved via a acceptable type cast e.g. Wire.write((char*)&val_1, sizeof(val_1)); (previously wrongly suggested (uint8_t*))

1 Like

The error message would be:
invalid conversion from ā€˜char*ā€™ to ā€˜const uint8_t* {aka const unsigned char*}ā€™ [-fpermissive]
if the write method was:
Wire.write((char*)&val_1, sizeof(float));

Your typecast helped a ton! Is it possible to get one more significant digit after the decimal? I set my float to float val_1 = -11.08765432; and would like to at least get down to the 3.
I also changed my arduino output statement to
Serial.print(val_1,9);
and is now reading
-11.087654113
Perhaps I could change it from uint8_t
conversion to uint10_t* ? Does this exist?
Thanks.

With floating point variables you need to understand that you cannot store every arbitrary real number but only a subset that can be build by a finite number or a sequence of powers of 2 (similar to rational vs. real numbers).
Hence the result you see is actually best ā€œapproximationā€ of your discrete decimal number in that scheme.

You can have a try with this online converter
http://www.binaryconvert.com/result_float.html?decimal=045049049046048056055054053052049049051

Hi Scruff,
This last digit represents a location not just an arbitrary number. How would I get one more digit of resolution? Is there perhaps an alternate method to do this? Such as converting it to a string and the sending the string over i2C?

Using a string is one option, but eventually you want to perform some math operations and hence you'll either lose the precision eventually using float or go for double precision floating point double.
But if you are using any library to do the calculation and it happens to use float your extra precision will be gone after all.

BTW, have you calculated what the actual distance of an angular displacement of 0.000002 degrees makes? :wink:

It may not be arbitrary for you and your use case but as far the datatype float is concerned it is an arbitrary - not representable - number.

After I saw your last post, I actually did a cursory google search...angular displacement was a tiny distance. IE I believe I only need 5 significant figures. .... I also may end up doing most of the math on my Master, which I'm okay with. You've been super helpful on this project! Thanks. Mike

2 Likes

Hi there again,
I have some updates on my project as well as an update. I determined I just need to transmit my data in the form of a string not a float or double, but that still doesnā€™t help my project.

Iā€™ve run into another roadblock on this project. Somehow the above code isnā€™t working anymore. I was able to get i2c running from arduino to arduino without resistors, but this method somehow doesnā€™t work. Hereā€™s my slave-Electron and master-arduino code along with the physical configuration and output. My wire.available is not being invoked and is timing out(see code).

The eventual overall goal would be to have the string output on the serial after being saved to a variable from the Arduino. Any help would be awesome! Thanks. -Mike

Electron Slave:

void setup() {
    Serial.begin(9600);
    Wire.begin(8); // i2c bus #8
    Wire.onRequest(requestEvent);  // register event
}

void loop() {
    delay(500);
    requestEvent();
}

void requestEvent() {
   
  // generate some simulated data
  //float val_1 = 11.08765; //dummy values - do not change   (random(200000) - 1000) / 100000.0;
  //float val_2 =-66.01234; //dummy values - do not change   (random(200000) - 1000) / 100000.0;
  float val_1 = 42.409671;
  float val_2 = -71.09733;
  char* val_3 = NULL;
  if ((val_1 + val_2) >= 1) {
    val_3 = "EN \0";
  } else {
    val_3 = "DIS\0";
  }
  
  // transmit the simulated data
  //Wire.write((char*)&val_1); // respond with message of 6 bytes, SENDS CRAP DATA
  //Wire.write((uint8_t*)&val_1, 9); //TESTING // WORKS TO END OF -11.087654/113 NEED 1 MORE DIGIT
  Serial.print("val_1 is");Serial.println(val_1,5);
  if (Wire.isEnabled()) {
    Wire.begin(8);
    Serial.println("wire enabled");
}
  String sendstring1 = String(val_1);
  //Wire.write((uint8_t*)&val_1, 5);  // currently trying to send as data. 4/28/19
  Wire.write(sendstring1);  //testing sending as a string 4/28/19
  
  //Wire.write((uint8_t*)&val_2, sizeof(val_2));// testing 4/27/19 commented to isolate
  //Wire.write((char*)&val_1, sizeof(float)); //THIS ERRORED FROM JOSH
}

Arduino Uno Master:

#include <Console.h> // to communicate over wifi
#include <Bridge.h>
#include <Process.h> //for gettime function
#include <Wire.h>
//int rxpin = 0;
float data =1.321;
String val_1str = "";
/*
On the Photon, Electron, P1 and Core, the I2C interface (Wire) is on D0 and D1:

DO: SDA
D1: SCL
*/

void setup() {
   // Initialize the Bridge and the Serial
  //Bridge.begin();
  //Console.begin();
  Serial.begin(9600);
  Wire.begin();  // join i2c bus (address optional for master)
  pinMode(13, OUTPUT);
  //while (!Console);
  while (!Serial);
  Serial.println("setup complete.*>>");

}

void loop() {
  // variables
  int packet_size = sizeof(float);

  Serial.print("packetsz is ");  //mtp feedback
  Serial.println(packet_size);  // mtp feedback
  // send READ request to slave
  Wire.requestFrom(8, packet_size);  // request 6 bytes from slave device #8
  int i = 0;
  char msg[32];
  float val_1 = 0.0;
  float val_2 = 0.0;
  char* status_msg = NULL;
  
  // JOSH: Added this in b/c maybe it's not getting all the data we need.
  unsigned long t_request = millis();
  while (i < packet_size) {
    
    // timeout will be performed if slave takes too long to transmit data
    if ((millis() - t_request) > 1000) {
      Serial.println("TIMEOUT OCCURED!!!");
      break;
    }
  
    // collect all data from slave (note: this may fail if master reads faster than slave writes)
    while (Wire.available()) { // slave may send less than requested
      msg[i] = Wire.read(); // read msg packet byte by byte
      Serial.println(msg[i]);  //mtp feedback
      i++;
      
      if (i >= packet_size) {
        break;
      }
    }
  }
    
  // another print to figure out how long it took to get all the data
  Serial.print("Time taken to receive data from slave: ");
  Serial.print((millis() - t_request)/1000.0, 2);
  Serial.println(" seconds");
  
  // more prints
  Serial.print("Number of bytes read from last transmission: "); // checking if this is less than 4.
  Serial.println(i, DEC);
  
  // JOSH: Realizing that this is a messy way to convert byte array to a float 
    // typecast the apppropiate sections on the packet
    //val_1 = (float*)(&msg[0]);          // extract first float
    //val_2 = (float*)(&msg[4]);          // extract second float
    //status_msg = (char*)(&msg[8]);      // extract string of text at the end
    
  // JOSH: This should also work
    memcpy(&val_1, &msg[0], sizeof(float));
    //memcpy(&val_2, &msg[4], sizeof(float));
    //memcpy(status_msg, &msg[8], 4);
  
  // perform all printouts
  Serial.print("value 1 is ");
  Serial.print(val_1,5);    //testing 4/27/19     
  Serial.print(", ");
  //Serial.println(val_2,5);  //testing 4/27/19    
  val_1str = String(data);
  Serial.println("val1str is "+val_1str);
  //Serial.print(", ");
  //Serial.print(status_msg);
  //Serial.print("\n");

  delay(2500);
}

and my output is:

setup complete.*>>
packetsz is 4
TIMEOUT OCCURED!!!
Time taken to receive data from slave: 1.00 seconds
Number of bytes read from last transmission: 0
value 1 is 0.00000, val1str is 1.32
packetsz is 4
TIMEOUT OCCURED!!!
Time taken to receive data from slave: 1.00 seconds
Number of bytes read from last transmission: 0
value 1 is 0.00000, val1str is 1.32
packetsz is 4
TIMEOUT OCCURED!!!
Time taken to receive data from slave: 1.00 seconds
Number of bytes read from last transmission: 0
value 1 is 0.00000, val1str is 1.32

The physical setup of the I2C interface on the Arduinos is different to the one on the Electron. Just because you don't need pull-ups between two Arduinos does not warrant the assertion that you won't need them for the Electron.

Is this setup more like it?

Using that setup, I get some interesting feedback. I am still timing out See this:
setup complete.*>>
packetsz is 4
TIMEOUT OCCURED!!!
Time taken to receive data from slave: 1.00 seconds
Number of bytes read from last transmission: 0
value 1 is nan, val1str is 1.32
packetsz is 4
āø®
āø®
āø®
āø®
Time taken to receive data from slave: 0.00 seconds
Number of bytes read from last transmission: 4
value 1 is nan, val1str is 1.32
packetsz is 4

Also, I am seeing some odd LED behavior- when I have the electron and arduino powered on, (regardless of USB or battery) my led changes from breathing cyan to blinking red, indicating a fault I believe. it then goes to breathing Cyan and then blinking green. I then hit reset and it goes back to breathing cyan. However I am not seeing a handshake on the Particle Console. I normally see this handshake even when Iā€™m not publishing anything, and power cycle the electron.
This is all while using the latest code I posted above.

Why am I also seeing value 1 to be nan? ScruffR, help would be awesome. Thx.

I guess the Arduion works with 5V on the I2C port, hence the Electron side should also be pulled up to 5V.
You should - most importantly - have common GND between your Arduino and Electron.

And what about the odd LED behavior?

That might be a side effect of either having 5V from the Arduinos pull-ups connected via the your 10k resistors to 3v3 or some issue with the ā€œfloatingā€ GND rail - or something else that may only be found after the other issues are gone.

Itā€™s also of interest what kind of blinking red there is (e.g. SOS + x slow blinks where x is of interest).

If I remember right ā€¦ you can pull the I2C to 3v3 and the UNO should still deal with it ok, There must be a common gnd or there will be all sorts of odd issues!

What I was trying to get across was: If the Arduino side does pull the I2C lines up to 5V it might be problematic to feed that 5V (or what's "left" of that after the serial resistors) into the 3v3 pin.

Like this?

We are also back to value 1 = 0.00000, but still not the value desiredā€¦ my LED status light is now continuously blinking green, tried getting it into safe mode but never found the cloud where it is in a good physical location to find it. Sometimes it oscillates fast cyan and fast green. Thoughts?

Im also thinking my ground wire is not connected properlyā€¦ trying not to get too frustratedā€¦

Hi guys I am still having issues and am hoping you can help. I rewired my breadboard, with a common ground going to the arduino. First question, is this ground correct? Here is an updated picture of my set up:


Second question, and hopefully this isnā€™t a double edged sword. Sometimes my electron will boot up, breathe cyan, and then turn solid cyan. Iā€™ve read this is due to errors, but Iā€™m not sure why this is happening?
Third set of questions. I am noticing my electron status change sometimes when I power on my arduino. This is fairly odd since I canā€™t reliably replicate this. On a couple of occasions, I noticed my arduino code is also getting hung up on Wire.requestFrom(0x8, 16); Iā€™ve tried changing the decimal address to hex, different number of bytes requested, as well as including and excluding the optional arguments. Any help would be greatly appreciated! My arduino hangs after printing out ā€œBrā€.

Updated Arduino Master Code:

#include <Console.h> // to communicate over wifi
#include <Bridge.h>
#include <Process.h> //for gettime function
#include <Wire.h>
//int rxpin = 0;
float data =1.321;
String val_1str = "";
/*
On the Photon, Electron, P1 and Core, the I2C interface (Wire) is on D0 and D1:

DO: SDA
D1: SCL
*/
byte incomingb =100;
byte incomingc =200;
void setup() {
   // Initialize the Bridge and the Serial
  //Bridge.begin();
  //Console.begin();
  Serial.begin(9600);
  Wire.begin();  // join i2c bus (address optional for master)

  Serial.println("setup complete.*>>");

}

void loop() {
  // variables
//  incomingb= digitalRead(A4);
//  Serial.println(incomingb);
//  incomingc = analogRead(A4);
//  Serial.println(incomingc);
  int packet_size = 4; //sizeof(float);

  Serial.print("packetsz is ");  //mtp feedback
  Serial.println(packet_size);  // mtp feedback
  // send READ request to slave
  Serial.println("Br");
  delay(1);
  Wire.requestFrom(0x8, 16);  // request 6 bytes from slave device #8 , third arg is true
  //Wire.requestFrom(8, packet_size);  // request 6 bytes from slave device #8Serial.println("ARRRRR");
  Serial.println("AFTERRRR");
  int i = 0;
  char msg[32];
  float val_1 = 0.0;
  float val_2 = 0.0;
  //char* status_msg = NULL;
  
  // JOSH: Added this in b/c maybe it's not getting all the data we need.
  unsigned long t_request = millis();
//  while (i < packet_size) {
//    Serial.print(i);
//    // timeout will be performed if slave takes too long to transmit data
//    if ((millis() - t_request) > 1000) {
//      Serial.println("TIMEOUT OCCURED!!!");
//      break;
//    }
  
    // collect all data from slave (note: this may fail if master reads faster than slave writes)
    while (Wire.available()) { // slave may send less than requested
      msg[i] = Wire.read(); // read msg packet byte by byte
      Serial.println(msg[i]);  //mtp feedback
      i++;
      Serial.println(i);
      //if (i >= packet_size) {
      //  break;
      //}
    }
//  }
    
  // another print to figure out how long it took to get all the data
  Serial.print("Time taken to receive data from slave: ");
  Serial.print((millis() - t_request)/1000.0, 2);
  Serial.println(" seconds");
  
  // more prints
  Serial.print("Number of bytes read from last transmission: "); // checking if this is less than 4.
  Serial.println(i, DEC);
  
  // JOSH: Realizing that this is a messy way to convert byte array to a float :P
    // typecast the apppropiate sections on the packet
    //val_1 = (float*)(&msg[0]);          // extract first float
    //val_2 = (float*)(&msg[4]);          // extract second float
    //status_msg = (char*)(&msg[8]);      // extract string of text at the end
    
  // JOSH: This should also work
    memcpy(&val_1, &msg[0], sizeof(float));
    //memcpy(&val_2, &msg[4], sizeof(float));
    //memcpy(status_msg, &msg[8], 4);
  
  // perform all printouts
  Serial.print("value 1 is ");
  Serial.print(val_1,5);    //testing 4/27/19     
  Serial.print(", ");
  //Serial.println(val_2,5);  //testing 4/27/19    
  val_1str = String(data);
  Serial.println("val1str is "+val_1str);
  //Serial.print(", ");
  //Serial.print(status_msg);
  //Serial.print("\n");

  delay(2500);
}