EEPROM - Advanced AVR Topics - Make: AVR Programming (2014)

Make: AVR Programming (2014)

Part III. Advanced AVR Topics

Chapter 19. EEPROM

AVR Secret Decoder Ring

In Chapter 16 we used external EEPROM (electrically erasable and programmable ROM) for nonvolatile external data storage. For making a data logger it was just right: EEPROM provides low-power, long-term storage of fairly large amounts of data. But if all you want is a little bit of data to survive a chip reset, adding an extra chip and tying up the SPI or I2C lines just for a few bytes of configuration data seems overkill. That’s why the Atmel chip designers put a small amount of EEPROM on each of the ATmega chips for you to use.

In this chapter, we’ll turn the AVR into a Vigenère Cipher encoder and decoder, storing the encryption key strings in nonvolatile EEPROM memory so that you don’t have to remember them yourself. Because the keys are stored on the AVR, you can make them fairly long and fairly complicated, making even a trivial example encryption scheme like this unbreakable for short enough messages. And because you can also preload key strings into the EEPROM, you can set up two AVR Secret Decoder Rings with the identical memory contents. Give one to a friend, and you’ll have a marvellous high-tech/low-tech encryption system.

Of course, if you encrypt important information using the AVR Secret Decoder Ring, you’ll want you be sure that you never reflash the chip or let it out of your possession.

WHAT YOU NEED

In this chapter, in addition to the basic kit, you will need:

§ A USB-Serial adapter.

The AVR’s internal EEPROM is full of trade-offs. There’s not a whole lot of it and it’s a little bit slow to erase or write to—taking 1.8 ms for a write or 3.4 ms for an erase and write combo. In practice, if you’re writing an application that runs on a human timescale or only needs to save configuration information periodically, the speed isn’t an issue. You’ve still only got 512 bytes, but whether that’s a lot or a little depends on your application.

The final limitation of EEPROM is that each memory cell has a given number of erase and write cycles that it can go through before it becomes unreliable. On the other hand, reads are unlimited, your data won’t be forgotten when the power goes off, and the EEPROM is specified for 100,000 writes.

In short, unlike the timer/counter modules, the ADCs, any of the serial communications hardware, or the interrupt system, the AVR’s internal EEPROM is a niche utility. It’s not fast enough to use as buffer memory or large enough to store tons of data, and you don’t want to write to it all the time. But when you do need a small amount of nonvolatile memory to save the AVR’s state when the power goes off, the built-in EEPROM is terribly convenient. Working with the EEPROM is also a great excuse for us to use all that we’ve just learned about pointers and memory addresses, so let’s get to it.

Using EEPROM

The AVR’s hardware-level EEPROM functions are fairly straightforward but a little bit cumbersome to use. For instance, to write a byte to a memory location, you need to first check that the EEPROM is ready to use, then write the data to one register, write the address where you’d like to save the data to another register, then set an enable bit, and then within four cycles set the write bit and optionally wait for it to finish. Pshwew!

If you have multiple bytes to read and write, all of this can get annoying, so it should be no surprise that the avr/eeprom.h library has a bunch of utility functions that streamline using EEPROM. Using these functions, combined with what we now know about pointers, makes working with EEPROM easy.

Storing in Memory

First off, there are two types of functions in the avr/eeprom.h library that store a variable: eeprom_write_byte() and eeprom_update_byte(). The difference is that the “write” commands just write to EEPROM, whereas the “update” commands first verify that the data has actually changed before performing a write operation. The extra read from EEPROM only adds a few microseconds, as opposed to milliseconds for the write command, and it can possibly save you time if the EEPROM value hasn’t changed. But more importantly, using eeprom_update_byte() avoids unnecessary writes to EEPROM, which is a good idea because EEPROM, although rewriteable, isn’t rewriteable indefinitely. We’ll be using the “update” family of functions exclusively. Forget about “write.”

EEPROM LIFE SPAN

Rewriting EEPROM causes electrical wear-and-tear on the insulating semiconductor layer that is responsible for retaining the data. The AVR datasheet says that the internal EEPROM is good for around 100,000 write and erase cycles. That’s quite a few, right? Well, maybe. If you write to the EEPROM once per hour, then you have an expected 11 years of useful EEPROM life. At one write per minute, 100,000 writes is only 69.5 days. If you write to EEPROM once per second, you’re looking at just under 28 hours of expected lifetime before data may start getting lost.

There are small improvements possible around the margins, like writing to different memory addresses instead of reusing the same memory cells every time—called wear leveling. But in the end, if you need to save your data very frequently, maybe EEPROM isn’t the right choice.

In the avr/eeprom.h library, EEPROM read and update commands come in a few different types, tailored for reading and writing different types of data. If you read through the include file avr/eeprom.h, you’ll find that the EEPROM update functions are specified as follows:

void eeprom_update_byte (uint8_t *__p, uint8_t __value)

void eeprom_update_word (uint16_t *__p, uint16_t __value)

void eeprom_update_dword (uint32_t *__p, uint32_t __value)

void eeprom_update_float (float *__p, float __value)

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

With the exception of the block update command, the functions take two arguments: an address to store the data (passed as a pointer of the correct type) and the value to store in memory. These avr-libc functions take care all the low-level details: checking that the EEPROM is ready for a write, enabling the EEPROM write bits, loading up the necessary registers, and so on.

FLOATS

I’ve been avoiding using float type variables. Floats are 32-bit floating-point representations that let you represent numbers with decimal places. The floating-point–compatible math libraries end up taking up a bunch of memory and are slow. In my experience, there’s almost always a way to rephrase your problem that can avoid using floating-point numbers; for instance the way we dealt with fractional frequency steps in the direct-digital synthesis method in Chapter 13. But just in case you’ve been secretly using floats all along, now you know how to store them in EEPROM.

The eeprom_update_block command is a little bit more complicated, but it makes nice use of pointers and memory addresses. The three arguments, in order, are: the address of the first element of a source array of data, the address in EEPROM where you want to start writing, and the size of the array in bytes. Internally, the function starts at the first byte of the RAM array and the specified location in EEPROM, and takes one step at a time, writing one byte to EEPROM before moving on to the next.

