EasyTransferI2C library lost data precision in float type. Why?

Hello again.

now i try a EayTransfer i2c library for my communication board to board.

I just modify samples codes

TX side:

#include <Wire.h>
#include <EasyTransferI2C.h>
const int analogInPin = A0; 
int sensorValue = 0;        
float outputValue = 0;   
//create object
EasyTransferI2C ET; 

struct SEND_DATA_STRUCTURE{
  //put your variable definitions here for the data you want to send
  //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO
  int blinks;
  int pause;
  float test;
  float out;
};

//give a name to the group of data
SEND_DATA_STRUCTURE mydata;

//define slave i2c address
#define I2C_SLAVE_ADDRESS 9

void setup(){

  Wire.begin();
  //start the library, pass in the data details and the name of the serial port. Can be Serial, Serial1, Serial2, etc.
  ET.begin(details(mydata), &Wire);
  
  pinMode(9, OUTPUT);
  
  randomSeed(analogRead(0));
}

void loop(){

  sensorValue = analogRead(analogInPin);            
   outputValue = map(sensorValue, 0, 1023, -100, 100);  
  
  //this is how you access the variables. [name of the group].[variable name]
  mydata.blinks = random(5);
  mydata.pause = random(5);
  mydata.test = -180.123451;
  mydata.out = outputValue;  
  //send the data


  ET.sendData(I2C_SLAVE_ADDRESS);
 
  
 /* //Just for fun, we will blink it out too
   for(int i = mydata.blinks; i>0; i--){
      digitalWrite(9, HIGH);
      delay(mydata.pause * 100);
      digitalWrite(9, LOW);
      delay(mydata.pause * 100);
    }
  */
  delay(500);
}

RX side:

#include <Wire.h>
#include <EasyTransferI2C.h>
#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(10, 4, 5, 6, 7, 8);
//create object
EasyTransferI2C ET; 

struct RECEIVE_DATA_STRUCTURE{
  //put your variable definitions here for the data you want to receive
  //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO
  int blinks;
  int pause;
  float test;
  float out;
};

//give a name to the group of data
RECEIVE_DATA_STRUCTURE mydata;

//define slave i2c address
#define I2C_SLAVE_ADDRESS 9

void setup(){
  lcd.begin(16, 4);
  Wire.begin(I2C_SLAVE_ADDRESS);
  //start the library, pass in the data details and the name of the serial port. Can be Serial, Serial1, Serial2, etc. 
  ET.begin(details(mydata), &Wire);
  //define handler function on receiving data
  Wire.onReceive(receive);
  
  pinMode(9, OUTPUT);
  
}

void loop() {
  //check and see if a data packet has come in. 
  if(ET.receiveData()){
    lcd.setCursor(0,1);
    lcd.print(mydata.test,6);
    lcd.setCursor(0,0);
    lcd.print(mydata.out,6);
    //this is how you access the variables. [name of the group].[variable name]
    //since we have data, we will blink it out. 
    /*for(int i = mydata.blinks; i>0; i--){
      digitalWrite(9, HIGH);
      delay(mydata.pause * 100);
      digitalWrite(9, LOW);
      delay(mydata.pause * 100);
    }*/
  }
}

void receive(int numBytes) {}

So. I have mydata.test = -180.123451; on TX side but on RX side i see on LCD -180.123443
I try different test data, but every time i see lost of precision in last two or three sign.

How i can fix this trouble?

I need six signs after decimal points without any mismatch. This is need to me form transmit latitude and longitude in decimal format…

thanks.
Sorry my bad english

So. I have mydata.test = -180.123451; on TX side but on RX side i see on LCD -180.123443
I try different test data, but every time i see lost of precision in last two or three sign.

Don't blame the library, it's what the chosen type offers in precision. A float uses 4 bytes to store the information, so you get about 7-8 decimal digits resolution, in total not just after the decimal point. With 3 digits in front of the point you get 4 to 5 digits after the point maximal precision. Try printing the input value on the sending side and you'll get the same output. If you need a higher precision, don't use floats (double translates to floats on the Arduino, so you cannot use them too). I'd suggest to use fixed point numbers with either 32 or 64 bit so you get the precision you want.

