Chapter 5: Coding and Memory Handling
Whenever we talk about programming, an essential part is to understand the tools available for us to use within the language we are working with. Every language features their own set of tools which make the preferable for a certain niche of programming. For instance, for statistical and data analysis, people prefer the ‘R programming language.’ For machine learning and working with neural networks, people prefer ‘Python,’ and in some cases, both Python and C++ can be used to create the same instruction set. However, it all boils down to which language the programmer is comfortable using. The reason why programmers have a preference when it comes to programming languages is because of the tools they are packing. These tools are essentially the libraries that feature classes, objects, and functions that enable the programmer to create a useful application. In short, libraries extend the usefulness of a programming language as a whole and Arduino is no exception (since it uses C++ as the base programming language).
In addition, coding requires that the user also accounts for any complications and other aspects which arise when software interacts with hardware. One of the most important aspects to be wary of when writing any program is its memory and error handling capabilities. In Arduino, error handling isn’t emphasized as much as memory handling because the programs have fewer chances of encountering an error as compared to applications designed for PCs. A good memory handling program can execute more efficiently,
consuming less RAM all whilst running smoothly and fast. This becomes even more important when we are working on a resource-intensive project and is required to deliver a level of performance that is well over the capabilities of the Arduino’s chipset itself. On the Arduino board, there a total of three memory types which have been listed below;
- Random Access Memory
- Program Memory
- EEPROM
This chapter will discuss how to use libraries, create our own, and even modify existing libraries. In the latter half, we will cover different memory handling techniques to make sketch programs more efficient without relying heavily on RAM.
Using the Libraries
The Arduino distribution comes with a collection of libraries for users to implement in their sketch programs from the get-go. Using a library that is by default included in the IDE is very simple. All we have to do is go to the ‘Sketch
’ menu and in the ‘Import Library
’ section, we will find all of the libraries currently available in the Arduino IDE. Apart from the ones that come with the distribution, we can install additional libraries as well. These libraries will show up on the same menu and will be separated from the default ones by a line.
To use a library, we simply go to the ‘Import Library
’ option and choose the one we want to use for the current sketch program. If we know the name of the library, we can also directly add it to our sketch program by using the ‘include’
header as shown below;
#include <nameOfTheLibrarySelected.h>
In this way, all of the functions, methods, classes, objects, and commands specified within this library become available for use in the current coding session. Here’s a list of the libraries that you’ll find come with the Arduino distribution.
-
EEPROM
; this library contains functions and classes that allow the user to save data within the EEPROM memory of the board and read it. The data stored within this memory is not
lost even when the power is turned off.
-
Ethernet
; this library features elements that allow the user to code a sketch program capable of interacting with the Arduino board's Ethernet shield.
-
Firmata
; this library contains protocols to facilitate serial communication. It also includes protocols that help the program to control the functioning of the board as well.
-
LiquidCrystal
; this library allows the sketch program to be able to control the LCD display connected to the Arduino board.
-
Matrix
; this library is primarily used to manipulate LED displays being used with the Arduino board.
-
SD
; this library features functions that allow the program to perform read and write operations on an SD card.
-
Servo
; this library is used when the Arduino board is connected to a ‘Servo motor’ so that it can control its functioning.
-
SoftwareSerial
; this library is used to enable additional serial ports on the Arduino board.
-
SPI
; this library is primarily used for controlling the Ethernet as well as any SPI hardware connected to the board.
-
Sprite
; this library allows the program to use ‘sprites’ on a matrix LED connected to the Arduino board.
Many more libraries are available with the Arduino distribution which unlock its full potential when used, making the hardware more versatile and compatible with many projects.
Installing Additional Libraries
As we discussed before, we are not limited to only using the libraries that come with the Arduino distribution, we can install additional libraries as well. To install a library in the IDE software, we first need to download it. Suppose the downloaded file is in the form of a .’zip’ file. In that case, we will first need to unzip and then relocate the folder containing the library files to the ‘libraries
’ directory found within the ‘Arduino Documents
’ folder. If you don’t know where this root folder is, you can simply go to the Arduino IDE and under the ‘Sketch
’ menu, go to the ‘Show Sketch Folder
’ menu. This will bring us to Arduino’s sketch folder and from here, we simply go to the root directory and this will lead us to the ‘Arduino Documents
’ folder. There’s a good chance that this will be the first time you are adding a library to the IDE, if that’s the case, then a ‘libraries
’ folder might not even exist. In this scenario, just create a folder named ‘libraries
’ by yourself and then put all of the libraries you want to add to this folder.
Once you add a library, it will show up when the IDE is booted. This is because the Arduino IDE scans its directory and checks if anything was added when it is being booted. However, if the IDE is running and the libraries are added, then they will not show up unless the IDE is closed and launched again.
Modifying Libraries
Users have the option to make changes to libraries as well if they want to add certain functionalities needed for their projects. For instance, if we download a library named ‘TimeAlarms
,’ we can use its methods and classes to program an alarm on the Arduino board. However, this library has a limitation: it only allows a total of 6 alarms to be set at any time. Let’s say that we need more than six alarms for our project. In such a scenario, it is much better and easier to modify the library itself rather than looking for one that supports more than 6 alarms.
The following sketch program is using the ‘TimeAlarms
’ library. In this demonstration, we are specifying 7 alarms instead of 6 to show the error it will trigger.
/*
multiple_alarms sketch
has more timer repeats than the library supports out of the box -
you will need to edit the header file to enable more than 6 alarms
*/
#include <Time.h>
#include <TimeAlarms.h>
int currentSeconds = 0;
void setup()
{
Serial.begin(9600);
// create 7 alarm tasks
Alarm.timerRepeat(1, repeatTask1);
Alarm.timerRepeat(2, repeatTask2);
Alarm.timerRepeat(3, repeatTask3);
Alarm.timerRepeat(4, repeatTask4);
Alarm.timerRepeat(5, repeatTask5);
Alarm.timerRepeat(6, repeatTask6);
Alarm.timerRepeat(7, repeatTask7); //7th timer repeat
}
void repeatTask1()
{
Serial.print("task 1 ");
}
void repeatTask2()
{
Serial.print("task 2 ");
}
void repeatTask3()
{
Serial.print("task 3 ");
}
void repeatTask4()
{
Serial.print("task 4 ");
}
void repeatTask5()
{
Serial.print("task 5 ");
}
void repeatTask6()
{
Serial.print("task 6 ");
}
void repeatTask7()
{
Serial.print("task 7 ");
}
void loop()
{
if(second() != currentSeconds)
{
// print the time for each new second
// the task numbers will be printed when the alarm for that task is
triggered
Serial.println();
Serial.print(second());
Serial.print("->");
currentSeconds = second();
Alarm.delay(1); //Alarm.delay must be called to service the alarms
}
}
Once this sketch program is uploaded to the Arduino board, it will start executing. But we won’t do that just yet because we know it will not work. Upon compiling and running the sketch program, the Serial Monitor will display each task's output for a total of 9 seconds. Each second, different tasks will be performed. The result is as shown below;
1->task 1
2->task 1
task 2
3->task 1
task 3
4->task 1
task 2 task 4
5->task 1
task 5
6->task 1
task 2 task 3 task 6
7->task 1
8->task 1
task 2 task 4
9->task 1
task 3
If we carefully analyze this result, then we can see that the task scheduled at the 7th
second did not execute. This is because we used a 7th
timer object even though the library only has 6 timer objects. To make this sketch work, we will have to open the library to modify it. To do this, we simply open the library we want to modify using a text editor. There’s no specific text editor application which is mandatory to install, even the default text editors that come with the Operating System are more than enough. Find the library you want to modify in the ‘libraries
’ folder and then open it with the text editor. If you are running Windows, you can use ‘Notepad’ and if you are using Mac, you can use the ‘TextEdit’ application. Once in the libraries folder, we need to find the ‘TimeAlarms.h
’ file and open it using the desired text editor.
Once we open the header file using the text editor, the very first lines will be like this;
#ifndef TimeAlarms_h
#define TimeAlarms_h
#include <inttypes.h>
#include "Time.h"
#define dtNBR_ALARMS 6
If we look at the very last line, we will see the number of timer objects defined. To add support for 7 alarms instead of 6, we simply need to change the value assigned to ‘dtNBR_ALARMS’
accordingly. Since we only want 7 alarms, we will assign it a value of ‘7.’
Once the modifications are done, we need to save the file to make the changes permanent and close it. Now, we run the lines of code in the Arduino IDE again and this time, instead of the 7th
-second task being missed, it will be properly executed this time.
1->task 1
2->task 1 task 2
3->task 1 task 3
4->task 1 task 2 task 4
5->task 1 task 5
6->task 1 task 2 task 3 task 6
7->task 1 task 7
8->task 1 task 2 task 4
9->task 1 task 3
The result displayed by the Serial Output Monitor this time clearly shows us that the task scheduled at the 7th
second succeeded this time whereas it failed when we used the original library.
However, modifications made to the library are not for free, on the contrary, they come at a cost that can be sometimes quite hefty to
pay for the board’s resources. For instance, the type of change we made to this library will ultimately consume more system resources and this consumption will affect the resources available for the rest of the program. But not everything is doom and gloom. In fact, we can use this to our advantage as well. By carefully structuring the sketch program, we can identify the portion that needs more system memory (RAM) and the portion that does not need it. Since the requirement is imbalanced, we can modify certain elements of the sketch program accordingly. For instance, we can selectively decrease the memory allocated to the ‘serial library’ being used in the program, this will increase the memory resource available to the rest of the program. Similarly, we can increase the memory resource available to a library being used in the program if the other code lines don’t require as much RAM. Hence, when creating a sketch program, one should always be wary of system requirements to take appropriate measures. Otherwise, the code execution will have problems.
Creating a Library
Generally, the reason why users would want even to create libraries is not to invent new functions and capabilities within the programming language, instead, this method is a pretty convenient way of sharing a block of code or reusing it in other programs.
This section will create a library from a sketch program example, which will be done without using any classes.
The sketch we will be using for this demonstration is given below;
/*
* blinkLibTest
*/
const int firstLedPin = 3; // choose the pin for each of the LEDs
const int secondLedPin = 5;
const int thirdLedPin = 6;
void setup()
{
pinMode(firstLedPin, OUTPUT); // declare LED pins as output
pinMode(secondLedPin, OUTPUT); // declare LED pins as output
pinMode(thirdLedPin, OUTPUT); // declare LED pins as output
}
void loop()
{
// flash each of the LEDs for 1000 milliseconds (1 second)
blinkLED(firstLedPin, 1000);
blinkLED(secondLedPin, 1000);
blinkLED(thirdLedPin, 1000);
}
We will now take out the ‘blinkLED()
’ function from the sketch program and copy it over to a different file and name it ‘blinkLED.cpp
.’
/* blinkLED.cpp
* simple library to light an LED for a duration given in milliseconds
*/
#include <WProgram.h> // Arduino includes
#include "blinkLED.h"
// blink the LED on the given pin for the duration in milliseconds
void blinkLED(int pin, int duration)
{
digitalWrite(pin, HIGH); // turn LED on
delay(duration);
digitalWrite(pin, LOW); // turn LED off
delay(duration);
}
All that’s left to do is create a header file with the same name as the .’cpp’ file, in this case, the name of the header file would be ‘blinkLED.h
.’ The contents of this header file are shown below;
/*
* blinkLED.h
* Library header file for BlinkLED library
*/
void blinkLED(int pin, int duration); // function prototype
Once done, we put the ‘blinkLED.ccp’ file and the ‘blinkLED.h’ into a folder of the same name and place it in the ‘libraries
’ folder. Now we are ready to use the functions defined within this code for any sketch programs.
We can even modify a library that we create to extend its functionalities further as well. In this case, as soon as we use the library in a blank sketch program, all three of the LEDs on the Arduino board will start flickering. But by adding in a few things, we can change how the library works as well. For instance, we can add a quality of life improvement in this library by including constants that define each blink's delays. This will allow users to refer to the constant value when changing the delay between each blink of the LED light instead of having to work with values in milliseconds. The first option is more convenient and easy to deal with. To make this modification, we first need to open the header file and inside the header file, we need to add the following lines;
/*
* blinkLED.h
* Library header file for BlinkLED library
*/
void blinkLED(int pin, int duration); // function prototype
After that, we open the ‘blinkLED.ccp’ file and change the lines of code present in the ‘void loop()
’ section as shown below;
void loop()
{
blinkLED(firstLedPin, BLINK_SHORT);
blinkLED(secondLedPin, BLINK_MEDIUM);
blinkLED(thirdLedPin, BLINK_LONG);
}
In this way, we assigned the constant values a variable that effectively describes the effect of the value upon the delay in the LED's blinking lights. When we export the sketch file to the Arduino board, we will see that the light will blink really fast at the start, then the intervals between each blink will be longer than before and in the final blink, the delay between the on and off intervals will be the longest.
We can also add different functions to this library as well which were not previously present. For instance, we can specify the number of times an LED should blink when the code is executed. This has been demonstrated in the following lines of code;
void loop()
{
blinkLED(firstLedPin,BLINK_SHORT,5 ); // blink 5 times
blinkLED(secondLedPin,BLINK_MEDIUM,3 ); // blink 3 times
blinkLED(thirdLedPin, BLINK_LONG); // blink once
}
If we want the library to include such functionality, we will have to
make changes to both the ‘blinkLED.h’ and ‘blinkLED.cpp’ files. First things first, we open the header file using a text editor and include the function prototype in it;
/*
* BlinkLED.h
* Header file for BlinkLED library
*/
const int BLINK_SHORT = 250;
const int BLINK_MEDIUM = 500;
const int BLINK_LONG = 1000;
void blinkLED(int pin, int duration);
// new function for repeat count
void blinkLED(int pin, int duration, int repeats);
Next, we open the ‘blinkLED.ccp’ file and include the function corresponding to the prototype added in the header file.
/*
* BlinkLED.cpp
*/
#include <WProgram.h> // Arduino includes
#include "BlinkLED.h"
// blink the LED on the given pin for the duration in milliseconds
void blinkLED(int pin, int duration)
{
digitalWrite(pin, HIGH); // turn LED on
delay(duration);
digitalWrite(pin, LOW); // turn LED off
delay(duration);
}
/* function to repeat blinking
void blinkLED(int pin, int duration, int repeats)
{
while(repeats)
{
blinkLED(pin, duration);
repeats = repeats -1;
}
}
Creating Libraries from Other Libraries
While this may sound confusing when read out loud, but it’s not that complicated at all. Just as we used a chunk of code to create a library, we can also use existing libraries as a part of another library. In other words, we can take an entire library, or one portion of it and then extend it to create a different yet similar library. This can be done with multiple libraries as well, just as how you would make a cocktail. One possible use of this technique would be to build a library to help us in debugging. We will be using this library to dispatch a print command and the result would be displayed on another Arduino board. This is possible through a library known as ‘Wire Library
.’
The sketch program we will be using to demonstrate this task will be using two main components;
However, for this demonstration to work, we need two Arduino boards and these boards must be connected to each other through an ‘I2C
’ connection. We will discuss I2C serial port connections some other time, for now, let’s focus on the main concept, which is to use the wire library to create another library (which will have a bit of salvaged code as well). We will be using appropriate code lines for handling the I2C connection and include it in the new library we will be creating.
Let’s name the folder where we will be placing the library as ‘i2cDebug
’ (reminder, every library folder which has a header and .ccp files need to be placed within the root directory of ‘libraries
’). First, let’s build a header file for the corresponding library;
/*
* i2cDebug.h
*/
#include <WProgram.h>
#include <Print.h> // the Arduino print class
class I2CDebugClass : public Print
{
private:
int I2CAddress;
byte count;
void write(byte c);
public:
I2CDebugClass();
boolean begin(int id);
};
extern I2CDebugClass i2cDebug; // the i2c debug object
Now that we have a header file, we now need a .’ccp
’ file to place it alongside the header file in the ‘libraries
’ directory inside the ‘i2cDebug
’ folder. The contents of the .’ccp
’ file will be as follows;
/*
* i2cDebug.cpp
*/
#include <i2cDebug.h>
#include <Wire.h> // the Arduino I2C library
I2CDebugClass::I2CDebugClass()
{
}
boolean I2CDebugClass::begin(int id)
{
I2CAddress = id; // save the slave's address
Wire.begin(); // join I2C bus (address optional for master)
return true;
}
void I2CDebugClass::write(byte c)
{
if( count == 0)
{
// here if the first char in the transmission
Wire.beginTransmission(I2CAddress); // transmit to device
}
Wire.send(c);
// if the I2C buffer is full or an end of line is reached, send the data
// BUFFER_LENGTH is defined in the Wire library
if(++count >= BUFFER_LENGTH || c == '\n')
{
// send data if buffer full or newline character
Wire.endTransmission();
count = 0;
}
}
I2CDebugClass i2cDebug; // Create an I2C debug object
Once we have created both the .’h’ and .’ccp’ files, we simply place them in their appropriate folder inside the ‘libraries
’ directory. We now have created an entirely new library from the ‘Wire library
’ and some lines of code (to handle I2C connections). Let’s now load the following sketch program which will be using the ‘i2cDebug
library
’ to perform its core functions.
/*
* i2cDebug
* example sketch for i2cDebug library
*/
#include <Wire.h> // the Arduino I2C library
#include <i2cDebug.h>
const int address = 4; //the address to be used by the communicating devices
const int sensorPin = 0; // select the analog input pin for the sensor
int val; // variable to store the sensor value
void setup()
{
Serial.begin(9600);
i2cDebug.begin(address);
}
void loop()
{
// read the voltage on the pot(val ranges from 0 to 1023)
val = analogRead(sensorPin);
Serial.println(val);
i2cDebug.println(val);
}
Memory Handling
This section will discuss how to implement memory handling techniques to ensure that our sketch program will never have to work with insufficient RAM and sacrifice performance. Before we can even determine how much memory we have to handle carefully, we first need to know how much memory is even allocated to the sketch program in the first place. This can be done by using the ‘memoryFree()
’ function as shown in the following demonstration;
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.print(memoryFree()); // print the free memory
Serial.print(' '); // print a space
delay(1000);
}
// variables created by the build process when compiling the sketch
extern int __bss_end;
extern void *__brkval;
// function to return the amount of free RAM
int memoryFree()
{
int freeValue;
if((int)__brkval == 0)
freeValue = ((int)&freeValue) - ((int)&__bss_end);
else
freeValue = ((int)&freeValue) - ((int)__brkval);
return freeValue;
}
This program uses the function ‘memoryFree()’ to validate the total system memory available for use. In turn, the function performs this task by utilizing a special type of variable known as ‘system variables
.’ System variables are not generally used in programs because the purpose of these variables is to facilitate the IDE’s compiler in managing the internal resources. As the program is undergoing the execution process, the number of bytes it stores in the system memory constantly changes. Thus, it is important to make sure that the program only uses as much memory as is available and not more than that.
The most common elements that drain memory have been listed below;
- During the initialization of constants. For example;
#define ERROR_MESSAGE "an error has occurred"
- During the declaration of global variables. For example;
char myMessage[] = "Hello World";
- When a function is called. For example;
void myFunction(int value)
{
int result;
result = value * 2;
return result;
}
- During memory allocation. For example;
String stringOne = "Arduino String";
Using lines of code that increasingly incorporate more of the elements listed above will steadily and surely drain the system memory to its last drop. In addition, it is also recommended to be wary of the global variables because these are declared most commonly in libraries. Setting a parameter on which we base the declaration of variables can also backfire because the number of variables might increase to the point where the majority of the system memory is used up during code execution.
Saving and Fetching Numeric Values from Program Memory
It is not possible to save everything in the system memory of the Arduino board. Moreover, we want to have the maximum amount of free RAM available whenever possible. In such cases, we can use the board’s flash memory (also known as the program memory) to do this. This section will discuss how we can store numeric values in the system’s program memory.
In the sketch, we will demonstrate functions in a way such that it makes adjustments to the fading away of the LED light according to our non-linear sensitivity perception. The sketch program will be using a total of 256 values and these values are in the form of a table. Moreover, this entire bundle of data is stored within the system’s program memory instead of the RAM.
When we execute this sketch, the light transitioning from bright to completely fading away is extremely smooth. This sketch works on the LED connected to the pin 5 terminal and this animation can be compared to the LED connected to the pin 3 terminal where the
latter has abrupt and hasty fade animations.
The sketch for storing 256 constant numeric values in the system’s program has been shown below;
/* ProgmemCurve sketch
* uses table in Progmem to convert linear to exponential output
*/
#include <avr/pgmspace.h> // needed for PROGMEM
// table of exponential values
// generated for values of i from 0 to 255 -> x=round( pow( 2.0, i/32.0) - 1);
const byte table[]PROGMEM = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5,
5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7,
7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10,
10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15,
15, 15, 16, 16, 16, 17, 17, 18, 18, 18, 19, 19, 20, 20, 21, 21,
22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30,
31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, 40, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 58, 59, 60, 62,
63, 64, 66, 67, 69, 70, 72, 73, 75, 77, 78, 80, 82, 84, 86, 88,
90, 91, 94, 96, 98, 100, 102, 104, 107, 109, 111, 114, 116, 119, 122, 124,
127, 130, 133, 136, 139, 142, 145, 148, 151, 155, 158, 161, 165, 169, 172, 176,
180, 184, 188, 192, 196, 201, 205, 210, 214, 219, 224, 229, 234, 239, 244, 250
};
const int rawLedPin = 3; // this LED is fed with raw values
const int adjustedLedPin = 5; // this LED is driven from table
int brightness = 0;
int increment = 1;
void setup()
{
// pins driven by analogWrite do not need to be declared as outputs
}
void loop()
{
if(brightness > 255)
{
increment = -1; // count down after reaching 255
}
else if(brightness < 1)
{
increment = 1; // count up after dropping back down to 0
}
brightness = brightness + increment; // increment (or decrement sign is minus)
// write the brightness value to the LEDs
analogWrite(rawLedPin, brightness); // this is the raw value
int adjustedBrightness = pgm_read_byte(&table[brightness]); // adjusted value
analogWrite(adjustedLedPin, adjustedBrightness);
delay(10); // 10ms for each step change means 2.55 secs to fade up or down
}
When working with a large expression of values that iterate over a regular interval, it is better to simply calculate these values before-hand and store them in the form of an array. In this way, when the code is executed, it doesn’t have to calculate each value repeatedly. This is not only time-efficient, making the program to run faster, but also memory efficient as well because if the values were to be calculated multiple times, then the result would be stored in the system’s RAM. Instead of doing that, we calculated the values before-hand and stored the resulting array in the program memory, hence saving Arduino’s RAM resource from being completely drained by this program. At the beginning of the sketch program, we first and foremost define the table used to store the values. This table is created through the following line;
const byte table[ ]PROGMEM = {
0, . . .
The ‘PROGMEM
’ expression is used to inform the compiler that the values written henceforth are to be saved in the board’s program memory rather than the board’s system memory. The values are then defined in the form of an array. To use the ‘PROGMEM’ argument, the sketch program needs the corresponding definitions
as well, otherwise, the compiler won’t be able to understand this instruction. For this purpose, we include a header file with the name of ‘pgmspace.h
’ in the libraries folder and then import it into the program using the ‘#include
’ argument.
The overall brightness of the LED light is controlled using the following lines of code in the sketch program shown above to ensure a smooth fading transition;
int adjustedBrightness = pgm_read_byte(&table[brightness]);
analogWrite(adjustedLedPin, adjustedBrightness);
This entire expression can also be written in the following way as well;
pgm_read_byte(table + brightness);
Let’s see another demonstration as well.
/* ir-distance_Progmem sketch
* prints distance & changes LED flash rate depending on distance from IR sensor
* uses progmem for table
*/
#include <avr/pgmspace.h> // needed when using Progmem
// table entries are distances in steps of 250 millivolts
const int TABLE_ENTRIES = 12;
const int firstElement = 250; // first entry is 250 mV
const int interval = 250; // millivolts between each element
// the following is the definition of the table in Program Memory
const int distanceP[TABLE_ENTRIES] PROGMEM = { 150,140,130,100,60,50,
40,35,30,25,20,15 };
// This function reads from Program Memory at the given index
int getTableEntry(int index)
{
int value = pgm_read_word(&distanceP[index]);
return value;
}
If you recall the beginning of this book, you’ll find that we discussed a similar experiment by using the LDR sensor instead of an Infrared Sensor. However, the difference is that instead of measuring the light intensity being emitted by the LED, we measure the distance of the light coming from the LED and reaching the Infrared sensor. In the case of the LDR experiment, we assigned different values that correspond to the different intensities of the LED light. Similarly, we will assign values in this program as well but these values will be stored in the board’s flash memory instead of RAM because all of these values are constant (meaning, the values themselves won’t change). To do this, we use the function ‘PROGMEM
.’
The distance is measured and interpreted by using the function ‘getDistance()
’ and the ‘getTableEntry()
function is used by the
program to fetch the corresponding values from the program memory and not from the RAM.
int getDistance(int mV)
{
if( mV > interval * TABLE_ENTRIES )
return getTableEntry(TABLE_ENTRIES-1); // the minimum distance
else
{
int index = mV / interval;
float frac = (mV % 250) / (float)interval;
return getTableEntry(index) - ((getTableEntry(index) -
getTableEntry(index+1)) * frac);
}
}
Saving and Fetching Strings Using the Program Memory
Another element in coding that eats up RAM greedily is strings. To conserve memory space, we will move the string constants being used in the sketch program to be stored in the system’s program
memory.
The sketch shown below generates a string directly in the flash memory (program memory) of the Arduino board. When the code is executed, the strings are fetched from the program memory and the resulting text is displayed in the IDE’s serial monitor. In addition, the sketch also has code that tells the free RAM available in the system for use;
#include <avr/pgmspace.h> // for progmem
//create a string of 20 characters in progmem
const prog_uchar myText[] = "arduino duemilanove ";
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.print(memoryFree()); // print the free memory
Serial.print(' '); // print a space
printP(myText); // print the string
delay(1000);
}
// function to print a PROGMEM string
void printP(const prog_uchar *str)
{
char c;
while((c = pgm_read_byte(str++)))
Serial.print(c,BYTE);
}
// variables created by the build process when compiling the sketch
extern int __bss_end;
extern void *__brkval;
// function to return the amount of free RAM
int memoryFree()
{
int freeValue;
if((int)__brkval == 0)
freeValue = ((int)&freeValue) - ((int)&__bss_end);
else
freeValue = ((int)&freeValue) - ((int)__brkval);
return freeValue;
}
Each character in a string takes up about one byte of memory space and thus, a large number of strings can easily gobble up a system’s RAM very easily. We used the same ‘PROGMEM
’ expression to move the string constants to the system’s program memory just as how we used it to move numeric constants in the previous section. To use this function, we need to include the header file which includes the corresponding definitions.
#include <avr/pgmspace.h> // for progmem
Strings that are being declared in the system’s program memory have a slightly different syntax which is as follows;
const prog_uchar myText[] = "arduino duemilanove "; //a string of 20 characters
in progmem
We can also leverage the functionality of preprocessors when declaring the strings. This not only creates all of the strings before the code in the program even starts executing. Moreover, the syntax used for declaring strings by using preprocessors is easier and simpler as shown below;
#define P(name) const prog_uchar name[] PROGMEM // declare a PROGMEM string
Once the declaration has been done using the syntax shown above, all we have to do is use a short-expression ‘P(name)
’ and it will be automatically be replaced by the entire string it is linked to.
P(myTextP) = "arduino duemilanove "; //a string of 20 characters in progmem
Now let’s compare this approach to using the standard way of declaring and storing strings in the system RAM.
char myText[] = "arduino duemilanove "; //a string of 20 characters
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.print(memoryFree()); // print the free memory
Serial.print(' '); // print a space
Serial.print(myText); // print the string
delay(1000);
}
Upon executing this block of code, the ‘memoryFree()’ function will tell us that we only have a total of 20 bytes of free RAM space.
Optimizing Constant Values
This section will discuss an optimization technique that will help reduce the memory usage of integer values. This is done by utilizing the ‘#define
’ and ‘const
’ expressions to tell the compiler the values are to be interpreted as constants. Once the values are interpreted as constants, they can be declared in multiple ways choosing the one that fits the situation the best.
Here's an example showing how to declare integer values as constant by using the ‘const
’ argument.
If we were to declare the value traditionally, it would be like this;
Generally, it is recommended to use constant values wherever possible instead of just using standard integer values. Moreover, using references is also a really good practice because you might need to change the value of the integer at some point in program development. Then you will have to arduously look through the entire source code and figure out the values that also need to be adjusted when making such a change.
Three distinct approaches can define a constant integer value and each approach has been demonstrated below;
int ledPin = 13; // a variable, but this wastes RAM
const int ledPin = 13; // a const does not use RAM
#define ledPin 13 // using a #define
// the preprocessor replaces ledPin with 13
pinMode(ledPin, OUTPUT);
By looking at this block, you will notice that the first two approaches are quite similar at first glance but that’s not the case at all. In the first method, we are declaring the integer as a standard variable. Since this variable can be changed at any point in the code execution, a piece of memory is reserved in the RAM for it. In the second method, we are using the ‘const
’ argument to declare the integer as a constant variable. In this case, since the variable’s value is constant, this means that the value will not change during code execution, thus the compiler doesn’t need to reserve space for this variable in the system RAM.
Memory Handling through Conditional Compilation
There are cases when programmers keep multiple versions of the same source code for different purposes. For instance, the source code used for debugging will be a different version as compared to the source code being executed as a program on the Arduino board. Similarly, some programmers even create different versions of the same sketch program to ensure that their program is compatible with different Arduino boards (since each Arduino board has slightly different specifications, like system memory, processor speed, etc.). This is an advanced practice that helps developers a lot throughout their program’s developmental stages and debugging stages as well.
To make this possible, we use conditionals during the compilation process. These conditionals mainly act on the compiler’s preprocessor and directly interfere with how the compiler builds the sketch program. Thus, we can manipulate the way the program is built.
Let’s take a look at a sketch example. The example you are about to see is strictly compatible with only Arduino boards that have been released after version ‘0018.’ In other words, boards of versions ‘0018’ and older will not support this sketch.
#if ARDUINO > 18
#include <SPI.h> // needed for Arduino versions later than 0018
#endif
Let’s now look at the sketch example which will use the ‘if’ conditional statement to determine if we want to use the program for debugging purposes. If the conditional is true, then the ‘DEBUG
’ function defined in the header file will be executed.
/*
Pot_Debug sketch
blink an LED at a rate set by the position of a potentiometer
Uses Serial port for debug if DEBUG is defined
*/
const int potPin = 0; // select the input pin for the potentiometer
const int ledPin = 13; // select the pin for the LED
int val = 0; // variable to store the value coming from the sensor
#define DEBUG
void setup()
{
Serial.begin(9600);
pinMode(ledPin, OUTPUT); // declare the ledPin as an OUTPUT
}
void loop() {
val = analogRead(potPin); // read the voltage on the pot
digitalWrite(ledPin, HIGH); // turn the ledPin on
delay(val); // blink rate set by pot value
digitalWrite(ledPin, LOW); // turn the ledPin off
delay(val); // turn LED off for same period as it was turned on
#if defined DEBUG
Serial.println(val);
#endif
}