Как записать переменную типа float в EEPROM

vvadim
Offline
Зарегистрирован: 23.05.2012

Не получается записать и прочитать переменную типа float  - например 3.5. Пытаюсь через промежуточную переменную типа int  умножая переменную типа float на 10, но причтении и делении на 10 теряются знаки после запятой. В библиотеке EEPROM с записью float не могу разобраться. Как правильно сделать?

int val;
float VAL;
val = VAL*10;

EEPROM.write(31, val);
delay(50);
///////////////

val = EEPROM.read(31);
VAL = val/10;

 

toc
Offline
Зарегистрирован: 09.02.2013

попробуйте так



VAL = val/10.0;

 

__Alexander
Offline
Зарегистрирован: 24.10.2012

изучите как храниться тип float в памяти, и записывайте его в явном виде.

vvadim
Offline
Зарегистрирован: 23.05.2012

Всё равно теряются десятки, например записываю val = 2.8*10 получаю val/10( или 10.0) =2.7 

__Alexander
Offline
Зарегистрирован: 24.10.2012

изучили?

vvadim
Offline
Зарегистрирован: 23.05.2012

Почитал. нашёл статейку http://dvo.sut.ru/libr/cvti/i618buz/2.htm

Но для чайника это больше ознакомительно, т.к. применить знаний не хватает.

Нашел пример maksima 

 

float val_1 = 30.12345;
float val_2 = 0;

// запись в ЕЕПРОМ
byte *x = (byte *)&val_1;
for(int i = 0; i < 4; i++) EEPROM.write(i, x[i]);

// чтение из ЕЕПРОМ
byte xx[4];
for(int i = 0; i < 4; i++) xx[i] = EEPROM.read(i);
float *y = (float*)&xx;
val_2 = y[0];

Но и здесь не знаю как правильно адресс записать

maksim
Offline
Зарегистрирован: 12.02.2012
#include <EEPROM.h>

void setup()
{ 
  float val_1 = 3000.12;
  float val_2 = 0;
  
  EEPROM_float_write(0, val_1);
  val_2 = EEPROM_float_read(0);

  // выводим в сериал
  Serial.begin(9600);
  Serial.println(val_2);
}

void loop(){}


void EEPROM_float_write(int addr, float val) // запись в ЕЕПРОМ
{  
  byte *x = (byte *)&val;
  for(byte i = 0; i < 4; i++) EEPROM.write(i+addr, x[i]);
}

float EEPROM_float_read(int addr) // чтение из ЕЕПРОМ
{    
  byte x[4];
  for(byte i = 0; i < 4; i++) x[i] = EEPROM.read(i+addr);
  float *y = (float *)&x;
  return y[0];
}

upd: не успел, опередили ) , только вынесено для удобства в функции и учитывайте что в памяти float занимает 4 байта , то есть если одну переменную записали по адресу 0, то следующая должна быть записана по адресу 4 иначе затрете предыдущую.

__Alexander
Offline
Зарегистрирован: 24.10.2012

о! vvadim это вы нашли правильный пример.

float занимает в памяти 4 байта. Итого берем указатель на него, и пишем в память четыре раза по байту. Назад в обратном порядке.

адрес как и у любого типа EEPROM.write(i, x[i]);

тут первая i это адрес.

можно и так записать

EEPROM.write(100, x[0]);
EEPROM.write(101, x[1]);
EEPROM.write(102, x[2]);
EEPROM.write(103, x[3]);

разместится float по адресам 100-103

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Дежавю. Вопрос пора в базу знаний :)

maksim
Offline
Зарегистрирован: 12.02.2012
vvadim
Offline
Зарегистрирован: 23.05.2012

Я уже смотрел и EEPROMEx , но как в код всё увязать - трудновато. Мне нужно сохранить четыре разных параметра  ну и потом прочитать их. Можно пример для наглядности как записать val_1 по адрессу например 10 и прочитать val_2

 

float val_1 = 30.12345;
float val_2 = 0;

