Go Down

Topic: Write negative floats to EEPROM. (Read 5228 times) previous topic - next topic

captainhooyah

Sep 16, 2013, 11:05 pm Last Edit: Sep 16, 2013, 11:14 pm by captainhooyah Reason: 1
Hello everyone, this morning I was trying to write a calibration to EEPROM so that my project would stay calibrated after I switched it off. 

I read some forum posts, most of which pointed to:
http://playground.arduino.cc/Code/EEPROMWriteAnything

I couldn't figure it out.  I'm sure it's easy for people better at C++ than me, but I am unfamiliar with the "struct" and after reading into it a little bit I just decided to come up with my own solution. 

I was hoping someone could look at what I was doing and show me how to do it more efficiently using the EEPROMWriteAnything library, or any other technique that you think is cleaner.

This is what I came up with first that gave me problems.  Check it out, and if you know what is happening let me know.

Code: [Select]



#include <EEPROM.h>

int x, addr = 0;
float adj,EEPread;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  adj=-.12;//this is the adjustment I am tring to save to EEPROM

  int val =(adj * 100)+x;
  //I multiply it by 100 to make it easier to work with.
  //adj will always be less that .99 so I thought I would
  //have no problem with this.  I add x just to see a change over time
  //to see how it works. 

  Serial.print("val (what I am trying to save to EEPROM: )");
  Serial.println(val);

  // write the value to the appropriate byte of the EEPROM.
  // these values will remain there when the board is
  // turned off.

  EEPROM.write(0, val);
  EEPread = EEPROM.read(0);
  Serial.print("EEPROM position: ");
  Serial.print(0);
  Serial.print(" contains ");
  EEPread = EEPread;
  Serial.println(EEPread);
  delay(25);

  x++;

  delay(5000);
}


Here is my solution to the problem.

Code: [Select]

/*
* Write adjustment to EEPROM to retain calibration after power is switched.
* adj can be negative or positive.  Theoretically should never be more than .99
* Was having issues storing a negative float.  This is my work around.
*/

#include <EEPROM.h>

int neg, x;
float adj, EEPread;

void setup()
{
  Serial.begin(9600);
}

void loop()
{

  //You only get to write to EEPROM 100,000 times... this stops it from writing over and over.
  //An EEPROM.write saved is a EEPROM.write earned ;)
  if (x <= 2)
  {
    adj = -.12;//this is the adjustment I am trying to save to EEPROM

    //If adj is less than zero (negative) EEPwrite 1 to memory address 0 as a flag
    //for future processing. Then multiply adj * -1.0 to make a positive number for easy
    //storage.

    if (adj < 0)
    {
      EEPROM.write(0, 1);
      adj = adj * -1.0;
      Serial.println("removed negative sign adj: ");
      Serial.println(adj);
    }


    //Otherwise just EEPwrite a 0 to the 0 address for future processing.
    else
    {
      EEPROM.write(0, 0);
      delay(25); //Is it a good idea to put these delays in?
    }


    //I multiply adj by 100 to make it easier to work with.
    //in my situation adj will always be less than .99 . 

    int val = (adj * 100);

    Serial.println("val (what I am trying to save to EEPROM: )");
    Serial.println(val);

    // write the value to the appropriate byte of the EEPROM.
    // these values will remain there when the board is
    // turned off.

    EEPROM.write(1, val);
    delay(25);


    EEPread = EEPROM.read(1);
    neg = EEPROM.read(0);

    Serial.println("neg:");
    Serial.println(neg);

    //If neg == 1 then that means the number was originally a negative,
    //this if statement will make it a negative again.
    if ( neg == 1)
    {   
      EEPread = EEPread * -1.0;
    }

    Serial.print("EEPROM position: ");
    Serial.print(1);
    Serial.print(" contains ");
    Serial.println(EEPread);
    Serial.print("Now multiply it by .01 to return it to the original number:\n");
    EEPread = EEPread * .01;
    Serial.println(EEPread);
    delay(25);
    delay(5000);
    Serial.print("\n\n\n");
    x++;
  }
}



Hope my solution helps someone that can't figure out the EEPROMWriteAnything library.

hugo007

#1
Sep 16, 2013, 11:28 pm Last Edit: Sep 16, 2013, 11:34 pm by HugoPT Reason: 1
A float are 4 bytes.Since you need to store them in eeprom you could get rived of eeprom class and use the AVR libc directly(It is what eeprom class does anyway ..)
This is because you have a usefull  function in avrlibc  eeprom_write_dword() which takes two parameters , the actual adress where you want to store and the actual data to be stored, in this case 2 double words which are 4 bytes.
So basicly you could use this function to write to the eeprom the float at one instruction and retriveted back when you want using  eeprom_read_dword()
Here is an example how to write a float to the eeprom and then get it back from there.

