Search |
 |
|
 |
| Author |
Message |
|
|
Posted: May 09, 2006 - 10:13 AM |
|


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  |
_________________ 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
|
| |
|
|
|
|
|
Posted: May 15, 2006 - 02:57 AM |
|


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. |
|
|
| |
|
|
|
|
|
Posted: Jun 01, 2006 - 06:26 PM |
|

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 |
|
|
| |
|
|
|
|
|
Posted: Jun 02, 2006 - 12:54 AM |
|


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! I've added a PDF version to the original post. Cheers!
- Dean  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Jun 02, 2006 - 03:30 PM |
|


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. |
|
|
| |
|
|
|
|
|
Posted: Jun 08, 2006 - 03:55 AM |
|

Joined: Apr 12, 2001
Posts: 46
|
|
Minor mistake, but I found that:
SRAMint = eeprom_read_byte(&NonVolatileInt);
should be:
SRAMint = eeprom_read_word(&NonVolatileInt); |
|
|
| |
|
|
|
|
|
Posted: Jun 08, 2006 - 06:29 AM |
|


Joined: Jan 23, 2004
Posts: 9817
Location: Trondheim, Norway
|
|
Darn it! There's always something I manage to miss . Thanks Brian, I've updated my OP.
- Dean  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Jun 20, 2006 - 02:59 PM |
|


Joined: Feb 24, 2006
Posts: 232
Location: Australia (BRISBANE)
|
|
thanks Dean. that helped me heaps. ur great at explaing things.  |
|
|
| |
|
|
|
|
|
Posted: Jun 21, 2006 - 05:04 AM |
|

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? |
|
|
| |
|
|
|
|
|
Posted: Jun 21, 2006 - 05:07 AM |
|


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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Jun 21, 2006 - 10:14 PM |
|

Joined: Jan 18, 2006
Posts: 116
Location: Gorokan NSW Australia
|
|
| Thanks Dean, that fixed it. Excellent tutorial. |
|
|
| |
|
|
|
|
|
Posted: Jul 09, 2006 - 12:26 PM |
|

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! |
|
|
| |
|
|
|
|
|
Posted: Jul 09, 2006 - 12:35 PM |
|


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  |
_________________ 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
|
| |
|
|
|
|
|
Posted: Jul 09, 2006 - 01:54 PM |
|

Joined: Sep 24, 2004
Posts: 24
Location: Istanbul, Turkey
|
|
Many thanks for this excellent tutorial!
Cemo |
|
|
| |
|
|
|
|
|
Posted: Jul 09, 2006 - 05:03 PM |
|

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. |
|
|
| |
|
|
|
|
|
Posted: Jul 09, 2006 - 10:43 PM |
|


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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Jul 10, 2006 - 05:46 AM |
|

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 |
|
|
| |
|
|
|
|
|
Posted: Jul 10, 2006 - 12:11 PM |
|

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. |
|
|
| |
|
|
|
|
|
Posted: Jul 10, 2006 - 12:19 PM |
|

Joined: Sep 03, 2005
Posts: 795
Location: Christchurch, NZ
|
|
| Oops, thank you for the correction. |
|
|
| |
|
|
|
|
|
Posted: Jul 17, 2006 - 10:37 PM |
|

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
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 |
|
|
| |
|
|
|
|
|
|
|
|