The AVR microcontrollers have flash memory and SRAM. Code goes in flash and data goes in SRAM, right?
Well, not quite.
It's useful to add a call to avr-size to AVR makefiles. I realised today that I was missing it, which is a shame because it supplies some very useful information about your application, and how it uses the different types of memory on the micro. For example, you can run it from the directory where you built the Nanduino 20x4 LCD code:
H:\src\nanduino\lcd>avr-size --mcu=at90usb162 --format=avr .build/firmware.elf
AVR Memory Usage
----------------
Device: at90usb162
Program: 1604 bytes (9.8% Full)
(.text + .data + .bootloader)
Data: 62 bytes (12.1% Full)
(.data + .bss + .noinit)
The section names .text, .data, .bss and .noinit are described in detail here, but here's a quick summary:
- The
.textsection appears only in flash and represents your code plus the AVR-GCC init code - The
.datasection appears in both flash and SRAM and represents data that is copied from flash into SRAM on RESET (e.g global variables with initialisers) - The
.bsssection appears only in SRAM and represents SRAM locations which are cleared on RESET (e.g global variables without initialisers)
The actual work of copying to and initialising SRAM locations on RESET is done by the __do_clear_bss() and __do_copy_data() functions in the C runtime (i.e the code which executes after RESET but before main() is called).
Typically the cheaper AVR micros have plenty of flash but are short on SRAM. For example the AT90USB162 micro used in the Nanduino has 12kB of useable flash (16kB if you're not using the USB bootloader), but only 512 bytes of SRAM. It is therefore important to conserve SRAM. Unfortunately, SRAM is used implicitly in ways which are often far from obvious. For example:
#include "supergizmo.h"
#include "lcd.h"
#define STATE_IDLE 0
#define STATE_READING 1
#define STATE_WRITING 2
uint8 state = STATE_IDLE;
int main() {
superGizmoInit();
lcdInit();
lcdPrintString("SuperGizmo v1.0\n");
for ( ; ; ) {
switch ( state ) {
case STATE_IDLE:
:
break;
case STATE_READING:
:
break;
case STATE_WRITING:
:
break;
}
}
}
You would be forgiven for thinking this code uses only one byte of SRAM, for the state byte. Actually, the "SuperGizmo v1.0\n" string literal is written to the .data section, which means it gets written to flash, but is copied into SRAM after RESET, using 17 bytes (15 characters, a newline and a NUL terminator) of precious SRAM.
To get around this problem you need to explicitly state that literals should reside in flash, and should not be copied over into SRAM. So instead of:
lcdPrintString("SuperGizmo v1.0\n");
...we do:
#include <avr/pgmspace.h>
:
lcdPrintFlashString(PSTR("SuperGizmo v1.0\n"));
...which causes the string literal to be allocated in the .text section along with the application code, and referenced by a program memory pointer. This way, no precious SRAM is wasted on it. Unfortunately this .text allocation introduces two challenges.
- Firstly, since the AVR uses a Harvard architecture, a program memory pointer is quite different from a data memory pointer. The AVR has a special machine instruction
LPM(Load Program Memory) for accessing data stored in program memory because the normal machine instructions for reading data memory do not work. As a result, it is necessary to write separate implementations of functions accepting strings, one which accepts data-memory pointers, and another which accepts program-memory pointers:#include <avr/pgmspace.h> void lcdPrintFlashString(const char *str) { char ch = pgm_read_byte(str); while ( ch ) { lcdPrintChar(ch); str++; ch = pgm_read_byte(str); } } void lcdPrintString(const char *str) { while ( *str ) { lcdPrintChar(*str); str++; } } - Secondly, there is no optimisation of multiple identical
PSTR("...")expressions. Each occurrence will result in the string literal being copied faithfully into its own unique location in flash memory. This can be avoided by declaring the literal once and referring to that declaration multiple times:#include <avr/pgmspace.h> : const char LONG_LINE[] PROGMEM = "This is a really really very long line!\n"; lcdPrintFlashString(LONG_LINE); lcdPrintFlashString(LONG_LINE);
This technique of storing static data in flash memory is not only useful for string literals; it can be used for all sorts of static data. For example, you can write an efficient CRC32 routine that uses a precomputed table in program memory thus:
static const uint32 lookupTable[] PROGMEM = {
0x00000000,
0x04C11DB7,
0x09823B6E,
0x0D4326D9,
:
};
uint32 crc32Calc(const uint8 *buf, uint16 len) {
const uint8 *p;
uint32 crc = 0xffffffff;
for ( p = buf; len > 0; ++p, --len ) {
crc = (crc << 8) ^ pgm_read_dword(&lookupTable[(crc >> 24) ^ *p]);
}
return ~crc;
}
Dean Camera (of LUFA fame) has written a very nice tutorial about this stuff here.