Let me walk you through some quick example usages of all the different update commands in Example 19-1.

Example 19-1. quickDemo.c listing

#include <avr/io.h>

#include <avr/eeprom.h>

int main(void) {

// Store the 8-bit value 5 in EEPROM slot 0

uint8_t *address = (uint8_t *) 0;

eeprom_update_byte(address, 5);

// Store the 16-bit value 12345 in EEPROM slots 5 and 6:

eeprom_update_word((uint16_t *) 5, 12345);

// Store a character array (string) in EEPROM slots 16-28:

char *stringPointer = (char *) 16;

char myString[] = "hello world.";

eeprom_update_block(myString, stringPointer, sizeof(myString));

return (0);

}

The first example is the most straightforward: we create a pointer to the EEPROM’s memory address 0 with the type uint8_t* so that we’re set up for storing a uint8_t variable. Then, using eeprom_update_byte(), we write the number five into EEPROM. If you wanted to store another byte of data in the next memory slot in EEPROM, you could simply add one to the pointer and go on: eeprom_update_byte(address+1, 17);.

In the next example, we store the 16-bit value 12345 into EEPROM memory locations five and six. Instead of creating a uint16_t* pointer variable just to encode the number five as an address, we convert the type of the Memory Address 1 to a pointer on the fly. You could just write a simple numeric “5” here and it will work because this is a trivial demo example, but the compiler would complain that eeprom_update_word expects a pointer to an unsigned 16-bit integer. If we were using a pointer, and you wanted to store another 16-bit integer, you’d be able to simply add one to the pointer and it would know to skip forward to memory slot seven because the pointer is declared to be 16-bits wide. That’s one reason that using pointers, which join an address with a type, is handy.

The third example writes a string into EEPROM using the eeprom_block_update() function. The first thing to remember about strings is that they’re really arrays of characters that are terminated with a numeric zero. Thus the length of my string is the 12 characters that you probably counted out, plus the trailing zero. That’s how I figured out that I’d be writing from address 16 to address 28 (inclusive). But I didn’t have to figure out the length of the string myself; using sizeof(myString) takes care of the counting for me.

Just about now, if you’ve got a whole lot of different variable types that you’d like to store into EEPROM, you’re probably starting to worry about how you’re going to keep their starting and ending locations straight. If you make a mental mistake on any of the addresses, you can overwrite data or read from the wrong bits. The good news is that you can let the compiler handle all of that for you, and you’ll see how in Organizing Data in EEPROM. Before I show you how to read out EEPROM values in code, though, let’s take a peek at the chip’s memory using AVRDUDE.

AVRDUDE and EEPROM

Not only can AVRDUDE read and write to the flash program memory in the AVR chips, but it can also read and write to EEPROM if your programmer supports it. A fun way to have a look at the EEPROM in your chip is to use AVRDUDE’s terminal mode.

If you’ve been using the makefile that I’ve included with the AVR-Programming-Library library included with the book, all you have to do is type make avrdude_terminal and you’re in. All that does is add the -nt flags to your usual AVRDUDE command line (“t” is for terminal mode) but it’s easier to run from the makefile, no?

Once you’re in terminal mode, you’re talking directly with the AVR chip itself. You can type help and get a list of options. In our case, to see what’s stored in EEPROM, type dump eeprom.

If you haven’t flashed any code that uses the EEPROM into your chip yet, you’ll find that each byte in memory is set to 255, or 0xff, which is what EEPROMs (including flash memory and similar) default to when they’re reset. If you’d flashed and run the littlequickDemo.c code snippet, this is what you’d get after it has run once and written to EEPROM:

avrdude -c usbasp -p atmega168 -nt

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e9406

avrdude> dump eeprom

>>> dump eeprom

0000 05 ff ff ff ff 39 30 ff ff ff ff ff ff ff ff ff |.....90.........|

0010 68 65 6c 6c 6f 20 77 6f 72 6c 64 2e 00 ff ff ff |hello world.....|

0020 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|

0030 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|

avrdude> quit

If you’ve never seen a memory dump before, this code can be helpful for visualizing what’s going on. On the far left are memory addresses of the beginning of the row, in hexadecimal. The middle two sections are blocks of eight bytes each, again represented in hexadecimal. This is literally, byte for byte, the memory that’s sitting in your chip’s EEPROM. The far right is the representation of the data in ASCII, which is good for finding strings (nonprinting characters are printed as periods).

Let’s see what we can learn from this. Well, we wrote a uint8_t 5 to position zero, and there it is. It doesn’t show up on the ASCII side because five isn’t in the range of printable characters. Next, we wrote a 16-bit 12345 (in decimal) at position 5. 12345 is 0x3039in hexadecimal, but we’ve got 39 30 in our memory dump. Storing 16-bit numbers with the least-significant byte first is called little-endian storage, and it’s an arbitrary choice. The avr/eeprom.h library functions handle the data consistently, loading up the LSB register first, then the MSB so you won’t have to be concerned with the byte ordering unless you end up editing EEPROM memory dumps directly.

Finally, we come along to the string. Note that it starts at location 0x0010, or 16 in decimal just as it was supposed to. I defined the string’s array to be just the right size for the string (using char myString[]) so it’s stored in the minimum amount of space in memory. And there it is, trailing 00 byte and all. Then the rest of the memory space is still unused, all 0xff. By default AVRDUDE only shows you the first 64 bytes, so if you’d like to see the rest of the EEPROM in your chip, type dump eeprom 0 512.

WAITING FOR AN EEPROM WRITE TO FINISH

Before we leave the section on writing EEPROM, I should mention that the avr/eeprom.h library also includes a couple of helper functions to test if the EEPROM is ready for access again after a write command. Remember, a write and erase command to EEPROM takes 3.6 ms. To that end, eeprom_is_ready() checks the EEPE bit in the EEPROM control register for you, and eeprom_busy_wait() just loops around until that bit is clear.

You won’t need to use the eeprom_is_ready() command for reading or writing to EEPROM, though; the library’s read and write commands already check for you. The only use I’ve ever had for these functions is double-checking that an EEPROM is done before entering sleep mode.