So. I try code for extracting integer and fraction part of my data.

//#include <math.h> 
#include <Wire.h>

#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(10, 4, 5, 6, 7, 8);




void setup(){

 lcd.begin(16, 4);
 pinMode(9, OUTPUT);
 Serial.begin(9600) ;
 
}

void loop() 
{
double a = -278.789456; // test value
int val=0;
long frac=0;
double q=0;

val=int(a); //integer part


    if(val >= 0) //fraction part
        frac = (a - int(a)) * 1000000;
    else
        frac = (int(a)- a ) * 1000000;


q=(frac*0.000001)+val;

lcd.setCursor(0,0);
lcd.print(a,6);
lcd.setCursor(0,1);
lcd.print(val);
lcd.setCursor(0,2);
lcd.print(frac);
lcd.setCursor(0,3);
lcd.print(q,6);


}

I have errors:

  1. Lost precision in fraction part.
    data is -278.789456
    On LCD i see -278.789459
    integer part -278 it’s ok
    fraction part is 789459 - wrong

  2. Incorrect recovery from integer and fraction part.
    I have -277.210540 on LCD
    I think - my recovery formula - not correct.

Please help with this troubles.

Thanks

So. I try code for extracting integer and fraction part of my data.

That doesn't help. The float data type doesn't have the precision you want. It's not the transfer, it's the data type.

Where do you get that precise data from? As soon as you store it in a float you loose precision and you cannot recover afterwards with some integer pseudo-magic.

I need this precision for translate over i2c GPS data: longitude, latitude, course, speed...

In TinyGPS++ library this data have a double data type

gps.location.lat(); // Latitude in degrees (double) as example....

So i need minimum six signs precision...

I fix my code

#include <Wire.h>

#include <LiquidCrystal.h>
LiquidCrystal lcd(10, 4, 5, 6, 7, 8);

void setup(){

  lcd.begin(16, 4);
  pinMode(9, OUTPUT);
  Serial.begin(9600) ;

}

void loop() 
{
  double a = 0.00000000;
  int val=0;
  long frac=0;
  double q=0;

  a=-180.123456;

  Serial.println(a,8);


  val=int(a);

  //????????
  if(val >= 0)
    frac = (a - int(a)) * 1000000;
  else
    frac = (int(a)- a ) * 1000000;

  //?????? 
  if (val>=0)
    q=(frac*0.000001)+val;
  else
    q=val+(-1*(frac*0.000001));


  lcd.setCursor(0,0);
  lcd.print(a,6);
  lcd.setCursor(0,1);
  lcd.print(val);
  lcd.setCursor(0,2);
  lcd.print(frac);
  lcd.setCursor(0,3);
  lcd.print(q,6);
}

Now it’s working good. But i don’t have any idea how to fix precision lost in last signs…
I have test data: -180.123456
But i see on LCD: -180.123458 - before disassembly
-180 - integer part - correct
123458 - factional part - wong
-180.123458 - assembled data - assembly correct, but fractional part wrong.

Any idea how i can fix that? I really need six signs precision…

What Arduino are you using? On 8bit AVRs the “double” type is identical with the “float” type, so your result is expected. BTW: your way to extract integers out of the floating point types will never give you an increased precision, the contrary is the case. You will always loose precision that way.

Create your own data structures then and send them. As mentioned, floats are too inaccurate for your purposes. This means you'll have to write any of the math functions yourself, since you can never convert the whole structure to floats (but maybe the fraction). However, since you know that the number has to be between -360 and 360, you can separate out the integer to be one variable and the fraction to be another variable.

In TinyGPS++ library this data have a double data type

Here is Bill's write-up on the EasyTransfer library;

Attention should be given to how he uses structures.

Simply create a struct to hold all the GPS information you want... send it across binary. Of course, you could convert everything to the String class and send it that way... but, lots more gyration.

Ray