Accessing AVR EEPROM memory in AVRGCC

AVR microcontrollers have some amount of EEPROM memory on chip. For instance Atmega328 has 1K of byte addressable EEPROM. EEPROM memory can be used to store and read variables during program execution and is nonvolatile. It means that it retains values when power supply is off. EEPROM memory comes handy when we need to store calibration values, remember program state before powering off (or power failure) or simply store constants in EEPROM memory when you are short of program memory space especially when using smaller AVRs. Think of simple security system – EEPROM is ideal place to store lock combinations and code sequences and passwords. AVR Datasheets claim that EEPROM can withhold at least 100000 write/erase cycles.

Accessing EEPROM

Standard C functions don’t understands how one or another memory is accessed. So reading and writing EEPROM has to be done by following special logical system. Simple  EEPROM byte read and write operations has to be done via special registers. Atmega328 has following registers to control EEPROM:

  • 16-bit EEAR (EEARH and EEARL) – EEPROM address register;
  • EEDR – EEPROM 8-bit data register;
  • EECR – EEPROM control register.

As Atmega328 has 1K of EEPROM memory highest cell address will be 0x3FF meaning that only 10 bits of 16-bit EEAR register are needed. EEPROM writing process is controlled via EECR register. In order to avoid failures there is some sequence of writing process:

  • Wait until EEPE bit becomes zero;
  • Write EEPROM address to EEAR register;
  • Write data to EEDR;
  • Set EEMPE bit while keeping EEPE bit zero;
  • In four clock cycles set EEPE bit to initiate writing.

You will find nice read and write examples in datasheet, so there is no need to repeat this here. Lets do more interesting thing – use an interrupt to write to EEPROM. As you may know Atmega328 has one dedicated interrupt EE_READY_vect, that may be set to occur every time when EEPROM becomes ready for writing or reading. Usually you would have to poll for EEPE bit become zero in loop – this require active processing power. Interrupt driven EEPROM writing may be more efficient especially when memory blocs has to be accessed. Lets write simple Interrupt driven AVR EEPROM writing routine and write to it a simple message string.

#include <avr/io.h>
#include <avr/interrupt.h>
//EEPROM address initial 0
volatile uint16_t eepromaddress;
//string array index initial 0
volatile uint8_t i;
//message to be written to EEPROM
uint8_t message[] ="Write to EEPROM";
//EEPROM writing ISR
ISR(EE_READY_vect)
{
/*check if not end of string and address
didn't reach end of EEPROM*/
if ((message[i])&&(eepromaddress<=E2END))
{
	//loads address and increments for next load
	EEAR=eepromaddress++;
	//loads current byte and increments index for next load
	EEDR=message[i++];
	//master write enable
	EECR|=(1<<EEMPE);
	//strobe eeprom write
	EECR|=(1<<EEPE);
}
else
{
	//disable eeprom ready interrupt
	EECR &= ~(1<<EERIE);
}
}
int main(void)
{
//Enable eeprom ready interrupt
EECR |= (1<<EERIE);
//enable global interrupts
sei();
	while(1)
	{
		//do other tasks or sleep
	}
}

As you can see ISR simply takes bytes from message buffer and writes it to EEPROM memory until buffer is empty or EEPROM end is reached. Using EE_READY interrupt leaves EEPROM writing completely to hardware and frees MCU resources for other tasks. In other words EEPROM itself asks for another byte when ready. This way you improve performance and reduce power consumption. Same interrupt source can be used for reading EEPROM.

Using EEPROM library from AVRLibC

We discussed how you can access EEPROM memory by using special registers and safe logic. TO avoid possible errors it is logical to use standard eeprom.h library from AVRLibC that comes with AVRGCC tools. To start working with EEPROM routines we need to include eeprom library in to our source code:

#include <avr/eeprom.h>

This opens access to several useful routines that allow us to read/write/update bytes, words, double words , floats and even memory blocks. Lets see how simple byte write and read looks when using eeprom.h library.

#include <avr/io.h>
#include <avr/eeprom.h>
//use last EEPROM cell
#define EEADDR 0x3FF
//data will be written to EEPROM
#define EEDATA 100
int main(void)
{
//put EEPROM data to this variable
volatile uint8_t EEByte;
//write EEPROM
eeprom_write_byte ((uint8_t*)EEADDR, EEDATA);
//read from EEPROM
EEByte = eeprom_read_byte((uint8_t*)EEADDR);
while(1)
{
	//do nothing
}
}