DATA TYPES

Numbers represented in binary have a necessarily limited number of distinct values they can take on. For instance, if you’ve only got one bit, all you can represent are the numbers zero and one. Two bits gets you zero, one, two, and three. Three bits covers the range zero to seven inclusive, and so on. Each additional bit doubles the number of possible values that binary digit can have.

If you want to represent negative numbers as well as positive numbers, you can use one bit as a positive/negative sign. This cuts the maximum value that’s representable in half, but then adds an equal number of possible negative values (plus one). The numbers are stored internally in two’s-complement order, but this is an implementation detail that you can ignore or look up on Wikipedia if you’re interested.

I strongly prefer the fixed-width variable type names that got introduced with ISO standard C99 for microcontroller work, because you know at a glance how many bits you’re dealing with. With a big computer, where resources aren’t critically allocated, I’m entirely happy to use 64 bits to represent the number five, but on our AVRs, large data types take up an unnecessary amount of memory and usually take significantly more CPU time to process. It’s a good idea to use the smallest data type you can.

Before C99, there were a few integer types, but the size of each wasn’t guaranteed at all and could vary across computer architectures:

char

Meant to represent ASCII character data, the char type is almost always an unsigned 8-bit integer. On the AVR, it’s identical to uint8_t, but I tend to use char when I mean to represent ASCII data and uint8_t when I’m thinking about the numerical value.

int

“Integers” in standard C are at least 16 bits. On the AVR, an int is usually 16 bits. There’s an avr-gcc compiler flag to redefine int as 8 bits, but you’re asking for trouble if you enable it. (See why I like uint16_t now?)

long

“Long integers” are 32 bits on the AVR.

long long

Double long integers are 64 bits on the AVR.

word

“Word” isn’t really a defined C type, but people commonly use it as they would int. Here, what we call a “word” is a 16-bit integer.

The AVR ATmega chips have an 8-bit architecture, meaning that they’re built to work with data that comes in 8-bit bytes. A byte gets you a range of integers, for instance, between zero and 255. When you need more numbers, the next step up is to string two bytes together, giving you a 16-bit word that can range, for unsigned integers, from 0 to 65,535, or from –32,768 to 32,767. Early Macintoshes and PC-ATs used 24-bit addressing, but that seems to have died out. The next commonly used integer is the 32-bit “long” integer, which covers the range from 0 to 4,294,967,296 when you need it.

Reading from Memory

Once you’ve got data stored in EEPROM, it’s simple enough to read it back out. Just like the update commands that we used for writing, there are corresponding read commands: uint8_t eeprom_read_byte(), uint16_t eeprom_read_word(), and uint32_t eeprom_read_dword(). Using these functions is straightforward: Pass them a memory address, and you get the data out. For reading stored strings or other array data, we’ll want to use the block read mode, eeprom_read_block(), which is worth a demonstration. See Example 19-2.

Example 19-2. favoriteColor.c listing

#include <avr/io.h>

#include <avr/eeprom.h>

#include <USART.h>

#define STRING_MAXLEN 0x20 /* 32 characters */

#define STRING_ADDRESS 0x20

#define COUNTER_ADDRESS 0x10

int main(void) {

char myString[STRING_MAXLEN];

char *eepromAddress = (char *) STRING_ADDRESS;

uint16_t counter;

initUSART();

while (1) {

// Read from EEPROM and print out

eeprom_read_block(myString, eepromAddress, STRING_MAXLEN);

counter = eeprom_read_word((uint16_t *) COUNTER_ADDRESS);

printString("\r\nYour old favorite color is: ");

printString(myString);

// Take input, store in EEPROM

printString("\r\n\r\n Type your new favorite color. ");

readString(myString, sizeof(myString));

/* pass by address, function will change its values */

eeprom_update_block(myString, eepromAddress, STRING_MAXLEN);

counter++;

printString("Thanks! \r\nYou've answered the same question ");

printWord(counter);

printString(" times. \r\n");

eeprom_update_word((uint16_t *) COUNTER_ADDRESS, counter);

}

return (0);

}

This demonstration code reads a string out of EEPROM, prints it out for you, and then asks you for a new string and stores it in the same place. Run it once and put in your favorite color. Then use AVRDUDE in terminal mode and dump the EEPROM to see the contents. Quit AVRDUDE and reopen a terminal to change the color again. See how it goes?

And now that we’ve had an introduction to pointers, we can discuss a little bit of the magic that’s going on here when we’re passing the variable myString from one function to another. Let’s start with the first eeprom_read_block() statement. If you look up the function in the AVR libc function reference, you’ll find the function prototype reads void eeprom_read_block (void *__dst, const void *__src, size_t __n). Phswew! Taking that apart, you’ll see that the function returns nothing. So how is it reading the data? Where does it go?

The answer lies in the arguments. The first argument is the pointer to a destination string where we want the data stored after it’s read out. The benefit of passing our function a pointer to a string is that the function doesn’t have to worry about assigning any variables in memory because we’ve already done that in the main() routine. We’ve defined the array myString[32], and then passed the address of myString to eeprom_read_block(), which fills it up.

Remember that when an array is passed to a function, what’s really passed is just a pointer to its first element. When we pass myString as an argument, it is the same as passing &myString[0]: the memory address where it should start storing the data it gets out of EEPROM. When the eeprom_read_block() function reads its first byte in, it stores it in the memory address that is pointed to by myString. Then it increments the pointer and stores the next byte, and so on, until it’s done.

How does the function know when it’s done? The third argument of the function is the length that we’d like read. We need to be careful here! If the compiler only allocated space in memory for 32 bytes, for instance, but we pass the eeprom_read_block() function a length that’s greater than 32, it’ll just keep writing as if nothing had happened. There’s no telling what is stored in the next region of memory, and we might end up corrupting other variables. (Writing over the end of an array and its close cousin the buffer overflow, are the classic sources of crashes and exploitable bugs in C code. If you pass a string or array to another function, make sure that you know how long it is.)

