Forum Menu




 


Log in Problems?
New User? Sign Up!
AVR Freaks Forum Index

Post new topic   Reply to topic
View previous topic Printable version Log in to check your private messages View next topic
Author Message
abcminiuser
PostPosted: May 09, 2006 - 10:13 AM
Moderator


Joined: Jan 23, 2004
Posts: 9817
Location: Trondheim, Norway

For an updated version of this tutorial in PDF format, please see this page of my website.

My second tutorial. This tutorial will centre around GCC's handling of data stored into EEPROM memory.


What is the EEPROM memory and why would I use it?

Most of the AVRs in Atmel's product line contain at least some internal EEPROM memory. EEPROM, short for Electronically Erasable Read-Only memory, is a form of non-volatile memory with a reasonably long lifespan. Because it is non-volatile, it will retain its information during periods of no AVR power and thus is a great place for storing sparingly changing data such as device parameters.

The AVR internal EEPROM memory has a limited lifespan of 100,000 writes - reads are unlimited.


How is is accessed?

The AVR's internal EEPROM is accessed via special registers inside the AVR, which control the address to be written to (EEPROM uses byte addressing), the data to be written (or the data which has been read) as well as the flags to instruct the EEPROM controller to perform a write or a read.

The C language does not have any standards mandating how memory other than a single flat model (SRAM in AVRs) is accessed or addressed. Because of this, just like storing data into program memory via your program, every compiler has a unique implementation based on what the author believed was the most logical system.

This tutorial will focus on the GCC compiler.


Using the AVRLibC EEPROM library routines:

The AVRLibC, included with WinAVR, contains prebuilt library routines for EEPROM access and manipulation. Before we can make use of those routines, we need to include the eeprom library header:

Code:
#include <avr/eeprom.h>



At the moment, we now have access to the eeprom memory, via the routines now provided by eeprom.h. There are three main types of EEPROM access: byte, word and block. Each type has both a write and a read variant, for obvious reasons. The names of the routines exposed by our new headers are:

Quote:
uint8_t eeprom_read_byte (const uint8_t *addr)
void eeprom_write_byte (uint8_t *addr, uint8_t value)
uint16_t eeprom_read_word (const uint16_t *addr)
void eeprom_write_word (uint16_t *addr, uint16_t value)
void eeprom_read_block (void *pointer_ram, const void *pointer_eeprom, size_t n)
void eeprom_write_block (void *pointer_eeprom, const void *pointer_ram, size_t n)


In AVR-GCC, a word is two bytes while a block is an arbitrary number of bytes which you supply (think string buffers).

To start, lets try a simple example and try to read a single byte of EEPROM memory, let's say at location 46. Our code might look like:

Code:
#include <avr/eeprom.h>

void main(void)
{
    uint8_t ByteOfData;

    ByteOfData = eeprom_read_byte((uint8_t*)46);
}


This will read out location 46 of the EEPROM and put it into our new variable named "ByteOfData". How does it work? Firstly, we declare our byte variable, which I'm sure you're familiar with:

Code:
uint8_t ByteOfData;


Now, we then call our eeprom_read_byte routine, which expects a pointer to a byte in the EEPROM space. We're working with a constant and known address value of 46, so we add in the typecast to transform that number 46 into a pointer for the eeprom_read_byte function.


EEPROM words can be written and read in much the same way, except they require a pointer to an int:

Code:
#include <avr/eeprom.h>

void main(void)
{
    uint16_t WordOfData;

    WordOfData = eeprom_read_word((uint16_t*)46);
}



But what about larger datatypes, or strings? This is where the block commands come in.


EEPROM Block Access

The block commands of the AVRLibC eeprom.h header differ from the word and byte functions shown above. Unlike it's smaller cousins, the block commands are both routines which return nothing, and thus operate on a variable you supply internally. Let's look at the function declaration for the block reading command.

Code:
void eeprom_read_block (void *pointer_ram, const void *pointer_eeprom, size_t n)


Wow! It looks hard, but in practise it's not. It may be using a concept you're not familiar with though, void-type pointers.

Normal pointers specify the size of the data type in their declaration (or typecast), for example "uint8_t*" is a pointer to an unsigned byte and "int16_t*" is a pointer to a signed int. But "void" is not a data type, so what does it mean?

Void pointers are useful when the exact type of the data being pointed to is not known by a function, or is unimportant. A Void is mearly a pointer to a memory location, with the data stored at that location being important but the type of data stored at that location not being important. Why is a void pointer used?