// запись в ЕЕПРОМ
byte *x = (byte *)&val_1;
for(int i = 0; i < 4; i++) EEPROM.write(i, x[i]);

// чтение из ЕЕПРОМ
byte xx[4];
for(int i = 0; i < 4; i++) xx[i] = EEPROM.read(i);
float *y = (float*)&xx;
val_2 = y[0]; 

__Alexander
Offline
Зарегистрирован: 24.10.2012

вам же maksim даже функции дал. что сложного?

 EEPROM_float_write(10, val_1)  // тут пишем по адресу 10

val_2 = EEPROM_float_read(10) // тут читаем

только запомнить, что записывая по адресу 10, флоат будет лежать с 10 по 13. т.е. в 11 писать другую переменную нельзя.

 

vvadim
Offline
Зарегистрирован: 23.05.2012

Это хорошо, функции я видел. Их нужно использовать для каждой записи (адреса)?

И в примере первом maksima функций нет, мне как бы проще.

__Alexander
Offline
Зарегистрирован: 24.10.2012

ниче не понимаю. ну да ладно. ))

vvadim
Offline
Зарегистрирован: 23.05.2012

Всем СПАСИБО, вроде разобрался, буду топать дальше.

maksim
Offline
Зарегистрирован: 12.02.2012

Вы лучше напишите что хотите сделать в итоге.

vvadim
Offline
Зарегистрирован: 23.05.2012

Делаю себе меню и хочу параметры настраивать кнопками на дисплее (раньше крутил потенциометр). Несколько переменных визуально удобнее использовать в виде десятичной дроби, вот и возникла тема на форуме. Спасибо за помощь - немного разобрался.

demon969
Offline
Зарегистрирован: 24.04.2012

__Alexander пишет:

вам же maksim даже функции дал. что сложного?

 EEPROM_float_write(10, val_1)  // тут пишем по адресу 10

val_2 = EEPROM_float_read(10) // тут читаем

только запомнить, что записывая по адресу 10, флоат будет лежать с 10 по 13. т.е. в 11 писать другую переменную нельзя.

 

Подскажите пожалуйста почему компилятор не пропускает вот такую строчку

int addr = 14;

Неужели вышел за пределы памяти?

Ошибка: redefinition of int addr

 error: 'int addr' previously defined here

UPD: Разобрался. addr уже было определено!! Не внимательность)))))

mishapk
Offline
Зарегистрирован: 08.07.2014

Число́ одина́рной то́чности (англ. Single precision, Single) — широко распространенный компьютерный формат представления вещественных чисел, занимающий в памяти 32 бита (4 байта). Как правило, под ним понимают формат числа с плавающей запятой стандарта IEEE 754.








union u_fi
{
unsigned  char c[4];
 float f;
}
u_fi fi;
fi.f=-3.1415926;
//Тогда получаем в памяти 
fi.c[0]=0xDA; fi.c[1]=0x0f; fi.c[2]=0x49; fi.c[3]=0xC0;
//Их и записываем побайтно в память

Для извлечения данных





u_fi ft;
//Считаем по байтно и присвоим 
 ft.c[3]=0xC0;
 ft.c[2]=0x49;
 ft.c[1]=0x0f;
 ft.c[0]=0xda;
//Результат будет в 
ft.f=-3.141593

Сталкивался с этим в протоколах Modbus RTU.

axill
Offline
Зарегистрирован: 05.09.2011

зачем так извращаться?

вот тут нормальные (а главное стандартные) функции описаны http://www.nongnu.org/avr-libc/user-manual/group__avr__eeprom.html

к тому же можно не назначать самому адреса в EEPROM, а использовать атрибут EEMEM, тогда линкер автоматом распределит память

NeiroN
NeiroN аватар
Offline
Зарегистрирован: 15.06.2013

А работает EEMEM также как и PROGMEM и какие есть грабли?

axill
Offline
Зарегистрирован: 05.09.2011