All that remains now is the second argument, a pointer to the starting EEPROM address. If you’re going to read from EEPROM memory, of course you have to tell it where to start and how many bytes to read at a time. The type of pointer you pass to the readfunctions should also reflect the size of the data you’re going to read out. So if you are reading a byte, you pass a uint8_t*, and if you’re reading a word, you pass a uint16_t*.

But what kind of pointer should you pass if you’d like to read out a block of some number of bytes? eeprom_read_block() expects a memory address in the form of a void*, a void pointer. A void pointer should probably actually be called a “generic” pointer—it’s a pointer without a type. It’s what you define when you just don’t care what type of data you’re pointing at. On the AVR platform, if you increment a void pointer, it increases by a byte, so it’s essentially a basic bytewise pointer.

The one virtue that a void* has is that it matches anything. So if you, the programmer, know that the data you’ve got stored in EEPROM is character data, you can pass the eeprom_read_block() a character pointer and you won’t have to convert the type of the pointer just to match the type of the function argument. Or said another way, if you write your function with a void pointer as an argument, you’re simply saying that all you care about is the address and not the type at all. The eeprom_read_block() function can get away with that because in the third argument, you have to tell it how many bytes of data you want to read.

I’ve also included a counter that shows how many times you’ve suffered through the favorite color routine. It’s a simple (and probably optimistic) uint16_t counter, stored at a fixed location in EEPROM, that gets updated and resaved each time you run the program. If you want to test the 100,000 write cycles for the EEPROM, just keep typing in colors! (Are there 100,000 words for colors?)

Saving and Loading EEPROM

The most vaulable (and fun) thing about EEPROM is that it’s nonvolatile. You can power down your chip completely, and the data in EEPROM will still be there when you turn the chip back on, even thirty years later. (Can you say the same for your hard drive?) But what if you want to send that data to a printer? Or save the EEPROM values to your desktop computer so that you can upload them again later? Or pre-load the EEPROM with data from some other source entirely?

Naturally, we’ll use AVRDUDE. We already saw how we could dump the EEPROM data to the screen from within terminal mode. If you’d like a more permanent record, you can save all of the EEPROM to a quasi-readable file using a command like avrdude -c usbasp -p atmega168 -U eeprom:r:myEEPROM.hex:i (where you’ll need to substitute your own programmer’s options).

The myEEPROM.hex file is stored in Intel hex dump format. This format uses the first eight digits for the memory address, followed by the data, and the last two digits as a checksum. Because of the checksum, editing the .hex files directly is a little bit awkward, but having the checksums around guards against data corruption. Use this if you just want to store, copy, and reupload EEPROM data.

If you’d like to edit the data, the easiest way is to dump the data in raw binary mode. With my setup, avrdude -c usbasp -p atmega168 -U eeprom:r:myEEPROM.raw:r creates a binary file with the data in it. You’ll want to use a hex editor to edit this file. There are about a thousand freeware hex editors out there, so you can just pick one.

Alternatively, you can open up the file in a Python interactive session, edit the data, and write it back out:

>> eeprom = file("myEEPROM.raw").read() ## Reads in as a string

>> eeprom[15:28]

'hello world.\x00'

>>> eepromList = list(eeprom) ## Converts to list so can edit elements

>>> eepromList[15] = "j"

>>> eeprom = "".join(eepromList) ## Back to string

>>> eeprom[15:28]

'jello world.\x00'

>>> file("newEEPROM.raw", "w").write(eeprom)

Now if you want to reupload either the hex or raw format EEPROM memories to the AVR, you issue a command like avrdude -c usbasp -p atmega168 -U eeprom:w:myEEPROM.raw:r, where the only thing I changed was eeprom:r: for read to eeprom:w: for write. Using this strategy, you can load whatever values into the EEPROM that you’d want to, even programmatically. This is handy if you’re flashing a bunch of devices but would like each one to have a unique serial number, for instance. A single Python routine can take care of flashing the program memory by calling AVRDUDE, creating an EEPROM image with the serial number in it, and then flashing that across as well.

The dumpEEPROM-edit-and-reflash procedure is good for a quick, one-time edit, and although you can write routines to generate EEPROM data that are separate from your C code, it turns out that you don’t have to. What do you do if you want to preload EEPROM value into the AVR but don’t want to have to keep track of all the memory locations yourself? The answer is the EEMEM macro—the topic of the next section.

Organizing Data in EEPROM

By now, you should be able to do anything you want to with EEPROM using just the read and write commands, if you conscientiously keep track of what data is in which memory locations. Heck, you can even preload values into EEPROM by saving them to a file and uploading them with AVRDUDE as you just saw. On the other hand, keeping track of memory locations is a drag, and hand-editing EEPROM dump files isn’t much fun either.

When you’re saving variables in “normal” RAM, you don’t have to worry about keeping track of the addresses by yourself—you leave that to the compiler. You just type in a=7; and the compiler figures out where to stash the number, and goes looking in the right place when you ask it to print out the value a again.

In this section, you’ll see how to use C’s built-in variable handling and (pointer) addressing scheme to handle the EEPROM memory management chores for you automatically, and additionally how to initialize data in EEPROM the easy way. Along the way, you will learn a bit more about how you actually use pointers for storing and recalling memory addresses. All of this is made possible by the EEMEM macro. I’ll demonstrate how this works in Example 19-3.

Example 19-3. eememDemo.c listing

#include <avr/io.h>

#include <avr/eeprom.h>

#include <USART.h>

#define STRING_LEN 80

// Define EEMEM variables

uint8_t eepromCounter EEMEM = 0;

char eepromString[STRING_LEN] EEMEM = "Welcome to the EEMEM Demo.\r\n";

uint16_t eepromWord EEMEM = 12345;

int main(void) {

initUSART();

char ramString[STRING_LEN];

uint8_t counter;

while (1) {

printString("\r\n------------------\r\n");

eeprom_read_block(ramString, eepromString, STRING_LEN);

printString(ramString);

printString("\r\nThe counter reads: ");

counter = eeprom_read_byte(&eepromCounter);

printByte(counter);

printString("\r\nMy uint16_t value is: ");

printWord(eeprom_read_word(&eepromWord));

printString("\r\n Enter a new introduction string below:\r\n");

readString(ramString, STRING_LEN);

eeprom_update_block(ramString, eepromString, STRING_LEN);

counter++;

eeprom_update_byte(&eepromCounter, counter);

}

return (0);

}

