Whenever you are going to write some “larger” programs on Arduino which usually has a ATmega328, ATmega168 or even ATmega8 micro-controller on board, you need to taking what’s written in the title into consideration. Especially with Atmega168 or ATmega8 which only has 512 byte SRAM, this is even more critical. I just suffered from this issue in a recent Arduino software project. The sketch used up too much SRAM and caused the board to reboot periodically. So I take sometime to find out good ways to decrease SRAM utilization.
Besides optimizing your algorithm and code logic to squeeze out some bytes free from SRAM, an easier way which needs no re-construction of your code is to take better use of available Flash memory space and EEPROM space on the chip. By putting the constant strings or structures in the Flash and EEPROM and loading them whenever you need to reference them, you can save considerable memory space in your Arduino programming. In my practical experience, this turns a program dying because of SRAM exhausting into life.
Let’s see how this will work. Assuming that you are going to let Arduino send a series of commands via serial UART periodically and you code like this:
#define CMD_COUNT 3 #define MAX_CMD_LEN 10 const char cmds[CMD_COUNT][MAX_CMD_LEN] = { "Command#1", "Command#2", "Command#3" }; void setup() { Serial.begin(57600); } void loop() { char cmd[MAX_CMD_LEN]; for (byte i = 0; i < CMD_COUNT; i++) { strcpy(cmd, cmds[i]); Serial.println(cmd); delay(1000); } }
This memory consumption of this program reported by avr-size is like this:
Program: 2352 bytes (7.2% Full)
Data: 226 bytes (11.0% Full)
The “Program” indicates Flash memory usage and “Data” indicates SRAM usage.
After changing the code to this:
#include <avr/pgmspace.h> #define CMD_COUNT 3 #define MAX_CMD_LEN 10 const char PROGMEM cmds[CMD_COUNT][MAX_CMD_LEN] = { "Command#1", "Command#2", "Command#3" }; void setup() { Serial.begin(57600); } void loop() { char cmd[MAX_CMD_LEN]; for (byte i = 0; i < CMD_COUNT; i++) { strcpy_P(cmd, cmds[i]); Serial.println(cmd); delay(1000); } }
There are 2 changes in the code. First, the PROGMEM directive is added in the array declaration which tells the compiler to store the data in Flash memory. Second, strcpy is replaced with strcpy_P. The strcpy_P() function is similar to strcpy() except that src is a far pointer to a string in program space which in most case is the on-chip Flash memory.
This time, the memory consumption reported by avr-size becomes:
Program: 2352 bytes (7.2% Full)
Data: 196 bytes (9.6% Full)
Obviously, with no change in program logic, you saved 30 bytes or 1.4% of ATmega328’s 1KB SRAM.
You can also relocate the constant data into EEPROM. Many people playing with Arduino ignore the existence of on-chip EEPROM. ATmega328 has 1KB and ATmega168 has 512 bytes EEPROM space. The life cycle of the EEPROM is usually limited for writing and erasing (100K times for ATMega series) but not for reading out. The code will be like this:
#include <avr/eeprom.h> #define CMD_COUNT 3 #define MAX_CMD_LEN 10 const char EEMEM cmds[CMD_COUNT][MAX_CMD_LEN] = { "Command#1", "Command#2", "Command#3" }; void setup() { Serial.begin(57600); } void loop() { char cmd[MAX_CMD_LEN]; for (byte i = 0; i < CMD_COUNT; i++) { eeprom_read_block(cmd, cmds[i], MAX_CMD_LEN); Serial.println(cmd); delay(1000); } }
The directive replacing PROGMEM is EEMEM, which tells the compiler that the data is stored in EEPROM and thus the addressing is changed to EEPROM address. The eeprom_read_block() read a block of specified bytes from EEPROM address to SRAM address.
The memory consumption:
Program: 2344 bytes (7.2% Full)
Data: 196 bytes (9.6% Full)
EEPROM: 30 bytes (2.9% Full)
The flash is freed for 8 bytes and EEPROM begins to be utilized. One thing to note is that you need to downloading EEPROM content to Arduino in additon to downloading the Flash content. AVRDUDE does that job perfectly for most Arduino boards.