Ну не совсем так же. С одной стороны и там и там линкер выделяет адреса под данные автоматически. И там и там для получения адреса переменной используется стандартный для Си оператор &. Но в отличии от PROGMEM EEMEM не програмирует автоматически данные в eeprom при прошивке. Данные в EEPROM все равно програмировать самому, для этого есть два способа. 1. Самый простой - при старте программы считывать eeprom и если там значения отдельных байт 0xff то это пустая ячейка и ее надо инициализировать. В случае если нормальные данные тоже могут иметь значения 0xff надо решать отдельно. Универсальное решение - разместить в eeprom специальную переменную типа uint8_t значение которой 255 если eeprom еще не инициализирован и 0 если уже инициализирован. 2. При использовании eemem линкер генерит файл .eep который содержит данные для прямого програмирования в eeprom програматором, но для ардуины это не поддерживается

граблей две: 1. Не возможно совмещать ручное размещение данных и автоматическое, либо так либо так 2. При добавлении новых переменных с атрибутом eemem не гарантируется, что адреса расположения в eeprom старых переменных останутся те же. Поэтому если делается заливка новой версии скетча с новыми eemem переменными надо специально позаботиться об инициализации и новых и старых переменных в eeprom

userAR
Offline
Зарегистрирован: 19.10.2014

maksim пишет:

#include <EEPROM.h>

void setup()
{ 
  float val_1 = 3000.12;
  float val_2 = 0;
  
  EEPROM_float_write(0, val_1);
  val_2 = EEPROM_float_read(0);

  // выводим в сериал
  Serial.begin(9600);
  Serial.println(val_2);
}

void loop(){}


void EEPROM_float_write(int addr, float val) // запись в ЕЕПРОМ
{  
  byte *x = (byte *)&val;
  for(byte i = 0; i < 4; i++) EEPROM.write(i+addr, x[i]);
}

float EEPROM_float_read(int addr) // чтение из ЕЕПРОМ
{    
  byte x[4];
  for(byte i = 0; i < 4; i++) x[i] = EEPROM.read(i+addr);
  float *y = (float *)&x;
  return y[0];
}

 

Подскажите, что необходимо изменить в коде выше чтобы он подходил под сохранение и чтение переменных типа unsigned long, ведь он тоже занимает 4 байта?

И прочтите мне пожалуйста строчку    float *y = (float *)&x;    человеческим языком.

userAR
Offline
Зарегистрирован: 19.10.2014

Ответ: ну собственно, ничего особо менять и не надо, только float везда в unsigned long. Вроде все рабртает.

maksim
Offline
Зарегистрирован: 12.02.2012
VanyambaElectronics
Offline
Зарегистрирован: 21.04.2017

Попробуйте использовать библиотеку VEduino (версии 0.20 и выше).

Пример использования:

Подключите потенциометр к выводу A0 платы Arduino UNO (или MEGA). При вращении потенциометра в Мониторе порта можно будет наблюдать текущее напряжение на входе A0.
Текущее значение можно записать в EEPROM по адресу 0x000 командой R. Командой W можно прочитать записанное значение.

#include <ve_avr.h>

float previousPot = 0.;

void setup() {
  Serial.begin(57600);
  Serial.print("Size of float: ");
  Serial.println(sizeof(float));
  Serial.print("Size of double: ");
  Serial.println(sizeof(double));
}

void loop() {
  float pot = 5.0 * analogRead(0) / 1023;
  if (fabs(pot - previousPot) > 0.01) {
    Serial.print("Pot: ");
    Serial.println(pot, 4);
    previousPot = pot;
  }
  if (Serial.available()) {
    float wPot;
    char cmd = Serial.read();
    switch(cmd) {
      case 'W':
      case 'w':
        Serial.print("Writing EEPROM... ");
        DEV_EEPROM[0] = pot;
        DEV_EEPROM[0] >> wPot;
        Serial.print(wPot, 4);
        if (wPot == pot)
          Serial.println(" OK");
        else
          Serial.println("  FAIL");
        break;
      case 'R':
      case 'r':
        Serial.print("Reading EEPROM... ");
        DEV_EEPROM[0] >> wPot;
        Serial.print(wPot, 4);
        Serial.println(" OK");
        break;
    }
  }
}