Introduction

EEPROM memory, as found in most (if not all) of the 8-bit PIC devices (and other micros too), is an array of byte sized cells. It is very simple to write char, int and long variables to EEPROM, since the individual bytes that make up the variable are easily separated through the use of binary shifting. However, more complex variables, e.g.: float, double and structs can’t be handled in the same way as char, int and long variables, so a different approach is needed. In order to store these variables, the separate bytes that make up the variable need to be accessed and then stored in EEPROM.

Implementation

The method I have used involves the use of pointers. Consider the following example:

float num = 1.3245;
char *ptr = (char *)#

ptr now points to the starting address of num. Because ptr is a pointer of type char (1 byte), *ptr will return the first byte that makes up num. *(ptr+1) will return the second byte, *(ptr+2) will return the third and so on. This way, the separate bytes the make up the floating point number can be retrieved and then stored. Exactly the same method can be used to retrieve the separate bytes in ANY variable or structure, providing you know the size of the variable, in bytes.

Code

Here are code listings for functions to read/write a float and a generic object to EEPROM.

typedef unsigned char byte;
typedef unsigned int word;

float Eeprom_Read_Float(word addr)
{
      float result;
      byte *ptr= (byte *)&result;
      byte i;

      for (i=0;i<4;i++)
          *(ptr++)=Eeprom_Read(addr++);

      return result;
}

void Eeprom_Write_Float(word addr,float data)
{
      byte *ptr= (byte *)&data;
      byte i;

      for (i=0;i<4;i++)
            Eeprom_Write(addr++,*(ptr++));
}

void Eeprom_Write_Obj(word addr,void *obj,byte size)
{
      byte i,*ptr=(byte *)obj;

      for (i=0;i<size;i++)
            Eeprom_Write(addr++,*(ptr++));
}

void Eeprom_Read_Obj(word addr,void *obj,byte size)
{
      byte i,*ptr= (byte *)obj;

      for (i=0;i<size;i++)
            *(ptr++)=Eeprom_Read(addr++);
}

The first two functions should be pretty self explanatory – Eeprom_Read_Float will take an address in EEPROM then reconstruct and return the 4-byte floating point value stored there. Eeprom_Write_Float will take a floating point value and store it, starting at the specified address, in EEPROM.

Eeprom_Write_Float(0,num);
Eeprom_Write_Float(4,3.142);

buf=Eeprom_Read_Float(0);     //buf = 1.32456
buf=Eeprom_Read_Float(4);     //buf = 3.142

The last two functions, Eeprom_Write_Obj and Eeprom_Read_Obj can be used to read and write ANY variable/structure to EEPROM. Both functions require you to specify an EEPROM address, a pointer to the object to be written/read and the size of the object in bytes. The following examples illustrates the use of these functions:

my_struct var={5,10,"PICs are ace",1.23e-6};
my_struct buf;

Eeprom_Write_Obj(0,&var,sizeof(var));     //Write the structure to EEPROM
Eeprom_Read_Obj(0,&buf,sizeof(buf));      //buf now equals var

long num=184030000,lbuf;

Eeprom_Write_Obj(0,&num,sizeof(num));     //You can use these functions to read/write any variable type
Eeprom_Read_Obj(0,&lbuf,sizeof(lbuf));    //lbuf = 184030000

