Some AVR memory questions

I had a few Arduino itches I wanted to scratch, bits and pieces which aren't particularly complex or interesting enough to warrant a standalone blog post, but which are nonetheless worth spending a few paragrapsh on.

Why does every Arduino build include "-R .eeprom" when producing the ELF binary?

I had previously wondered whether it was necessary to pass in "-R .eeprom" when building the ELF binary, but I didn't really explore it much at the time. Essentially all articles or guides about using the GNU toolchain to compile programs for Arduino/AVR (and indeed the Arduino IDE itself) specify this switch so it's really worth clarifying what it's all about.

The first thing is that some articles are just plain wrong in their documentation about this option, for example the Arduino Playground docs for FreeBSD state that it's required to upload our binary to the Arduino board:

This is pretty wrong, since in avr-objcopy the -R switch actually specifies sections to be removed from the binary - per the manpage below:

So let's investigate what's up - here's an avr-objdump on the binary produced by blink.c:

There's actually no .eeprom section here to be removed - so nothing in binary would end up there anyway. If we define a variable with the necessary attribute "__section__" we can force something to be placed here, like the following string:

char *foo __attribute__((__section__(".eeprom"))) = "bar";

and now if we rebuild our ELF binary and inspect using avr-objdump:

All of a sudden we've got an .eeprom section in our executable, which would be removed if we followed the default instructions.

So why is this removed by default when uploading an Arduino sketch? I can only guess, but presumably it's just a common courtesy to anyone who uses .eeprom as persistent runtime storage - so they don't accidentally hose their data when programming their board. Hopefully if you know enough to place a variable in .eeprom, then you'd have the know-how to be able to alter the build configuration to omit these flags. It could also be possible that the eeprom wears out after fewer writes than internal flash so a bit of extra care is taken to preserve it - though I really cannot say why.

How is Arduino memory laid out and what controls it?

I was curious about how the memory layout is defined in the avr-gcc toolchain used in building programs for the Arduino, and ow it can be controlled. I've played around with LDF files for Analog Devices' Blackfin and SHARC chips (which look like ADSP-BF533.ldf and ADSP-21160.ldf respectively), but have never used GCC's linker scripts before.

On OS X using CrossPack these linker scripts reside in /usr/local/CrossPack-AVR/avr/lib/ldscripts, and there are an astonishing 95 of them in total:

These linker scripts aren't defined per microprocessor (i.e. atmega328, atmega168), but are written per architecture - taking a quick look at the documentation at nongnu.org we can see that the AVR chips used in current Arduino boards are all in the avr5 family (except for the atmega2560, which is avr6) so we can narrow things down slightly, but there are still five linker scripts to choose from:

Which one of these is used depends on the arguments passed to the linker, and each of them should be different and tailored for a certain purpose - although in practice this is not strictly true:

  • avr5.x: default linker script, this is used for linking straight C programs
  • avr5.xn: linker script used when the "-n" flag is passed to the linker. According the the ld manpage this flag is used to turn off page alignment and mark the output as "NMAGIC" however diffing this and avr5.x shows no difference in practice.
  • avr5.xbn: used when the "-N" flag is passed to ld. It's meant to mark the .text and .data sections to be readable & writeable (as well as not page-aligning the data segment apparently) however again this is script is identical to avr5.x.
  • avr5.xr: used when "-r" flag passed to ld. This is to "generate relocatable output" - but essentially it's used to pre-link C++ files I think?
  • avr5.xu: used when "-Ur" is passed to ld. This is used for the final link on C++. It's roughly the same as avr5.xr but includes something about ctor table

To see what the memory layout the basic linker script produces, we can take a pretty boring program like blink.c and take it apart using avr-objdump. The GCC docs have a nice wee visualisation of the SRAM usage of an atmega128, which won't be a million miles away from the 328p:

So the SRAM contains Data, BSS (a segment containing variables initialised to zero) and then some space for the heap (which starts at the address __heap_start and continues to a handful of bytes before the stack base) and the stack begins at RAMEND (address 0x8FF on Atmega328p) and grows down as necessary.

This is why you get that little "low memory" warning in the Arduino IDE - if there's only a couple hundred bytes spare in SRAM it's not too hard to accidentally write a program that uses too much stack and ends up overwriting some of heap variables. There's no memory protection (that I'm aware of) on these chips so there's no way to prevent this other than being careful.

How quickly can we access the Arduino's SRAM, Program Memory and EEPROM?

I wrote a couple of simple benchmarks to see how quickly we can access the different memory types available on the Arduino:

  • read: read and sum a 256 byte array
  • write: write the values 0...255 into a 256 byte array

This is not massively scientific but I just wanted ballpark figures to see how things stacked up. The results I got were:

memory read (μs)
write (μs)
data 148 148
program 196 n/a
eeprom 584 859580

I was pretty surprised by how fast reads from program memory could be, and even more so how quick eeprom reads were - but slow eeprom writes aren't particularly surprising. It's worth noting that for program memory at runtime you can only write to certain sections reserved for the bootloader and I want to risk messing that up so I left it out.