By Joe Pardue View In Digital Edition
In our last episode, we introduced computer memory and learned a little about how the C programming language uses pointers for addresses to data variables. We also learned to use Pelles C to test C programming concepts on a PC. Now, we'll get back to looking at examples for the AVR memory as applied to reading the EEPROM.
Yes, we belabored this last time but I’ve never felt comfortable explaining pointers because I still tend to mess them up. (I remember how many false starts I had trying to learn them.) On the surface, they are simple: A pointer is a memory location containing the address of another memory location. In C, a pointer is a data type for a variable intended to hold the address of another variable. You tell C that a data type is a pointer by marking it with an asterisk ‘*’. So, when you define char *myCharPointer, you are telling the C compiler that myCharPointer is the address of a character. Then, if you want to set this variable to the address of a char, you use the ‘&’ operator to extract it. So, if you define char myChar = ‘a’, you can get the address of myChar by stating myCharPointer = &myChar. This may sound simple, but implementation can be the killer.
Cliff Lawson — the number one poster on AVRFreaks — manages large software projects with 50+ programmers and he says that 50% of the bugs come from pointers. And his guys know what they are doing. So, expect to have to approach learning about pointers many times and from many directions. The best thing I can suggest to help you learn to safely use pointers is to write small pieces of code and thoroughly test them before including them in larger pieces of code.
When you get experienced enough that pointers seem second nature, that’s when you will start to get into real trouble and you’ll get bugs that drive you, well ... buggy. That’s part of the price of admission to C programming of microcontrollers. (Hey, what a nice title for a book — and coincidentally, a book that you can get from Nuts & Volts, along with an excellent hardware projects kit to give yourself a leg up on this C stuff.)
An ideal computer as imagined by John von Neumann would have a single memory that holds the program and data. In the real world, however, memory speed and cost play a role, and few real computers are entirely von Neumann in their architecture. Computers that use different kinds of storage for programs and data are referred to as having a Harvard Architecture, and most contemporary microcontrollers use modifications of this concept.
When a computer is running, it must read the program memory as rapidly as possible. Based on the instructions contained in program memory, it reads and writes data memory. The main difference in the program and data memory areas is that the computer only needs to read program memory, but it must both read and write to data memory. It turns out that from a hardware perspective, it is much cheaper to make memory that is mostly read from, than memory that is both read and written to. So, to fit these different needs and economies, two types of memory were developed. For data, we use RAM (Random Access Memory) that can be easily written to and read from. RAM can be volatile meaning that it doesn’t matter if it loses its data when the power is off. However, for the stored program that is mostly read we use ROM (Read Only Memory). ROM must be non-volatile so that it can retain the program when the power is off. ROM is much cheaper to manufacture than RAM. You often see computers with plentiful, cheap ROM, but with much smaller amounts of the more expensive RAM.
The terminology isn’t 100% clear, of course, because if ROM is read only, how do we store (write) the program on it in the first place? The answer is that it’s really a ‘read mostly’ design that can be written to — usually very slowly and only with special programming equipment. But, as we will see in a minute, one of the innovations of the AVR was that it uses a special type of ROM (Flash EEPROM) for the program memory that allows it to be programmed by ISP (In System Programming).
SRAM (Static Random Access Memory) is fast but expensive relative to Flash. As you can see in Table 1, AVRs have much more Flash than SRAM. Reading and writing SRAM with C is a piece of cake since all you need to do is assign a variable; C takes care of assigning SRAM memory locations for the variables. Since so much of the C programming language was written for use with RAM, we don’t need to learn anything special to use it. The same can’t be said for ROM, however.
AVR | SRAM Bytes | EEPROM Bytes | Flash Bytes | Pins | Cost* |
ATmega48 | 512 | 256 | 4096 | 28 | 2.69 |
ATmega168 | 1024 | 512 | 16384 | 28 | 4.11 |
ATmega328 | 1024 | 512 | 32768 | 28 | 4.30 |
ATmega644P | 4096 | 2048 | 65536 | 40 | 7.76 |
*Cost from Digi-Key 2010 Catalog — DIP Package |
EEPROM Versus Flash EEPROM
In Smiley’s Workshop 22 (Nuts & Volts May ‘10), we mentioned the difference between EEPROM and Flash EEPROM in the context of bootloaders and Baron Munchhausen (who saved himself after a shipwreck by pulling himself out of the ocean by his own bootstraps). [Yes, it was relevant]. These two memory types differ mainly in that for EEPROM, each byte can be read or written individually which requires a lot more circuitry than for Flash, where data is written in large blocks. Less circuitry means lower cost, so Flash is much cheaper. It also means the inconvenience of not being able to deal with data one byte at a time. Most AVRs include both types of memory — reserving Flash for the infrequently written program memory and EEPROM for more frequent writes of small amounts of data that need to be remembered between power cycles.
A further difference is that AVR Flash is rated for 10,000 write cycles (a lot, but not if the data is changed at computer rates), where the EEPROM is rated at 100,000 write cycles. [You can read both all you want with no wear-out problems.] To help get our heads around the meaning of the write cycles, think about updating some data once per hour. You could safely do this for just over 416 days with Flash memory, and 4,166 days (just under 11-1/2 years) with EEPROM. You can see that doing updates every minute would quickly exhaust both memory types (166.6 hours for Flash and 1,666 hours or 69 days for EEPROM). So, we reserve our frequent writes for SRAM, our semi-infrequent writes to EEPROM, and our rare writes for Flash.
The write ratings are low estimates. You can probably get by with a lot more writes, but Atmel only guarantees the low number in the datasheet. You can also use special programming techniques to extend the use of EEPROM that are beyond the scope of this article, but discussed in the AVR101 application note shown in Listing 1.
Listing 1: Atmel AVR EEPROM Application Notes. AVR100: Accessing the EEPROM on tinyAVR and megaAVR Devices |
EEPROM
Let’s say we designed a solar powered GPS guided robot that shuts down at night. If we store our GPS waypoints in EEPROM, then the next morning when our robot restarts it can know where it is and how to get back home. [If it turns out that in the morning it is a mile east of where it went to sleep, then it can have the robotic equivalent of a panic attack.]
For most ATmega AVRs, EEPROM memory is not formally available as part of the AVR addressable memory space. Instead, it is accessed as an internal peripheral device. This requires using special EEPROM registers and read/write instructions. Also, EEPROM access is much slower than the AVR SRAM access so timing has to be considered when using the EEPROM. Since we are using C, we don’t have to directly mess with the low-level register stuff because we can use the avrlibc library EEPROM functions included as part of the WinAVR toolchain. All you have to do is use the header file EEPROM.h in an AVRStudio program and the underlying tools will link to the functions you need.
You can see the docs about these tools at www.nongnu.org/avr-libc/user-manual/group__avr__eeprom.html. Be warned, however, before looking at the web page. This stuff uses the typical proliferation of cryptic (IMHO) data type identifiers from the GCC compiler that tend to befuddle most folks who don’t use this compiler on a daily basis. Just don’t let the prickly stuff scare you. The test software here is much more warm and fuzzy, or at least less prone to give you a rash.
Watch for Brown Outs
One thing you need to know about EEPROM is that if the voltage gets hinky and falls below a certain value while you are writing to EEPROM, you stand a good chance of corrupting your data. We will set the brown-out fuse on the AVR to help prevent this kind of issue. This problem can get you in trouble with things like the hypothetical solar powered robot which has to detect that the power is failing while it still has enough juice left to record the waypoints.
The EEMEM Data Type
The EEMEM attribute tells the compiler to assign a variable to EEPROM rather than SRAM. Unfortunately, this attribute can lead to confusion later since there is nothing to stop you from forgetting that you meant to use this variable with the EEPROM. So, you might do something like:
uint8_t EEMEM myChar;
// BAD USE OF EEMEM<br />
void myFunc()<br />
{<br />
uint8_t c;<br />
c = myChar;<br />
}
If you do this, then the compiler thinks myChar is in SRAM and loads the data from the address indicated by myChar — which isn’t what you intended.
// CORRECT USE OF EEMEM<br />
void myFunc()<br />
{<br />
uint8_t c;<br />
c = eeprom_read_byte(&myChar);<br />
}
Fine. Now you know how to do it and if you mess up, then it’s all your fault for forgetting that six months ago you meant for myChar to be in the EEPROM. Harsh? Yeah, and unrealistic. So, let’s apply an arbitrary programming practice to using EEMEM: We promise to always name our variables that we meant to have in EEPROM with the prefix EE_ so instead of myChar, we would use EE_myChar like below:
// this is in a header we wrote six months ago<br />
// and have now forgotten about:<br />
uint8_t EEMEM EE_myChar;
// correct and saner use of EEMEM<br />
void myFunc()<br />
{<br />
uint8_t c;<br />
c = eeprom_read_byte(&EE_myChar);<br />
}
EEPROM Functions
Read one byte from the EEPROM:
uint8_t eeprom_read_byte (uint8_t *addr)
Returns the byte located at addr.
Write one byte to the EEPROM:
void eeprom_write_byte (uint8_t *addr, uint8_t value)
Writes the value to addr.
Read one word from the EEPROM:
uint16_t eeprom_read_word (uint16_t *addr)
Reads the word (AVR word is 16-bit so data type is uint16_t) at the addr.
Write one word to the EEPROM:
void eeprom_write_word (uint16_t *addr, uint16_t value)
Writes the word at addr.
Read ‘n’ sequential bytes from the EEPROM:
void eeprom_read_block (void *array,void *array, uint16_t n)
Read ‘n’ bytes from the EEPROM beginning at the given address and load them into the given array.
Write ‘n’ sequential bytes to the EEPROM:
void eeprom_write_block (void *array, void *addr, uint16_t n)
Write ‘n’ bytes from the given array into the EEPROM beginning at the given address.
One Code Module, Four Devices
The EEPROM_Test software allows us to test each of the avrlibc EEPROM functions. It is written so that you can use it with four different development environments. Changing one line of code causes it to compile for one of the four. You can use it with AVRStudio and an AVR Butterfly, or the BeAVR (ATmega644 we discussed in WS22), or an Arduino board. Plus, you can use it with the Arduino board in the Arduino IDE, all by changing one line of code! How cool is that?
Well, as usual, there is a price. This works because the code is cluttered with ‘#if defined’ statements that cause the compiler to only look at certain sections of the code. As the user, you get to look at it all whether you need to or not. For instance, if you are using an Arduino with AVRStudio, you couldn’t care less about the Butterfly oscillator calibration; the compiler ignores that section — but the code is there for you to see, anyway. It doesn’t get compiled, so it doesn’t inflate the Arduino code, but it does add text you have to look past. It’s a trade-off that you’ll need to get used to if you advance in C programming.
You get to tell the compiler to generate code for one of the following:
Arduino // Arduino ATmega328 board with Arduino IDE - 57600 baud<br />
ATmega328 // Arduino board with AVRStudio/WinAVR - 57600 baud<br />
ATmega644 // BeAVR board - 16 MHz clock - 57600 baud<br />
Butterfly // The good old Butterfly - ATmega169 - 19200 baud
You tell the compiler which one you want to use by removing the comment directive ‘\\’ that is in front of the device that you will be using. Make sure the other devices have that ‘\\’ in front of them or there is no telling what you’ll get. This is how you would select the Butterfly:
//#define Arduino<br />
//#define ATmega328<br />
//#define ATmega644<br />
#define Butterfly
Finally, note that if you select ‘Arduino,’ you must use the Arduino IDE for compiling. If you select ‘ATmega328,’ you can use the Arduino hardware but must compile using AVRStudio/WinAVR. Also, when using AVRStudio make sure that you select the correct AVR device in the AVR Studio Project/Configuration Options window. If you get a string of errors complaining about undefined registers, you probably didn’t set this to the correct device.
We will use Bray’s Terminal (a.k,a., Br@y++ Terminal) because my Developer Terminal developed a hiccup in Vista and I don’t know if I’ll get it fixed by the time this article goes to press. It still works, but it double prints each received byte. I won’t subject you to a rant about how annoyed I get when every new Microsoft Windows update breaks my stuff. Anyway, Brays works with Vista and is free, so let’s use it. You can get it at http://sites.google.com/site/braypp/terminal. This is a great terminal program and the only reason I’ve rolled my own is that I wanted some features it lacks, but it will do just fine for this demonstration.
FIGURE 1. Br@y++ Terminal. |
One thing that confuses some folks is that the send textbox is the white single line area near the bottom in Figure 1 (with the text $77$00$0A in it). You write stuff there and then click the ‘Send’ button to send text. Many folks mistakenly try to send from the Receive window where you see 03 04 in Figure 1. You can send characters using their hexadecimal in Brays by marking them with a ‘$’ rather than ‘0x’ like we are used to. Bray’s help file is very brief, but if you pay attention, it’s all you really need.
In order to demonstrate how to use the EEPROM_Test code, I’ve written EEPROM_Test_Send.doc that contains more details about how to use Brays for testing the EEPROM code. [This file is in the Workshop24.zip in the downloads section.]
Well we are running out of space and haven’t even gotten to the source code yet. You can also find it in the Workshop24.zip file.
The materials we discussed here can be used with either the AVR Butterfly or the Arduino. Both devices are available from Nuts & Volts as part of the Book/Projects-Kit combos; either of which can give you a lot of help learning about microcontrollers. Next time, we will build on what we’ve done so far, and tackle the AVR program memory space. I promise that everything will get done in a Flash! NV
At the end of this article in the "printed" magazine, (Smileys Workshop #24), a last minute edit caused a line to wrap over to the next, and pushed the last line off the page. The last sentence should read:
“Next time, we will build on what we’ve done so far, and tackle the AVR program memory space. I promise that everything will get done in a Flash! NV”