Designating variables to be stored in EEPROM memory is easy enough; see the first three (global) definitions for usage. All you need to do is add the EEMEM qualifier to the variable declaration. C will then go ahead and allocate addresses in EEPROM to store those variables. In your code, you can get those addresses back using the & “address-of” operator whenever you need to.

Notice how much easier this is than generating an EEPROM file by hand, having to remember where all the addresses are, and copying them into your C code. Here, the preloaded EEPROM is stored in your code, and its address is available to you.

As we move down through the main() function, you’ll see how it’s used. Because our printString function needs a copy of the string in RAM to work, we had to declare a big enough character array to handle the string stored in EEPROM. And then before we can print it out, we use the eeprom_read_block() function to copy the string out of EEPROM memory and into our RAM character array, ramString. Don’t forget that eepromString is an array, so that passing it as an argument to a function is really passing the address of the first element.

The next few lines demonstrate how to read byte (8-bit) and word (16-bit) values out of EEPROM. It’s easy when the EEMEM macro keeps track of the variable addresses for us. To get the data stored as eepromCounter, we need to pass its address toeeprom_read_byte(). If we allocated this memory manually, we’d have to remember that the counter data was stored at EEPROM memory slot whatever. But here, we simply use the “address-of” operator on the EEMEM-defined variable, and we’re all set. Compareread_byte with read_block—we have to explicitly use the & operator here because we wanted the address of an integer. We didn’t nedd to use the & operator in the earlier example because in that case we wanted the address of an array.

If you dump the EEPROM using the AVRDUDE terminal, you’ll find that the 16-bit number is stored at memory location 81. You could have figured that out yourself (one byte for the counter plus 80 bytes for the welcome string), but it’s a lot easier to let gcc take care of it all for you. Much more importantly, if you change your code, adding a variable or two stored in EEPROM, or changing a string’s length, all the addresses get updated automatically when you recompile.

The last half of the demo takes input from you, stores it as the next welcome string, increments the counter, and stores that away in EEPROM, too.

Initializing EEPROM

But if we just flash this program into our chip, the program will just print out a bunch of garbage because although we’ve changed the program, we haven’t updated the EEPROM yet. (Try it!) Where did our predefined EEPROM data go?

When the EEMEM statement allocates the variables in EEPROM, it writes them into our compiled .elf file, so in principle the variables are usable by the rest of the code. But AVRDUDE doesn’t flash the EEPROM without our permission. This means that we can reflash the program memory or the EEPROM memory or both, which is very handy if we’ve got some values in EEPROM that we’d like to keep. The upshot of this is that in order to preload the EEPROM data, we need to split it out into an EEPROM file and burn that into the AVR in a separate step, just as we were doing in Saving and Loading EEPROM.

Where you normally type make flash to compile and load the program into the AVR, you can type make flash_eeprom to generate the EEPROM file and flash it into EEPROM. And if you type just make eeprom, you’ll end up with a copy of the Intel hex file that it uses. Feel free to open it up in a hex editor and have a look. This time, though, you won’t need to edit the EEPROM memory dump file, because it’s generated from your C code.