Using a void pointer means that the block read/write functions can deal with any datatype you like, since all the function does is copy data from one address space to another. The function doesn't care what data it copies, only that it copies the correct number of bytes.

Ok, that's enough of a derail - back to our function. The block commands expect a void pointer to a RAM location, a void pointer to an EEPROM location and a value specifying the number of bytes to be written or read from the buffers. In our first example let's try to read out 10 bytes of memory starting from EEPROM address 12 into a string.

Code:
#include <avr/eeprom.h>

void main(void)
{
    uint8_t StringOfData[10];

    eeprom_read_block((void*)&StringOfData, (const void*)12, 10);
}


Again, looks hard doesn't it! But it isn't; let's break down the arguments we're sending to the eeprom_read_block function.


Code:
(void*)&StringOfData


The first argument is the RAM pointer. The eeprom_read_block routine modifes our RAM buffer and so the pointer type it's expecting is a void pointer. Our buffer is of the type uint8_t, so we need to typecast its address to the necessary void type.

Code:
(const void*)12


Reading the EEPROM won't change it, so the second parameter is a pointer of the type const void. We're currently using a fixed constant as an address, so we need to typecast that constant to a pointer just like with the byte and word reading examples.

Code:
10


Obviously, this the number of bytes to be read. We're using a constant but a variable could be supplied instead.


The block write function is used in the same manner, except for the write routine the RAM pointer is of the type const void and the EEPROM is just a plain void pointer.

Using the EEMEM attribute

You should now know how to read and write to the EEPROM using fixed addresses. But that's not very practical! In a large application, maintaining all the fixed addresses is an absolute nightmare at best. But there's a solution - the EEMEM attribute.

Defined by the same eeprom.h header, the EEMEM attribute instructs the GCC compiler to assign your variable into the EEPROM address space, instead of the SRAM address space like a normal variable. For example, we could allocate addresses for several variables, like so:

Code:
#include <avr/eeprom.h>

uint8_t  EEMEM NonVolatileChar;
uint16_t EEMEM NonVolatileInt;
uint8_t  EEMEM NonVolatileString[10];



Although the compiler allocates addresses to your EEMEM variables, it cannot directly access them. For example, the following code will NOT function as intended:

Code:
#include <avr/eeprom.h>

uint8_t  EEMEM NonVolatileChar;
uint16_t EEMEM NonVolatileInt;
uint8_t  EEMEM NonVolatileString[10];

int main(void)
{
    uint8_t SRAMchar;

    SRAMchar = NonVolatileChar;
}


That code will instead assign the RAM variable "SRAMchar" to whatever is located in SRAM at the address of "NonVolatileChar", and so the result will be incorrect. Like variables stored in program memory (see tutorial here) we need to still use the eeprom read and write routines discussed above.

So lets try to fix our broken code. We're trying to read out a single byte of memory. Let's try to use the eeprom_read_byte routine.

Code:
#include <avr/eeprom.h>

uint8_t  EEMEM NonVolatileChar;
uint16_t EEMEM NonVolatileInt;
uint8_t  EEMEM NonVolatileString[10];

int main(void)
{
    uint8_t SRAMchar;

    SRAMchar = eeprom_read_byte(&NonVolatileChar);
}


It works! Why don't we try to read out the other variables while we're at it. Our second variable is an int, so we need the eeprom_read_word routine:

Code:
#include <avr/eeprom.h>

uint8_t  EEMEM NonVolatileChar;
uint16_t EEMEM NonVolatileInt;
uint8_t  EEMEM NonVolatileString[10];

int main(void)
{
    uint8_t  SRAMchar;
    uint16_t SRAMint;

    SRAMchar = eeprom_read_byte(&NonVolatileChar);
    SRAMint  = eeprom_read_word(&NonVolatileInt);
}


And our last one is a string, so we'll have to use the block read command:

Code:
#include <avr/eeprom.h>

uint8_t  EEMEM NonVolatileChar;
uint16_t EEMEM NonVolatileInt;
uint8_t  EEMEM NonVolatileString[10];

int main(void)
{
    uint8_t  SRAMchar;
    uint16_t SRAMint;
    uint8_t  SRAMstring[10];   

    SRAMchar = eeprom_read_byte(&NonVolatileChar);
    SRAMint  = eeprom_read_word(&NonVolatileInt);
    eeprom_read_block((void*)&SRAMstring, (const void*)&NonVolatileString, 10);
}



Setting initial values

Finally, I'll discuss the issue of setting an initial value to your EEPROM variables.