This Post Has 13 Comments

  1. Steve

    Hi Amr,

    I got to here via EPE. I’ve looked at your EEPROM page, and it made me wonder:

    Have you come across those PICs (many of PIC18, PIC24) where no EEPROM exists, and you have to use program memory? I think you have to r/w at least a ‘page’ (eg, 1kB) and Microchip have some example code in which they try to use ‘wear levelling’.

    I’m asking because I may have occasion to use a PIC24 soon.

    BTW: I’m curious in how easy it is to put in CAPCHA boxes (like you have).

    Regards,
    Stephen (EPE = ‘741’).

    1. Amr Bekhit

      Hi Stephen/741,

      Yes, I have used the flash memory on some of those devices but not using Microchip’s EEPROM emulation code. A friend of mine has used it on the PIC32 and says it’s easy to use and works well. I would imagine that the code presented here would work fine if you replaced the EEPROM read and write calls with Microchip’s emulation versions.

      Instead of using an emulation library, I used a different method of reading and writing to flash. What I did was to copy a block of flash memory into a RAM char array. I then created a structure which defined my settings and pointed it to the start of the RAM array. I can read and write to the structure how I please and the bytes automatically get stored in the RAM array, which I can then write to flash memory:

      e.g.:


      // The contents of the flash memory are copied to this buffer for manipulation
      char FlashBuffer[256];

      // This structure makes it easy to read and write individual settings.
      typedef struct
      {
      char SomeString[10];
      float number1, number2;
      long number3;
      } FlashSettingsStruct;

      // A pointer is created that points to the flash buffer. This makes it easy to access the individual members and
      // reading and writing is handled by the compiler.
      FlashSettingsStruct *FlashSettings = (FlashSettingsStruct *)FlashBuffer;

      // The flash memory is read into the buffer
      ReadFlashBlock(Address, FlashBuffer);

      // Settings can be read and written through the pointer.
      printf("SomeString is %s\n", FlashSettings->SomeString);
      printf("number1 is %f, number2 is %f\n", FlashSettings->number1, FlashSettings->number2);

      float var = FlashSettings->number1 * FlashSettings->number2;

      FlashSettings->number3 = 123456789;
      FlashSettings->number1 = 3.1415;
      strcpy(FlashSettings->SomeString, "A String!");

      // Writing to the pointer automatically writes to the flash buffer. Now you can save it back to flash memory
      EraseFlashBlock(Address);
      WriteFlashBlock(Address, FlashBuffer, 256);

      You can easily add and remove settings by adding and removing members from the structure. You just need to make sure that your RAM buffer is large enough to hold the entire structure.

      I would guess that this method is faster than using the emulation method because you only perform the flash memory write on a large block once you’ve written all your data to the flash buffer, whereas the emulation method writes every individual byte. There is no wear levelling, but that suited me fine as my application doesn’t need to change these settings very often.

      Regarding the CAPTCHA boxes: My website is powered by WordPress, which contains hundreds of ready made plugins. The CAPTCHA boxes are simply a plugin that I have installed. It’s called SI Captcha.

  2. Jarni

    From mikroC manual(and you are using it, I suppose):
    “Ensure minimum 20ms delay between successive use of routines Eeprom_Write and Eeprom_Read. Although PIC will write the correct value, Eeprom_Read might return an undefined result”

  3. Bryan Mayland

    Your Eeprom_Write_Float() function takes the address of a function parameter, which I’m surprised even compiles because that’s illegal in C. You should assign it to a local variable first then use the address of that, or stash it in a union:

    union
    {
    float val;
    struct
    {
    uint8_t byte0;
    uint8_t byte1;
    uint8_t byte2;
    uint8_t byte3;
    } byte;
    } x;

    x.val = data;
    eeprom_write_byte((uint8_t *)addr, x.byte.byte0);
    eeprom_write_byte((uint8_t *)addr + 1, x.byte.byte1);
    eeprom_write_byte((uint8_t *)addr + 2, x.byte.byte2);
    eeprom_write_byte((uint8_t *)addr + 3, x.byte.byte3);

    1. Amr Bekhit

      Hi Bryan,

      There is a mistake in the code, which is probably what’s causing you compiler errors: I haven’t cast my pointers. For example, the Eeprom_Write_Float should be:

      void Eeprom_Write_Float(word addr,float data)
      {
      byte *ptr= (byte *)&data;
      byte i;

      for (i=0;i<4;i++)
      Eeprom_Write(addr++,*(ptr++));
      }

      However, as far as I'm aware, there is nothing illegal about taking the address of a function parameter in C. Where did you find this? With my pointers cast correctly, this code compiles fine in Visual C++ 2010 and passes through Lint with no problems.

      In any case, thanks to your comment, I will be updating the code on this page with the corrections.

  4. Marcelo Melo

    Excellent article. You removed all my doubts about the subject. Congratulations!

  5. Jarni

    You have to put the 20ms delay, or it won’t work properly.

  6. John

    great info.
    please change the web page color: MIGRAINE!!

  7. Thayhor

    Good one, It does not compile in MikroC, can you make a MikroC format of your above code on Eeprom_Write_Float, Eeprom_Write_Obj and Eeprom_Read_Obj.

  8. rajib

    Don’t we need to add some header file for eeprom use in the code ?

    1. Amr Bekhit

      Hi Rajib,

      In this example, it’s assumed you are using the microC compiler, which has a built in EEPROM library. If you are using a different compiler, you will need to include any relevant libraries.

  9. MassaM

    Dear Amr,

    Nice article and explanation.

    One doubt still, how to write 3 dimension array with structs to be as arrays?

    For example, when PORTA are all outputs and i have 8 LEDs connected to each pin (8 bits), now if i
    need to write the PORTA state to eeprom, where the port bits

    (PORTA) are all part of struct called channelData which has Banks array of 99 and Patches array of 8 and LEDs array of 8.

    I mean something in this context:

    typedef struct
    {
    unsigned char bank[99];
    unsigned char patch[8];
    unsigned char channel[8];
    } ChannelData;

    In general, its a programmable LED chaser with 99 memory banks of 8 patches and 8 leds per patch which makes one channel data.

    How would i access to read and write values to each array and then to the EEPROM?

    I use MikroC and PIC16F877A.

    Thank you.

    MassaM

  10. Hello

    Awesome article. Good explanation of code.

Leave a Reply