Saturday, March 10, 2012

Arduino, limited RAM and PROGMEM

And so I was happily adding more and more descent error handling code to a piece of code running on an atmega328p (the Atmel AVR micro-controller also typically used in Arduino). And then the thing started acting up. Now I've had that happen to me before so I immediately thought that it was running out of RAM.

The atmega328p has 32k flash, which is typically plenty, but it has only 2k RAM, which is not very much at all.

I was doing things like:

Serial.println("FAIL invalid argument");

Now, you might be thinking, why does it have to store the "FAIL invalid argument" in RAM?
It has all to do with how braindead C/C++ is/are.

"FAIL invalid argument" is a "const char *" which although it seems read-only it actually isn't (and no "const char const *" isn't either).
Thing is in C it is perfectly legal to cast away any types and just start changing characters in the string.
And the compiler can't know you're not doing that as you could be doing it via some very scary indirect pointer arithmetic to get to the characters in the string.

Bottom line is that every "constant" string in the code is copied to RAM because it could theoretically be used.

Now, on to PROGMEM.

It is a way to use strings directly as contained in the program code, but without the copying to RAM.

Okay, I'll first give the short answer which works fine on arduino 0.22 or newer:

Serial.println(F("FAIL invalid argument"));

This does some casting and other magic in the back and does the right thing.

Just for educational purposes lets take a look how to do it without arduino tricks.

the <avr/pgmspace.h> header contains a useful macro, called PSTR which looks like we could use it just like this:

Serial.println(PSTR("FAIL invalid argument"));

Unfortunately that doesn't work. PSTR returns a PROGMEM pointer and it turns out (even though the atmega has a flat address space) we can't use PROGMEM pointers just like regular pointers.

It will nicely compile and give garbage.

The only way is copying the data from PROGMEM to RAM and then using it.

This could be done byte-by-byte with pgm_read_byte (this is what arduino does internally, take a peek in arduino-1.0/hardware/arduino/cores/arduino/Print.cpp ) or to a buffer in RAM with strcpy_P.

All this is quite awkward and error-prone and makes me long for a nicer language for microcontrollers ;)