Thursday 15 March 2012

Wireless sensor node - getting started with an SD card (12)

A couple of months ago I bought an SD card breakout board. The intention was that this would allow my Arduino base station to log to memory card to provide a persistent record of the sensor readings. However, apart from soldering on some male headers, I haven't done anything with the SD card breakout until now.

To prepare for writing some code to use the SD card I assembled a neat new layout on the breadboard attached to my Arduino Uno. 


The next step was to write code to use the SD card. I used the Lady Ada tutorial to get started. I used the CardInfo sketch to validate that I could access the SD card and the list files sketch to demonstrate using the file system. 

With this complete I set about integrating the SD card code into my wsn_arduino sketch. This seemed fairly straightforward until I tried to actually use the code at which point I started hitting mysterious crashes! :(

It turns out that the SD card library is a memory hog and I was already running low on RAM before adding the SD card code. I didn't think my existing code was using much RAM until I found out that string constants e.g. those used in Serial print statements are all stored in RAM! Argh!

Reducing Memory Used by String Constants

Firstly, it is important to be able to measure the amount of free memory so that we can see how badly/well we are doing. This can be done using the FreeMem method from SdFatUtil.h

  #include <SD.h>
  ...
  Serial.print("Free RAM: ");
  Serial.println(FreeRam());

So what can we do to reduce this memory usage? The answer is to use the PROGMEM keyword. This instructs the compiler to store a variable in the much larger program memory space. However, using the keyword initially looks quite awkward. 

The article describing its use suggests that you store strings in explicit variable and copy them into a RAM buffer before use. This is really unwieldy when you just want to print a string to the console!

The solution is to use the PSTR macro in the avr/pgmspace.h header. This has the following definition. 

  #define PSTR(s) ((const PROGMEM char *)(s))

This marks a string constant for storage in program memory without having to use a separate variable. However, you now need to use a different print method to access the string. Conveniently the SD card header SdFatUtils.h has just what we need in the form of PgmPrint and PgmPrintln.

  #include <avr/pgmspace.h> 
  #include <SD.h>
  ...
  PgmPrint("Free RAM: ");
  Serial.println(FreeRam());

The other method which I needed to use was one from pgmspace.h.

  strcmp(stringData, "dbg_fls_f")

becomes

  strcmp_P(stringData, PSTR("dbg_fls_f"))

The result of using PgmPrint and strcmp_P+PSTR for my sketch was the following:
  • Free RAM: 393 (Before)
  • Free RAM: 925 (After)
A couple of other points to note:
  • It is vital that all opened Files are closed when you are done with them as each one takes up ~68 bytes of memory while open. 
  • Arduino 1.0 includes support for a new F() macro which allows you to use Serial.print directly. This looks like the following.
  Serial.print(F("string"));

As far as I can tell this has the same effect as using PgmPrint. The advantage is that F() is included in the base Arduino headers whereas PgmPrint is only available in the Sd library header. I have stuck with PgmPrint as I slightly prefer the shorter syntax.

SD vs SdFat

When I started out this article I was following the Lady Ada tutorial which pointed me at the SD library. Unfortunately I ended up hitting a bug where I could both list files and write to a file but listing files after writing to a file didn't work. This prompted me to investigate the SD library a bit further and properly understand that it was acting as a wrapper for the SdFat library.

SdFat has been updated more recently than SD and comes with a better range of examples. For both of these reasons I switched my code to use SdFat and have subsequently managed to get everything working nicely.