Code: [Select]

#include <avr/eeprom.h>
float myValue = 1234.2f;
float myReadingValues;


void setup()
{
Serial.begin(9600);
eeprom_write_dword((uint32_t *)0, myValue);

Serial.println("Retrived back the values from eeprom");
myReadingValues =  eeprom_read_dword(0);

Serial.println("The value is : ");
Serial.println(myReadingValues);
Serial.println("Done");
}
void loop()
{

}

I haven't test the code but it should be something like this ...
Debian,Mint,Ubuntu
Arduino Mega 2560
Arduino Nano
Arduino Duemilanove
MAC OS Montain Lion
Raspberry PI Model B

PeterH


Here is my solution to the problem.


You seem to be making a simple problem very complicated. If you just define a variable that holds the data you want to store in EEPROM (be that a byte, int, float, struct or whatever) you can make a single call to EEPROM_readAnything() to read EEPROM content into the variable and a single call to EEPROM_writeAnything() to write your variable to EEPROM. How much simpler could it be?

Code: [Select]

struct config_t
{
    long alarm;
    int mode;
} configuration;

void setup()
{
    EEPROM_readAnything(0, configuration);
    // ...
}
void loop()
{
    // let the user adjust their alarm settings
    // let the user adjust their mode settings
    // ...

    // if they push the "Save" button, save their configuration
    if (digitalRead(13) == HIGH)
        EEPROM_writeAnything(0, configuration);
}

PaulS

Quote
How much simpler could it be?

Not necessarily simpler, but a better example would be one that used just a float, as OP wanted.

Code: [Select]
float configuration;

void setup()
{
    EEPROM_readAnything(0, configuration);
    // ...
}
void loop()
{
    // let the user adjust their alarm settings
    // let the user adjust their mode settings
    // ...

    // if they push the "Save" button, save their configuration
    if (digitalRead(13) == HIGH)
        EEPROM_writeAnything(0, configuration);
}
The art of getting good answers lies in asking good questions.

captainhooyah

I am obviously not grasping the inner workings of what I am doing here with EEPROManything. It seems like it's outputting the number of bytes and not the variable I am trying to save to memory.

Can someone offer me an explanation of what's going wrong, please?

I included an regular EEPread and it's not even close.

Is it because I am not specifying 4 bytes for an address? I'm out of ideas.

I just noticed Hugo's post... I will try that, but I still want to understand.

Thanks in advance for your time.

Code: [Select]

#include <EEPROM.h>
#include <EEPROMAnything.h>

float adj = -.12, EEPadj, regRead;

void setup()
{   
  Serial.begin(9600);
  EEPROM_writeAnything(20, adj);
 
  delay(1000);


}
void loop()
{   

  EEPadj =  EEPROM_readAnything(0,adj);
  delay(25);
  Serial.print("EEP Anything Read:  ");
  Serial.println(EEPadj);
 
  regRead = EEPROM.read(20);
  delay(25);
  Serial.print("regular EEP read: ");
  Serial.println(regRead);
  delay(5000);

}




captainhooyah

Hugo's doesn't do what I need either... It returns 0.00

Code: [Select]

#include <avr/eeprom.h>
float myValue = -0.12;
float myReadingValues;


void setup()
{
Serial.begin(9600);
eeprom_write_dword((uint32_t *)0, myValue);

Serial.println("Retrived back the values from eeprom");
myReadingValues =  eeprom_read_dword(0);

Serial.println("The value is : ");
Serial.println(myReadingValues);
Serial.println("Done");
}
void loop()
{

}

Tom Carpenter

#6
Sep 17, 2013, 08:08 pm Last Edit: Sep 17, 2013, 08:29 pm by Tom Carpenter Reason: 1
There is another way which should work:

To store to the EEPROM:
Code: [Select]
float config = -1.23; //float to be stored
unsigned int address = 20;
for(byte i = 0; i < sizeof(config); i++){
 EEPROM.write(address+i,reinterpret_cast<byte*>(&config)[i]);
}


To read back from the EEPROM
Code: [Select]
float config; //float to be read back in to
unsigned int address = 20;
for(byte i = 0; i < sizeof(config); i++){
 reinterpret_cast<byte*>(&config)[i]=EEPROM.read(address+i);
}
//config is now equal to -1.23


That works now :D In fact it will work with any data type (I *think*).
~Tom~

captainhooyah

Thanks Tom!

I had to move on to something else for the day, I will put this together in my program and see how it works later. 

So to clarify for learning purposes, at first (with the EEPROMwrite, not EEPROMwriteAnything) I wasn't specifying enough memory addresses for the data type I was trying to store in EEPROM.