We have written number byte value of 100 to EEPROM address location 0x3FF and read back to some variable. Same expressions can be used if you need to write word, double word or or even float. Just use one of prop[er functions like eeprom_read_word().

Working with EEPROM memory blocks

We have discussed one way of dealing with EEPROM memory blocks – using interrupt driven writing from buffer. But if you don’t want to write your own routine, then you can use one of standard functions from library. There are three functions for this:

void eeprom_write_block (const void *__src, void *__dst, size_t __n);

void eeprom_read_block (void *__dst, const void *__src, size_t __n);

void eeprom_update_block (const void *__src, void *__dst, size_t __n);

You can write, read and update memory blocks in EEPROM. Functions may not look usual to some of you they use void pointers instead usual data-types like uint8_t. Good thing is that these functions are universal allowing to deal with any data type. Lets see how this works. Lets take eeprom_write_block() function it has three arguments:

const void *__src – pointer to RAM location;

void *__dst – pointer to EEPROM location;

size_t __n – number of bytes to be written.

Actually there is no big difference what type of pointers are used, the only thing important to us is the number of bytes needs to be written. Lets write simple program demonstrating block write and read routines.

#include <avr/io.h>
#include <avr/eeprom.h>
//start from first EEPROM cell
#define EEADDR 0
//block size to be copied
#define BLKSIZE 16
//message to be written to EEPROM
uint8_t message[] ="Write to EEPROM";
int main(void)
{
//where block has to be read
uint8_t readblock[BLKSIZE];
//write block EEPROM
eeprom_write_block((const void *)message, (void *)EEADDR, BLKSIZE);
//read block from EEPROM
eeprom_read_block ((void *)readblock, (const void *)EEADDR, BLKSIZE);
while(1)
{
	//do nothing
}
}

As we see in example we only need to typecast pointers to a void pointers like (void *)readblock so they meet function requirements – this action doesn’t affect pointer value itself. Our real concern now is to read and write correct number of bytes and that’s it.

Allocating variables in EEPROM with EEMEM attribute

We learned how to perform EEPROM write and read operations by defining special addresses. This way we have to keep track of EEPROM addresses that are used in functions. In some cases this is OK but if we need to store lots of EEPROM data it becomes unpractical. Why not define an eeprom variable where address would be allocated automatically by compiller. This is where EEMEM attribute comes in. Again there is no magic in it it simply means that variables with this attribute will be allocated in .eeprom memory section. Lets see this simple example:

#include <avr/io.h>
#include <avr/eeprom.h>
#define BLKSIZE 16
#define DATA 0x10
uint8_t EEMEM eechar;
uint16_t EEMEM eeword=0x1234;
uint8_t EEMEM eestring[] = "Write to EEPROM";
int main(void)
{
uint16_t sramword;
//where block has to be read
uint8_t readblock[BLKSIZE];
//write byte to location eechar
eeprom_write_byte(&eechar, DATA);
sramword=eeprom_read_word(&eeword);
//read block from EEPROM
eeprom_read_block ((void *)readblock, (const void *)eestring, BLKSIZE);
while(1)
{
	//do nothing
}
}

We have declared one EEPROM variable:

uint8_t EEMEM eechar;

This will automatically allocates one byte in EEPROM memory with initial zero value. Second two variables have some defined values:

uint16_t EEMEM eeword=0x1234;

uint8_t EEMEM eestring[] = "Write to EEPROM";

Here we have some initial values assigned to EEPROM variables. But these have to be stored in EEPROM memory. Compiler recognizes these values and creates separate .eep file along with .hex.

When chip is being flashed, .eep file also has to be uploaded in order to have initial EEPROM values. Otherwise your program will fail trying to use them.

This is end of another tutorial. Comments and corrections are always welcome.

Bookmark the permalink.

4 Comments

  1. uint8_t readblock[15];

    This should be BLKSIZE instead of 15 else you’ll get a buffer overflow.

  2. Oops missed that. Thanks.

  3. thank you this is the most complete eeprom tut i have found yet

Leave a Reply

Your email address will not be published. Required fields are marked *