Библиотека EasyTransferI2C - потеря точности в типе float. Как забороть?

Тема в разделе "Arduino & Shields", создана пользователем Zorg_79, 1 апр 2014.

  1. Zorg_79

    Zorg_79 Нуб

    доброго дня.

    Решил я таки не изобретать велосипед и использовать библиотеку EasyTransferI2C.

    Слегка модифицировал примеры.

    Код передатчика:
    Код (Text):

    #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);
    }
     

    Код приемника:
    Код (Text):

    #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) {}
     
    Все компилируется и стартует. Смущает вот что:

    Со стороны передатчика : mydata.test = -180.123451;

    А со стороны применика на ЖК отображается -180.123443 т.е. потеря точности в последних двух знаках.

    Пробовал разные числа - картинка примерно одна и тажа - потеря точности в последних 2х - 3х знаказ после запятой.

    Как это можно забороть?
    Мне критически важно точное соответствие всех 6 знаков после запятой. А то уплыву не туда :)

    Спасибо.
     
  2. Tomasina

    Tomasina Сушитель лампочек Модератор

    передавай по две части: отдельно до точки, отдельно после точки. На приемнике собирай обратно.

    Тут тебе тоже ответили в том же духе.
     
  3. Zorg_79

    Zorg_79 Нуб

    Уже прочитал :) Тогда прошу ткнуть носом в метод разделения целой и дробной части.
    И в каком формате их передавать?

    Заменил сейчас в примерах на int все типы - в итоге вместо -180 получаю ерунду....

    На стороне передатчика - тестовая переменная -180 целое значение....
     
  4. Zorg_79

    Zorg_79 Нуб

    1550104015004 вот что получается :) Даже не знаю откуда такое может быть????
     
  5. Zorg_79

    Zorg_79 Нуб

    А, с этим разобрался.

    Ошибка в выводе на ЖК была.
    Нельзя переменную int выводить как float, т.е. конструкция вида lcd.print(mydata.test,6); выдает ерунду если test типа int.
     
  6. Tomasina

    Tomasina Сушитель лампочек Модератор

    код примерно такой (проверить сейчас негде):

    На передатчике:
    Код (Text):
    signed float x; // данные с сенсора
    x = ...                      // получаем данные
    unsigned int a = (int) x;    // выделяем целую часть
    unsigned int b = (x - a) * 1000000;    // выделяем дробную часть с точностью 6 знаков после запятой
    if(x < 0) a = a + 1000;      // если исходное число отрицательное, то добавляем "флаг" - 1000
    ... // тут как-то передаем оба числа
    На приемнике:
    Код (Text):
    char dataA, dataB; // данные с приемника (или int, я не знаю в каком виде ты передаешь данные - как строку символов или как число)
    const byte precision = 6; // число знаков после запятой
    signed float x;
    unsigned int a;
    unsigned int b;
    dataA = ... // тут получаем от приемника значения двух переменных
    dataB = ...;
    a = dataA - '0'; // если они текстовые - преобразуем в число
    b = dataB - '0';
    if(a > 1000) a = -1*a - 1000;      // если полученное число больше 1000, значит оно должно быть орицательным - преобразуем
    x = a + (b / 1000000);                      // формируем полное число
     
  7. Zorg_79

    Zorg_79 Нуб

    Tomasina спасибо. Я решил пойти этим же путем, но передавать с помощью библиотеки.

    написал вот такой код для проверки:

    Код (Text):

    void setup(){

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

    }

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


    val=int(a); //целая часть


    if(val >= 0) //разбираю на дробную часть с учетом знака
    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); //собранное обратно

    }
     
    Проблемы:
    1. Потеря точности в младших двух знаках. Вместо -278.789456 выдается на жк: Целая часть: -278. Дробная часть: 789459

    2. неправильно собирается обратно: -277,210540

    При подсчете на калькуляторе по той же формуле что в программе цифра тоже совпадает - т.е. ошибка не в цифрах а в методологии, в формуле....
     
  8. Tomasina

    Tomasina Сушитель лампочек Модератор

    а размерность int - 65535, происходит переполнение.
    Может в этом причина?

    Попробуй long
     
  9. Zorg_79

    Zorg_79 Нуб

    Так ведь для дробной части переменная frac и имеет размерность double.... Смущает то что сразу как бы неверно записывается в переменную... я же на ЖК сразу вижу неверную переменную а=-278.789459 вместо -278.789456 еще до того как начинею прочие манипуляции....
     
  10. Unixon

    Unixon Оракул Модератор

    А почему просто массивом байт не передаете?
     
  11. Zorg_79

    Zorg_79 Нуб

    потому что библиотекой удобнее. Вопрос сейчас не в способе передачи а в потере точности....
     
  12. Unixon

    Unixon Оракул Модератор

    Кстати, глянул код библиотеки. Она же внутри себя все побайтово и передает. Откуда там потеря точности может взяться?
     
  13. Zorg_79

    Zorg_79 Нуб

    Я же написал - сразу выводится неверно.
    я же на ЖК сразу вижу неверную переменную а=-278.789459 вместо -278.789456 еще до того как начинею прочие манипуляции....

    причем - всегда последних два разряда искажается....
    Я не думаю что это проблема в библиотеке. Но в чем еще может быть????
     
  14. Unixon

    Unixon Оракул Модератор

    Нужно полный формат вывода включить. У вас похоже закосячивается число при приведении его к строковому виду внутри print().
     
  15. Zorg_79

    Zorg_79 Нуб

    Вот я и сломал весь мозг себе - что это - глюк вывода или все же глюк с хранением данных.... А как его включить то - полный формат этот. Я пробывал больше знаков после запятой выводить - то же самое. Т.е. два знака неверных и потом -нули... т.е вот так -278.78945900
     
  16. Unixon

    Unixon Оракул Модератор

    Ну отличить то это элементарно - нужно сравнить побайтовое представление структуры в памяти до и после передачи.

    Попробуйте воспользоваться sprintf(str,"%e",val);
     
  17. geher

    geher Гуру

    Плавающая точка по жизни с погрешностями считается.
    Во-первых, попробуйте вместо 1000000 использовать 1000000.0 или при вычитании домножить целое на 1.0. Помнится, в некоторых компиляторах влияло на результат, иногда сильно влияло. Это к вопросу о точности в последних знаках.
    Во-вторых, в случае отрицательного числа дробная часть при подобных вычислениях получается положительной. В результате при сборке она прибавляется к отрицательной целой части, что приводит к неправильному результату. Тут тоже нужно учитывать знак числа.
    Третье, вычисления будут неадекватны, если число отрицательное, но целое равно нулю.

    Можно использовать такой вариант.

    Код (Text):
    int absval;
    double absa;
    val=int(a)
    if (a>=0)
    {
      frac=int(a-(val*1.0)) * 1000000;
    }
    else
    {
      frac=-int((-a)-((-val)*1.0))* 1000000;
    }

    //Сборка:
    q=val+(frac*0.000001);
     
    В этом случае знак дробной части будет совпадать со знаком целой части. В результате при сложении получится то, что надо.
     
    Последнее редактирование: 1 апр 2014
  18. Zorg_79

    Zorg_79 Нуб

    Спасибо за идею geher !
    Изменил код:
    Код (Text):
    #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);


    }
     
    Но проблема с потерей точности в последних разрядах не забарывается... Т.е. вместо -180.123456 вижу на ЖК -180,123458 сразу, до разборки. И в порт посылаю - то же самое....
    И принудительно нули присваиваю до присвоения значения- та же фигня....

    Попробовал уже даже в другой чип залить - думал аппаратный глюк - нет. То же самое.
     
  19. Unixon

    Unixon Оракул Модератор

    Смотрите на внутренности функций Serial.print() и lcd.print(), больше различию возникать негде.
     
  20. Zorg_79

    Zorg_79 Нуб

    Увы, не настолько я крут в программерском колдунстве... Что бы внутренности функций разобрать....

    А есть ли альтернативный способ вывода?

    Т.е. например побайтно прочитать эту переменную и байты вывести на ЖК или в порт. А уж проверить - я и в ручную востановлю....