You fixed that issue with this:

Code: [Select]

for(byte i = 0; i < sizeof(config); i++){
  EEPROM.write(address+i,reinterpret_cast<byte*>(&config)[i]);

//And this is beyond my understanding but I will take your word on it:

for(byte i = 0; i < sizeof(config); i++){
  reinterpret_cast<byte*>(&config)[i]=EEPROM.read(address+i);


//I'll have to look into what reinterpret_cast does.
//I'm taking a C++ class right now, but I haven't come across the * operator used the way you use it: <byte*>
//Or (&config).  What should I look into to learn about that?   
//Any comments on how this code works would be greatly appreciated. 




If anyone knows a good place to learn about the inner workings of Arduino EEPROM and how the different libraries work I'm interested.  I don't like to bother the forum if I can figure it out on my own. The tutorials I've found googling just offer a code and a vague explanation.

 

Tom Carpenter

#8
Sep 17, 2013, 10:06 pm Last Edit: Sep 17, 2013, 10:16 pm by Tom Carpenter Reason: 1
A float is basically 4 bytes starting at a given address in the memory. The compiler sees this address as a float* type, which is basically telling it that what it is looking at is a pointer (the *) and telling it a bit about what it points to (a float).
An array of 4 bytes takes up the same amount of space, but they have a different meaning. For this array you would have the address to the first element stored as a byte* type - telling the compiler that it is a pointer (the *) and each element is a byte in size.
Both the float and the array take up the same amount of space, so if you have a pointer to a float, it could just as easily be a pointer to an array of 4 bytes. For calculations you want a float, for storing to an EEPROM you want the four bytes. But how do you tell the compiler to pretend that float* is a byte*?

In C++, the reinterpret_cast<>() operator in simple terms tells the compiler to start using the pointer in the () as if it is a pointer of the type in the <>. So in this example you have:

Code: [Select]
reinterpret_cast<byte*>(&config)

Which basically does the following:
(1) get a point to the float variable named 'config', that's just using the standard reference (&) operator.
(2) even though the new pointer is a float*, tell the compiler to treat it as if it is a pointer to an array of bytes, a byte*.
It's essentially toll-free type casting for pointers if I understand it correctly.

Taking that a step further, you now have a pointer to an array of 4 bytes, so how do you access each element? you simply add an index, the [ i ] in my example.

Basically this:
Code: [Select]
reinterpret_cast<byte*>(&config)[i]=EEPROM.read(address+i);
is a short, and perhaps confusing, way of writing:
Code: [Select]
byte* aPointer; //A pointer to an array of one or more bytes
aPointer = reinterpret_cast<byte*>(&config); //the pointer now points to the same address as the float
aPointer[i]=EEPROM.read(address+i); //read in a byte from the EEPROM into this address





There is an easier to understand, but messier way of doing it, but this way only works for: float <=> byte[4] and not for any other data type (without changing the union), and temporarily costs 4 more bytes of RAM.

Define a new type using a union:
Code: [Select]
typedef union{
  float flt;
  byte array[4];
} FloatConverter;

To write
Code: [Select]

float config = -1.23; //float to be stored
FloatConverter aConverter; //create a new variable of type FloatConverter
aConverter.flt = config; //set its value (using the float blueprint) to the value of config
unsigned int address = 20;
for(byte i = 0; i < 4; i++){
  EEPROM.write(address+i,aConverter.array[i]); //store each of the 4 bytes of aConverter to the EEPROM, accessing them using the byte[4] blueprint
}

To Read
Code: [Select]

float config; //float to be read
FloatConverter aConverter; //create a new variable of type FloatConverter
unsigned int address = 20;
for(byte i = 0; i < 4; i++){
  aConverter.array[i] = EEPROM.read(address+i); //read 4 bytes from the EEPROM to aConverter using the byte[4] blueprint
}
config = aConverter.flt; //set the value of config to the value of aConverter using the float blueprint
~Tom~

Tom Carpenter

Found it:

http://www.cplusplus.com/doc/tutorial/typecasting/

That's a tutorial for type casting (including the reinterpret_cast<>() operator amongst others).
~Tom~

captainhooyah

You're the man.  Thanks a lot for taking the time to explain that.

PeterH


Can someone offer me an explanation of what's going wrong, please?


You're over-thinking this.

This statement writes the content of adj into EEPROM :
Code: [Select]

  EEPROM_writeAnything(0, adj);


This statement reads the content of EEPROM into adj:
Code: [Select]

  EEPROM_readAnything(0,adj);


The value 0 is the offset in EEPROM that you're accessing, and needs to be the same in both cases.

Go Up