If you haven’t already, set the EESAVE fuse bit and play around with this two-stage program and EEPROM cycle. Change some of the ‘EEMEM` variables’ initial values in your code, and type make flash to reprogram the chip. You’ll notice that the values stored in EEPROM didn’t change. Now make flash_eeprom and they will be updated. If you edit the code and reflash that in and none of the memory addresses have changed, you’ll find the variables preserved in whatever state you last left them.

The one thing to be careful of is moving or adding new EEMEM definitions. EEMEM is allocated in the same order it’s defined in your file. If you switch the order of the variables around, between the string and the uint16_t variable for instance, the variables will get assigned different addresses in EEPROM, and things will get scrambled up. If you do change the ordering of EEMEM variables, or even just want to be on the safe side, reflash both the program memory and the EEPROM. To be on the super safe side, you can first run run make clean, which will remove all of the compiled files—machine code and EEPROM. Then use make flash flash_eeprom to remake your entire project from scratch and flash both program and EEPROM memories.

THE EESAVE FUSE BIT

Naturally, there’s a catch. ATmega168 chips come from the factory with a fuse setting that automatically invokes an EEPROM erase every time you program the chip. This can be handy if you want all of the chip’s memory to be in a known state and don’t want data from previous incarnations lying around in EEPROM. Just remember that your EEPROM will be blank unless you reload it each time after a programming cycle.

On the other hand, sometimes you want to flash one program over another without losing the EEPROM values—if, for instance, you’ve found a bug in the code that doesn’t impact the addresses or content of EEPROM. Or maybe you’ve got user data stored in EEPROM that you don’t want to forget. (Though actually, the safest thing to do in that case is to save the EEPROM to a file.)

If you’d like the EEPROM to not be erased automatically when you flash in a new program, you’ll have to set the EESAVE bit in the high fuse byte. Starting from a stock mega168 chip, you’d pass AVRDUDE an argument like -U hfuse:w:0xd7:m. Or if you’re using m makefile, just type make set_eeprom_save_fuse. You can reset the EESAVEfuse by writing a 0xd9 to hfuse or by invoking make clear_eeprom_save_fuse.

If you haven’t set the EESAVE fuse bit, make sure that you’re flashing the EEPROM initialization values after you’ve flashed in your program. The order is important because each time you reprogram the chip, it will reinitialize the EEPROM.

Project: Vigenère Cipher Encoder/Decoder

I’ve already walked you through a bunch of the good uses for EEPROM and how you’d implement it. In my projects, I’ve never used more than a few bytes of EEPROM—for serial numbers or for storing a couple configuration parameters. Nonvolatile storage in small amounts like this is invaluable when you need it, but you never really need all that much.

For instance, in a logging accelerometer I made, I used five bytes of EEPROM—one for a sensitivity setting (2 g / 8 g) and four for the location of the last page write on the SD card that was used for storing the data. This enabled it to restart in the right place in case it lost power or had a brownout. That’s not an ideal use of EEPROM because the logger ended up saving its state every three minutes or so, but it only had to run for four hours anyway, and it was only four bytes, so if it burned them out, I’d just switch to another location.

In this project, however, I’m going hog-wild. The program itself is a menu-driven Vigenère cipher encoder and decoder that stores its code phrases in EEPROM. I’m also storing the strings that are used as menu prompts in EEPROM, just because I can. (Flash program memory is a better place for storing static data, but that’s the topic of Chapter 18.)

A Vigenère cipher is an old encryption method, based on the Caesar cipher. In the Caesar cipher, every letter of the message is replaced with a letter that’s been shifted by the same number of letters in the alphabet. If the shifted result runs off the end of the alphabet, you start over at “a” again. So if the message is “hello” and the shift is one, the encrypted message is “ifmmp.” Julius Caesar is reported to have used the secret key “c,” which shifts all characters over by three and isn’t really all that clever: “C” for Caesar. It might have worked in Emperor Caesar’s time, but it’s easily crackable these days.

On the other hand, two changes can make the encryption unbreakable. The first is to shift each letter by a different amount, depending on their position in the message. The most common way to do this, called a Vigenère cipher, is to have a key phrase that’s easy to remember and then shift each letter in the message by a number of letters that corresponds to the key phrase. So if your key phrase is “abdc” and the message is “hello,” you’d encrypt as follows: “h” is shifted over by one because of the “a,” to become “i.” “e” is shifted over two letters because of “b,” and becomes a “g.” The AVR version here updates the Vigenère cipher to use the full range of printable ASCII characters, but the operation is the same. To decrypt, you just apply the same idea in reverse—subtracting the letter value of each letter in the key phrase.

For short key phrases, the Vigenère cipher is good enough to challenge a novice cryptanalyst. If you use a five-letter key phrase, for instance, cracking the code is like solving five simultaneous Caesar ciphers, which is hard, but possible if the plaintext message is long enough. If you want truly unbreakable encryption, you can make the key phrase truly random and as long as the message—a “one-time pad.” Using a medium-length memorable key phrase gives you encryption that’s somewhere between these extremes of simple to decrypt and unbreakable.

So flash the code example and play around. If everything looks like garbage after you’ve flashed the code in, don’t forget to reinitialize the EEPROM. make clean flash flash_eeprom should get you set up. Enter a sample message, and either use a precompiled code phrase or enter your own. What’s fun about Vigenère ciphers is that they’re symmetric—if you encrypt twice with the same code phrase, then decrypt twice, you still get back to your original message.

Now let’s see what makes the program tick. I’ve split the code up into two sections to make it more manageable. Let’s have a look at the header first in Example 19-4.

Example 19-4. vigenereCipher.h listing

#include <avr/io.h>

#include <avr/eeprom.h>

#include "USART.h"

#define MAX_TEXT_LENGTH 256

#define CODE_LEN 64

// -------- Global Variables --------- //

char EEMEM code0[CODE_LEN] = "ettubrute";

char EEMEM code1[CODE_LEN] = "attackatdawn";

char EEMEM code2[CODE_LEN] = "theraininspainfallsmainlyontheplain";

char EEMEM code3[CODE_LEN] = "ablewasiereisawelba";

char *codePointers[] = { code0, code1, code2, code3 };

// Menu strings. Why not store them in EEPROM?

char EEMEM welcomeString[] = "\r\n--== Vigenere Cipher ==--\r\n";

char EEMEM menuEncode[] = " [e] to encode text\r\n";

char EEMEM menuDecode[] = " [d] to decode text\r\n\r\n";

char EEMEM menuEnterText[] = " [n] to enter new text\r\n";

char EEMEM menuCodeText[] = " [c] to select your code phrase\r\n";

char EEMEM menuChangeCode[] = " [x] to modify code phrases\r\n";

char EEMEM promptCode[] = "code: ";

char EEMEM promptText[] = "\r\ntext: ";

char EEMEM promptSelectCode[] = "Select codephrase:\r\n\r\n";

char EEMEM promptTypeText[] = "Type your text: ";

// Given the address of an EEPROM string, prints it out

// Used for menu items

void printFromEEPROM(char *eepromString);

// Takes input from serial, stores it in the text array

void enterText(char text[]);

// Reads code phrases out of EEPROM and prints them.

// Uses the codeBuffer for temporary storage

void displayCodes(void);

// Changes a code phrase, both in EEPROM and the current code

void changeCode(char codeBuffer[]);

// Pick a code phrase from EEPROM

void selectCode(char code[]);

// Encodes the passed text string, in place

void encodeVigenere(char text[], char code[]);

// Decodes the passed text string, in place

void decodeVigenere(char text[], char code[]);

You can see that I’m going to be letting the compiler allocate the EEPROM memory using the EEMEM keyword—there are tons of EEMEM declarations in the global-variable section of the file. Other than those defines, I’d like to draw your attention to the array of character pointers that’s defined to point to the four code phrase strings: codePointers.

codePointers is really just an index. To find the address of the second key phrase, for instance, you first look up the address of the desired code string in EEPROM from the array codePointers[], and then you’re ready to fetch the string itself out viaprintFromEEPROM() or eeprom_read_byte(), or whatever.

Almost every time I have a bunch of related strings stored in memory, EEPROM, program flash, or even RAM, I’ll create this kind of pointer-array for indexing purposes. It’s much easier to type code like:

char* codePointers[] = {code0, code1, code2, code3};

stringAddress = codePointers[i];

printFromEEPROM(stringAddress);

than it is to explicitly handle each case:

if (i==0){

printFromEEPROM(code0);

}

else if (i==1){

printFromEEPROM(code1);

}

else if (...){

// etc. for each possible code

}

Next we come to a bunch of strings that we’re going to use for prompts and menu items later on. Why am I doing this? When we print out a string using printString("hi");, for instance, the compiler allocates memory to encode the phrase “hi” both in the program memory, and as a variable in RAM, because our printString() command takes a normal RAM array as an argument. Here, we stash the strings in EEPROM instead and read the characters out from EEPROM one by one, which saves on RAM. A better place to store nonchanging prompt strings is in program memory, as we saw in Chapter 18, but I had the extra storage space in EEPROM, so why not?

Finally, we have the function prototypes. When we write free-standing libraries in C, we almost always have a header (.h) file and a code (.c) file. In these cases, it’s mandatory to prototype your functions in the header file, so that the compiler knows what functions your main code has available to it just by including the header file. Here it’s not strictly necessary because all of my functions are defined in the main file, but it’s still nice for style. Looking through all the function prototypes like this can give you a good overview of what the code does.

From the prototypes, we can see that there are functions for printing strings stored in EEPROM to serial output, for entering text into strings, for displaying the code phrases, for changing the code phrases, for picking an active code phrase, and for encoding and decoding. The main() routine basically ties all these together and gives us a fairly sophisticated program.

Most of these functions use a trick that’s made possible by pointers. You’ll notice that they all have return type void, meaning they don’t return anything. But then how can a function like enterText(), which takes in text and “updates” a text string variable, actually work? The answer is that the argument, a text string (a character array with a zero at the end), is passed into this function by its address: the pointer to its first memory element. Everything the function does with this “array,” it’s actually doing in the memory location that is passed by the pointer. So the function doesn’t need to return anything at all; all the changes to the variable are done within the function itself.

When we call enterText(char text[]) from the main() routine, we pass a string that’s defined in main() somewhere, let’s say that it’s called textBuffer in main(). This data, the letters that make up the string, are stored somewhere in RAM. When we pass the array to the enterText() function, remember that what’s being passed is actually the address of the first character in RAM. When the enterText() function stores a letter in text[0], it’s actually storing the letter in the memory address that was passed to it—the same memory slot that’s pointed at by the first character of textBuffer in main().

The upshot of passing arrays by pointers like this is that the called function can write directly to the same memory that’s been allocated in main(). This in turn means that there’s no reason for our enterText() function to pass anything back to main() as a return value—it can just edit the underlying variable memory directly, and then when the function returns, main() will have the new character data already sitting in textBuffer.

In order to see how this all works in practice, and to play around some with reading and writing to EEPROM memory using pointers, let’s dig into the C code now. Because the listing is so long, I’ll break it up into an outline first in Example 19-5, and then we’ll fill in the details as we work through it.

Example 19-5. vigenereCipher_outline.c listing

/*

Vigenere Cipher encoder / decoder demo

And an excuse to play around with EEPROM memory

*/

#include "vigenereCipher.h"

// -------- Functions --------- //

void printFromEEPROM(char *eepromString);

void enterText(char text[]);

void displayCodes(void);

void changeCode(char codeBuffer[]);

void selectCode(char code[]);

void encodeVigenere(char text[], char code[]);

void decodeVigenere(char text[], char code[]);

int main(void) {

// -------- Inits --------- //

char textBuffer[MAX_TEXT_LENGTH];

char codeString[CODE_LEN];

char input;

initUSART();

// ------ Event loop ------ //

while (1) {

// Menu

printFromEEPROM(welcomeString);

// .... more fany menu printing

printFromEEPROM(menuDecode);

input = receiveByte();

switch (input) {

case 'e': // encode

encodeVigenere(textBuffer, codeString);

break;

case 'd': // decode

decodeVigenere(textBuffer, codeString);

break;

case 'n': // new text

printFromEEPROM(promptTypeText);

enterText(textBuffer);

break;

case 'c': // choose code

selectCode(codeString);

break;

case 'x': // change code

changeCode(codeString);

break;

}

} /* End event loop */

return (0); /* This line is never reached */

}

Before we get lost in the trees, let’s have a look at the whole forest—let’s start with the main() routine. First off, RAM storage for two strings (character arrays) are allocated—MAX_TEXT_LENGTH for the textBuffer and CODE_LEN for the codeString. We’ll store the encrypted and decrypted text in the former, and the code phrase in the latter.

At the top of the event loop, you’ll see a lot of printing statements that are responsible for creating the menu that you first see when you run the program. I’ve shortened them because they are a little bit repetitive. Most of the printing action takes place from EEPROM. We’ll definitely want to see how that’s done in the printFromEEPROM() function.

The rest of the event loop is a simple event dispatcher. We type in a menu item, and then the switch / case statement calls the right function. All of these functions change one or the other of the two character arrays in RAM declared within the scope of main():textBuffer and codeString. And because both the text and the code phrase are printed out just above in the menu, any updates to either that occur in these functions are immediately displayed to the user.

All the rest of the action is in the individual function definitions, so let’s have a look at those now:

void printFromEEPROM(char* eepromString){

uint8_t letter;

do {

letter = eeprom_read_byte((uint8_t*) eepromString);

transmitByte(letter);

eepromString++;

} while(letter);

}

First off, printFromEEPROM() makes good use of the EEMEM macro. Remember that what EEMEM did for us was to assign a variable name to a location in EEPROM, so that we could use it by taking the address of that “variable.” That’s what makesprintFromEEPROM(char* eepromString) tick. Notice that the argument is a pointer to characters stored in memory—in this case, the address in EEPROM.

printFromEEPROM() then takes the pointer to a memory address, reads a byte out of memory, transmits it across the serial line, and then moves on to the next byte in memory by incrementing the pointer. It does this as long as the letter it read out is anything other than a zero, which marks the end of the string. The only other catch is the typecasting of the type char* to uint8_t*, which is essentially meaningless because both types refer to a byte of data, but we know we’ve stored characters, while the functioneeprom_read_byte() expects to have stored numerical data. (If you omit the typecast, the compiler throws a warning about it, but everything works just fine.)

The next function, enterText(), is basically printFromEEPROM in reverse, with the result stored in RAM:

void enterText(char text[]){

uint8_t i=0;

char letter;

do{

letter = receiveByte();

transmitByte(letter); /* echo */

text[i] = letter;

i++;

} while(!(letter == '\r') && (i < (MAX_TEXT_LENGTH-1)));

text[i-1] = 0;

}

For every letter that you type in over the serial line, another character is stored in the RAM character array text[]. This ends when you either send a carriage return (\r) or the maximum length of the text buffer is reached, and then a zero is added to designate the end of the string. Again, when you’re looking at this function, remember that it’s all done with pointers; inside this function, the character array text[] is actually an address in RAM of some variable that’s defined in main(). So when a letter is stored in text[]inside this function, it’s actually stored in the RAM locations that go with whatever variable in main() was passed to the function.

Previously, we took input from the serial port and stored it in variables, so why do we need pointers and addresses here? In the other examples, everything was taking place from the context of a main() function. Here, only the variables defined inside the functionenterText() and the arguments passed to it (and globals and defines) are available inside that function. The point of passing the string variable text[] by its address is that the function can write to memory starting at that address and it will be as if the function wrote directly into the RAM string variable in main() that we passed as an argument to this function:

void displayCodes(void){

uint8_t i;

for (i=0; i<4; i++){

transmitByte(' ');

transmitByte('0'+i);

printString(": ");

printFromEEPROM(codePointers[i]);

printString("\r\n");

}

}

displayCodes() is a convenience function that loops over all of the code phrases and prints them out along with their index number. It calls the already-defined printFromEEPROM function to do most of the work involved with actually printing out the strings. But remember in the header when I said to remember that RAM array of pointers to our EEPROM code phrases? Well, this is where it comes in handy.

Each entry in the global array codePointers[] points to the corresponding memory location in EEPROM where the codes are stored, and as a result we get to write slick code like printFromEEPROM(codePointers[i]);. Each entry in codePointers is a pointer to EEPROM—the address of the start of the string in EEPROM memory. So by picking the second codePointer, we get the starting address of the second code string, which we then pass to printFromEEPROM, which then steps through the whole string address by address, byte by byte.

This same array-of-pointers trick also comes in handy in the functions selectCode(char code[]) and changeCode(char codeBuffer[]). Because they work very similarly, let’s look at selectCode(). Here, the function prints out all the code phrases for us to select from, takes input from us as to which we want to use, and returns the corresponding code string. Only instead of returning the string directly, the main() function passes it the address of a string to selectCode(), and then it’s modified in place:

void selectCode(char code[]){

char input;

char* codeAddress;

printFromEEPROM(promptSelectCode);

do{

displayCodes();

input = receiveByte();

} while((input < '0') || (input > '3'));

codeAddress = codePointers[input-'0'];

eeprom_read_block(code, codeAddress, CODE_LEN);

}

void changeCode(char codeBuffer[]){

char input;

char* codeAddress;

printString(" -- Choose code phrase to replace:\r\n");

do{

displayCodes();

input = receiveByte();

} while((input < '0') || (input > '3'));

codeAddress = codePointers[input-'0'];

printString(" -- Enter new code: ");

enterText(codeBuffer);

eeprom_update_block(codeBuffer, codeAddress, CODE_LEN);

}

Remember that the character string in RAM, code[], is passed to the function by the address of its first element. When the line eeprom_read_block(code, codeAddress, CODE_LEN); is run at the end of the function, it is going to pull a string from EEPROM and write it into the RAM address &code[0]—the memory address of the RAM string that’s available in main().

The pointer array codePointers[] is responsible for getting the right location in EEPROM to copy the data from. Here’s how it works. Imagine that we’ve just typed in the character “2” because we want code phrase number two. input - '0' subtracts the ASCII character values for “2” and “0,” resulting in a numeric two. Now the codePointers array stores the memory locations of our four code strings in EEPROM, so codeAddress = codePointers[2] gets the address of code phrase number two.

Now we’ve got the starting address of a string in RAM memory that was passed as an argument, the starting address of the code string in EEPROM, and the defined macro that says how many bytes to copy. The last line simply block-reads this code phrase data into RAM memory—starting at the address pointed to that was passed as an argument to the function. Because we called this function from main() as selectCode(codeString);, our variable codeString now contains the copied value from EEPROM. I love it when a plan comes together.

The other thing that’s cute about this function is the way that the do{}/while{} loop makes sure that we type a valid code number by re-asking us for input until the value lies between 0 and 3. This is important because otherwise we’d accidentally type in a number that’s bigger than three, the function would go looking for an EEPROM memory pointer where there aren’t any stored, and who knows what would happen!

void encodeVigenere(char text[], char code[]){

uint8_t codePosition = 0;

uint8_t textPosition = 0;

do{

if (code[codePosition] == 0){ /* loop when at end of code phrase */

codePosition = 0;

}

text[textPosition] += code[codePosition] - 32;

if (text[textPosition] > 126){

text[textPosition] -= 95;

} /* keeps within printing characters */

codePosition++; /* and move on to the next */

textPosition++;

} while(text[textPosition]); /* until zero at the end of string */

}

void decodeVigenere(char text[], char code[]){

uint8_t codePosition = 0;

uint8_t textPosition = 0;

do{

if (code[codePosition] == 0){

codePosition = 0;

}

if (code[codePosition] > text[textPosition]){

text[textPosition] += 95;

} /* keeps within printing characters */

text[textPosition] -= code[codePosition] - 32;

codePosition++;

textPosition++;

} while(text[textPosition]);

}

In comparison, the functions encodeVigenere and decodeVigenere are almost boringly straightforward. As mentioned earlier, Vigenère ciphers work by adding the numerical values of a letter in the code phrase to the plaintext to encrypt it, and subtracting the same code phrase from the encrypted text to get the plaintext back. Doing this in C is made easy by the equivalence of numbers and letters in ASCII. The first printable character is “space” at ASCII 32, and the last printable character is “~” at 126—that’s 95 printable characters in all. When you add the difference between the code phrase’s character and 32 to the text character, wrapping around to the beginning if the sum is too large, you’ve just Vigenère-ciphered. Decoding is the subtraction version of the same procedure, with bounds-checking to make sure the subtracted characters stay in the printable range.