Upon compilation of your program with the default makefile, GCC will output two Intel-HEX format files. One will be your .HEX which contains your program data (and which is loaded into your AVR's memory), and the other will be a .EEP file. The .EEP file contains the default EEPROM values, which you can load into your AVR via your programmer's EEPROM programming functions.

To set a default EEPROM value in GCC, simply assign a value to your EEMEM variable, like thus:

Code:
uint8_t EEMEM SomeVariable = 12;



Note that if the EEP file isn't loaded, your program will most likely run with the entire EEPROM being set to 0xFF, or at least unknown data. You should devise a way to ensure that no problems will arise via some sort of protection scheme (eg, loading in several values and checking against known start values).


For an updated version of this tutorial in PDF format, please see this page of my website.


- Dean Twisted Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.


Last edited by abcminiuser on Feb 04, 2012 - 02:21 PM; edited 11 times in total
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
timwhunt
PostPosted: May 15, 2006 - 02:57 AM
Hangaround


Joined: Nov 14, 2005
Posts: 169
Location: Philadelphia, USA

Thanks again Dean! You've picked another great topic that I've been wanting to learn.
 
 View user's profile Send private message  
Reply with quote Back to top
pop48m
PostPosted: Jun 01, 2006 - 06:26 PM
Wannabe


Joined: Nov 17, 2005
Posts: 95


abcminiuser wrote:
My second tutorial. This tutorial will center around GCC's handling of data stored into EEPROM memory.


Do you plan to publish a pdf version of this fine tutorial?

Pop
 
 View user's profile Send private message  
Reply with quote Back to top
abcminiuser
PostPosted: Jun 02, 2006 - 12:54 AM
Moderator


Joined: Jan 23, 2004
Posts: 9817
Location: Trondheim, Norway

pop48m wrote:
abcminiuser wrote:
My second tutorial. This tutorial will center around GCC's handling of data stored into EEPROM memory.


Do you plan to publish a pdf version of this fine tutorial?

Pop


Oh yes, I forgot! Embarassed I've added a PDF version to the original post. Cheers!

- Dean Twisted Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
kscharf
PostPosted: Jun 02, 2006 - 03:30 PM
Posting Freak


Joined: Aug 04, 2004
Posts: 1822
Location: Davie, FL

I usually provide a default value in my code in case the eeprom wasn't loaded.
IE:

uint8_t setting;

setting = eeprom_read_byte(EEPROM_SETTING_OFFSET);
if (setting == 0xff) {
setting = DEFAULT;

This assumes that unprogrammed eeprom contains all ones.

I sometimes also check the value read against some range of legal
values just in case the eeprom contains an illegal value that would cause
a program malfunction, and if so use a defined default setting. You could
also write the default setting into the eeprom if the value contained was
unset or out of range, though this isn't usually necessary since the correct
value is now in sram.
 
 View user's profile Send private message  
Reply with quote Back to top
BrianMcCann
PostPosted: Jun 08, 2006 - 03:55 AM
Rookie


Joined: Apr 12, 2001
Posts: 46


Minor mistake, but I found that:

SRAMint = eeprom_read_byte(&NonVolatileInt);

should be:

SRAMint = eeprom_read_word(&NonVolatileInt);
 
 View user's profile Send private message  
Reply with quote Back to top
abcminiuser
PostPosted: Jun 08, 2006 - 06:29 AM
Moderator


Joined: Jan 23, 2004
Posts: 9817
Location: Trondheim, Norway

Darn it! There's always something I manage to miss Razz. Thanks Brian, I've updated my OP.

- Dean Twisted Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
danrulz01
PostPosted: Jun 20, 2006 - 02:59 PM
Hangaround


Joined: Feb 24, 2006
Posts: 232
Location: Australia (BRISBANE)

thanks Dean. that helped me heaps. ur great at explaing things. Smile
 
 View user's profile Send private message  
Reply with quote Back to top
Taipan
PostPosted: Jun 21, 2006 - 05:04 AM
Hangaround


Joined: Jan 18, 2006
Posts: 116
Location: Gorokan NSW Australia

Hi,

Not sure why, but as soon as I put this line in my code
Code:
#include <avr/eeprom.h>
AVR Studio locks up on me when I Build. I am using a STK500 with a Tiny13. Any ideas?
 
 View user's profile Send private message  
Reply with quote Back to top
abcminiuser
PostPosted: Jun 21, 2006 - 05:07 AM
Moderator


Joined: Jan 23, 2004
Posts: 9817
Location: Trondheim, Norway

You need to install the latest service packs and plugin updates. You can get SP2 from the Atmel website (SP3 is currently in beta), which should include the GCC plugin update to prevent the freezes.

- Dean Twisted Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
Taipan
PostPosted: Jun 21, 2006 - 10:14 PM
Hangaround


Joined: Jan 18, 2006
Posts: 116
Location: Gorokan NSW Australia

Thanks Dean, that fixed it. Excellent tutorial.
 
 View user's profile Send private message  
Reply with quote Back to top
davef
PostPosted: Jul 09, 2006 - 12:26 PM
Resident


Joined: Sep 03, 2005
Posts: 795
Location: Christchurch, NZ

Dean,

***
EEPROM words can be written and read in much the same way, except they require a pointer to an int:

Code:
      
#include <avr/eeprom.h>

void main(void)
{
uint16_t WordOfData;

WordOfData = eeprom_read_byte((uint16_t*)46);
}   


***
Shouldn't this be:
WordOfData = eeprom_read_word((uint16_t*)46);


I am trying to port AVR101 to AVR-GCC. I thought I'd use the eeprom.h functions as, from what I understand, they do not require interrupts to be disabled. Functions written in C (for some compliers), evidently can exceed "a 4 cycle limit" and you must disable interrupts for correct operation.

In <avr/eeprom.h>, eeprom_read_byte and eeprom_write_byte use a u08 pointer for the address. Doesn't this mean you can only access 256 bytes in the EEPROM? What about the remaining 1024-256 = 968 EEPROM bytes in a ATmega32?

The functions in AVR101 originally used u16 pointers so I cast them to u08 pointers to get the code to compile without warnings, when using the <avr/eeprom.h> functions.

I am aware of other forum messages saying that this AVR101 only works for values up to 255, which is OK by me . . . at this stage!
 
 View user's profile Send private message  
Reply with quote Back to top
abcminiuser
PostPosted: Jul 09, 2006 - 12:35 PM
Moderator


Joined: Jan 23, 2004
Posts: 9817
Location: Trondheim, Norway

Davef,

Damnit, yet another typo. That should indeed be read_word - I'll go fix that now. Amazing that anyone still bothers to read my tutorials with all these silly mistakes!

Quote:
In <avr/eeprom.h>, eeprom_read_byte and eeprom_write_byte use a u08 pointer for the address. Doesn't this mean you can only access 256 bytes in the EEPROM? What about the remaining 1024-256 = 968 EEPROM bytes in a ATmega32?


That tripped me up too at the start. In AVR-GCC, all pointers are an int in size, two bytes. The pointer typecast only specifies what type of data the pointer is pointing to, not the pointer's size. A (uint8_t*), (uint16_t*) and (uint32_t*) are all identical in size, they just point to increasingly larger datatypes.

For example, (uint8_t*)0x1234 is a pointer two bytes in size, pointing to a byte at the location 0x1234.

This is a problem when dealing with pointers to the AVR's flash memory, since in some AVRs this overflows an int. Because of this, pointers to flash addresses are still two bytes in size but the pointer is word rather than byte aligned, so an increase of one in the pointer's address corresponds to a jump of two bytes.

If I can think of enough material for this I might make it into a short tutorial since it seems to be such a prevalent issue.

- Dean Twisted Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.


Last edited by abcminiuser on Jul 09, 2006 - 01:59 PM; edited 1 time in total
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
cemox
PostPosted: Jul 09, 2006 - 01:54 PM
Rookie


Joined: Sep 24, 2004
Posts: 24
Location: Istanbul, Turkey

Many thanks for this excellent tutorial!

Cemo
 
 View user's profile Send private message  
Reply with quote Back to top
lfmorrison
PostPosted: Jul 09, 2006 - 05:03 PM
Raving lunatic


Joined: Dec 08, 2004
Posts: 4719
Location: Nova Scotia, Canada

abcminiuser wrote:
This is a problem when dealing with pointers to the AVR's flash memory, since in some AVRs this overflows an int. Because of this, pointers to flash addresses are still two bytes in size but the pointer is word rather than byte aligned, so an increase of one in the pointer's address corresponds to a jump of two bytes.


Almost. Pointers to functions in WinAVR point to word-aligned addresses. For one thing, this allows binary compatibility with the AVR architecture's IJMP instruction, which internally expects a word-aligned pointer.

It also coincidentally allows 16-bit function pointers to reach any address in any AVR up to and including the ATmega128/1280/1281.

But pointers to data in flash is still byte-aligned. This is quite necessary, otherwise storing data in flash would become an extremely wasteful proposition... a 10-character string would require 20 bytes of Flash storage, with the high-order of every Flash word being wasted.

As a result, the decision has been made that all PROGMEM data should be placed as low in Flash memory as possible. Specifically, it goes immediately after the interrupt vectors and before any other executable code. That way, unless you have almost 64 kB of PROGMEM variables, you'll be sure not to overflow the 16-bit data pointer size.

This means that data pointers to Flash can reach any address in ann AVR up to the ATmega64x.

Access to data living above the 64 kB mark on devices that have it, needs to jump through special hoops.
 
 View user's profile Send private message  
Reply with quote Back to top
abcminiuser
PostPosted: Jul 09, 2006 - 10:43 PM
Moderator


Joined: Jan 23, 2004
Posts: 9817
Location: Trondheim, Norway

I stand corrected - I had no idea that GCC aligned the two flash sections differently. Cheers.

- Dean Twisted Evil

_________________
Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
davef
PostPosted: Jul 10, 2006 - 05:46 AM
Resident


Joined: Sep 03, 2005
Posts: 795
Location: Christchurch, NZ

Dean,

Just remember until someone actually writes something down in black and white there would be NO opportunity to find errors!

Until I read the tutorial I had never used the internal EEPROM in my ATmega32. An hour or two and I got things working up to the stage you got to in the tutorial, so I think that makes a pretty good tutorial.

I had read something about pointers being 2 bytes, but couldn't find the reference. Probably wouldn't have helped anyhow!

So, AVR pointers can point to 65536 bytes, ints or longs of information.

Thank you for clearing that one up.

Regards,
davef
 
 View user's profile Send private message  
Reply with quote Back to top
lfmorrison
PostPosted: Jul 10, 2006 - 12:11 PM
Raving lunatic


Joined: Dec 08, 2004
Posts: 4719
Location: Nova Scotia, Canada

65536 bytes, (or any 8-bit data types)
32768 ints, (or any 16-bit data types)
16384 longs, (or any 32-bit data types)
or any combination of the above.
 
 View user's profile Send private message  
Reply with quote Back to top
davef
PostPosted: Jul 10, 2006 - 12:19 PM
Resident


Joined: Sep 03, 2005
Posts: 795
Location: Christchurch, NZ

Oops, thank you for the correction.
 
 View user's profile Send private message  
Reply with quote Back to top
cemox
PostPosted: Jul 17, 2006 - 10:37 PM
Rookie


Joined: Sep 24, 2004
Posts: 24
Location: Istanbul, Turkey

I would like to contribute to this wonderful tutorial by adding two examples about writing/reading doubles and structures from Eeprom for newbies like me Smile

Code:
double EEMEM EEVar;
double a;

void WriteDoubleToEeprom(double x){
   eeprom_write_block((const void*)&x, (void*)&EEVar, sizeof(double));
}

double ReadDoubleFromEeprom(void){
   double temp;
   eeprom_read_block((void*)&temp, (const void*)&EEVar, sizeof(double));
   return(temp);
}


for example, we can easily write any double value to eeprom by

Code:
WriteDoubleToEeprom(123.45);


and then read it back by

Code:
a=ReadDoubleFromEeprom();


******************************

As for the structures, suppose we have structure ST like this:

Code:
typedef struct SStructure{
    int8_t varintbyte;
    int16_t varintword;
    double vardouble;
    char varchar[10];
}ST;



we have to define a EEPROM variable of ST type
Code:

ST EEMEM EEStruct;


and also an ordinary variable of type ST

Code:
ST mystructure;


Now, suppose we initiate mystructure like this:

Code:
mystructure.varintbyte=10;
mystructure.varintword=516;
mystructure.vardouble=123.45;
strcpy(mystructure.varchar,"Share it");


We can write the whole structure to eeprom without bothering with the size, individual members, etc. by calling this function:

Code:
void WriteStructureToEeprom(ST a){
   eeprom_write_block((const void*)&a, (void*)&EEStruct, sizeof(ST));
}


like this: (note the sizeof(ST) statement in the function)

Code:
WriteStructureToEeprom(mystructure);


and read the whole structure back from eeprom by calling this function:

Code:
ST ReadStructureFromEeprom(void){
   ST temp;
   eeprom_read_block((void*)&temp, (const void*)&EEStruct, sizeof(ST));
   return(temp);
}


like this:

Code:
mystructure=ReadStructureFromEeprom();



Cheers,

Cemo
 
 View user's profile Send private message  
Reply with quote Back to top
Display posts from previous:     
Jump to:  
All times are GMT + 1 Hour
Post new topic   Reply to topic
View previous topic Printable version Log in to check your private messages View next topic
Powered by PNphpBB2 © 2003-2006 The PNphpBB Group
Credits