Arrays and Memory - Beginning Arduino Programming (Technology in Action) (2011)

Beginning Arduino Programming (Technology in Action) (2011)

Chapter 8. Arrays and Memory

Arrays are an essential part of the Arduino programmer's toolbox. They are sometimes such a necessity that we have already thrown a few in our sketches and projects here and there. Arrays are essentially lists of variables that can contain multiple elements of the same data type. They can be used to store a list of pin numbers, sensor values, character strings, and even bitmaps for animations. While we could have introduced arrays earlier when we discussed variables and data types, the topic truly requires its own chapter to best understand how to use them. Because arrays can consume a large amount of the available memory on the Arduino microcontroller, we should also look at the types of memory space on the microcontroller chip and methods for how to access them, in addition to discussing how to declare arrays, accessing and using arrays, as well as using character and multidimensional arrays. But before tackling arrays in detail, let's jump in with our eighth project, Decision Machine, so that when we get to dicussing arrays in more depth you'll have already seen them in action.

What's needed for this chapter:

• Arduino Uno

• 16 x 2 character liquid crystal display HD44780 compatible

• ADXL335 accelerometer (SparkFun breakout)

• 5mm LED of any color

• 1x 220 ohm and 1x 2.2 kilohm У watt resistor or similar

• Hookup wires

• Solderless breadboard

Project 8: Decision Machine

Holding our completed project in your hands, you might ask it a simple question before giving it a gentle shake and at that moment a forecast will mystically appear from the murky depths of its display. If that sounds familiar, it's because we loosely found inspiration for this project in a classic, sometimes irreverent, fortune-telling icon. Our prototype design uses an accelerometer to recreate the familiar rotate or shake to activate the device, and an LCD to provide us with the short but erudite answers to our imagined yes-or-no questions. To make it interesting, we will use arrays throughout our project to give us something to talk about in this chapter and to demonstrate how arrays work in our code.

Hooking It Up

With only few components to hook up, this project is not overly complex, although you will need lots of hookup wires. To display our fortune, we are using a 16 x 2 backlit character liquid crystal display or LCD. Ours has a black background with bright white text for effect and can display two rows of 16 characters each. This display uses the venerable HD44780 interface controller that has been around for ages, making it super easy to display text using the Arduino. Our circuit is fairly standard, using six digital pins to interface with the LCD with two little differences. First, we need to connect a 2.2 kilohm resistor from the contrast pin marked V0 in the schematic to ground. This pin controls the contrast for the LCD and is usually connected to a potentiometer to allow for manually adjusting the contrast of the screen.

By using a single resistor, we keep things a little simpler. Secondly, to add theatricality, we are connecting the positive or anode pin of the LED backlight to PWM pin 11. With the analogWrite() function, we can fade the answer in and out of existence to simulate the smarmy answers emerging from the murky depths of the display. As usual, we could substitute many other versions of this LCD in different colors or even sizes, as long as it uses the same interface chip.

To detect the customary shake or rotation of the device after a question has been asked, we will use a 3-axis analog accelerometer, which fittingly enough is a device used to measure acceleration or the change in speed or movement along multiple axes. Our particular version is the ADXL335 from Analog Devices that provides an analog voltage corresponding to acceleration in the X, Y, or Z-axis. Because the chip is so small and not very breadboard friendly, we are using the breakout board available from SparkFun Electronics. This breakout board is the first device that needs pin headers or wires soldered to the device to work with our breadboards. For more on this topic, refer to the section on soldering in Chapter 12 With these in place, the connection is simple enough with an output from each of the three axes to three analog in pins marked A0, A1, and A2. Because this device runs on +3.3 volts we need to connect its positive power pin marked VCC to the Arduino interface board's 3.3V output pin. A number of other analog accelerometers would work just as well, but the code might need to be modified for fewer axes or if the accelerometer used some other communication protocol.

One last thing to be aware of is that to simplify wiring a little bit, we are making use of the ground bus on the side of the breadboard, usually marked by a blue line and a “-” sign. The neat thing about this row of pins is that they connect horizontally along the length of the board unlike the other pins that we have used so far. Not all breadboards have these, so you might need to adjust your wiring appropriately. Remember to take your time with the wiring to make sure the wires go to the correct pins. Figures 8-1 and 8-2 show how to hook up this project.

+5 VDC-

PIN 5 I >

PIN 6 I >

R1 2.2K

ИЛЛН

GND

+5VDC

V0

RS

R/W

E

NC

NC

NC

NC

DB4

DB5

DB6

DB7

LED+

LED-

NC

PIN A0

Z

PIN A1

Y

PIN A2

X

GND

+3.3 VDC

+3.3 VDC

ADXL335

GND

HD44780 16x2 LCD

GND

Figure 8-1. Decision Machine schematic

PINS 5, 6, 7, 8, 9, 10, 11

Figure 8-2. Decision Machine illustration

+3.3VDC GND PINS A0, A1, A2

Uploading the Source Code

While you might think this sketch is a bit intimidating and lengthy, in reality it's not too bad because most of it is white space. Half of the source code is tied up in listing the 20 possible answers that could be displayed each time the device is shaken or rotated. The rest builds on prior projects to come before it by using many of the functions and structures used in past sketches. We've stuck with some answers that might seem familiar as a starting point, but you should definitely make up your own answers. To get things to line up in the center of the screen, each answer is written as two lines of 16 characters using spaces as appropriate to center each line on the screen.

To compartmentalize our code a bit, we have created three new functions. The first function, named getReading(), reads the three analog inputs for each of the axes on the accelerometer. The second, oldReading(), keeps track of the last sensor readings so that we can determine if a threshold has been crossed. Finally, getAnswer() generates a random answer from the 20 possible, gets the message text to be displayed, and then sends that message out to the LCD display. To create a sense of drama, this function will also fade the display up to full brightness and then back again to darkness to simulate the response bubbling up from the aether within. Before we get into how this all works, we need to wire up the circuit, upload the source code in Listing 8-1, and see what happens.

Listing 8-1. Decision Machine Source Code

#include <LiquidCrystal.h>

char* allAnswers[] = {

" As I see it, ",

" yes ",

" It is ",

" certain ",

It is

decidedly so

,

,

Most

likely

,

,

Outlook

good

,

,

Signs point to yes

,

,

Without a doubt

,

,

Yes

,

,

Yes -

definitely

,

,

You may rely on it

,

,

Reply hazy, try again

,

,

Ask again later

,

,

Better not tell you now

,

,

Cannot predict now

,

,

Concentrate and ask again

,

,

Don't count on it

,

,

My reply is no

,

,

My sources say no

,

,

Outlook not so good

,

,

" Very ",

" doubtful " };

LiquidCrystal lcd(5, 6, 7, 8, 9, 10);

const int backlight = 11;

const int axisPin[] = {A0, A1, A2};

const int threshold = 60; const int brightness = 175;

int axis[3]; int oldAxis[3];

void setup() { lcd.begin(16, 2); randomSeed(analogRead(A5)); getReading(); oldReading();

}

void loop() { getReading();

if (abs(axis[0]-oldAxis[0]) > threshold || abs(axis[1]-oldAxis[1]) > threshold || abs(axis[2]-oldAxis[2]) > threshold) { getAnswer(); delay(500); getReading(); oldReading();

}

delay(125);

}

void getReading() {

for (int i=0; i<3; i++) axis[i] = analogRead(axisPin[i]);

}

void oldReading() {

for (int i=0; i<3; i++) oldAxis[i] = axis[i];

}

void getAnswer() { int thisAnswer = random(40);

while (thisAnswer % 2 != 0) thisAnswer = random(40);

lcd.setCursor(0, 0); lcd.print(allAnswers[thisAnswer]); lcd.setCursor(0, 1); lcd.print(allAnswers[thisAnswer+1]);

for (int i=0; i<=150; i++) { analogWrite(backLight, i); delay(30);

}

for (int i=150; i>=0; i--) { analogWrite(backLight, i); delay(30);

}

lcd.clear();

}

Source Code Summary

With that out of they way, we should have an Arduino connected to a breadboard through a slew of wires and ... nothing actually happens. That is at least until we shake the thing or turn it upside down when an answer will illuminate briefly before fading away again. Now, let's see how it does that.

Inclusions and Declarations

The first line enables the LiquidCrystal library that gives us some easy to use code for talking to LCDs: #include <LiquidCrystal.h>

We'll get to libraries in more depth when we talk about LiquidCrystal among other hardware libraries in the next chapter. Following this line is the first of our declarations:

char* allAnswers[] = {

" As I see it, ",

This page-long list of curious two-line answers is called a character array and contains all of the possible 20 answers that our Decision Machine might display. Each answer is spaced so that it will appear in the center of the LCD and some extra white space has been added to make each answer easier to read in code at the cost of extra paper. Each answer will be addressed by an index number elsewhere in our sketch. After defining our array of answers, we now need to set up the pins that we plan to use, as follows:

6, 7, 8, 9, 10); 11;

{A0, A1, A2};

LiquidCrystal lcd(5, const int backlight const int axisPin[]

Like our previous sketches, these lines of code are the I/O pin numbers that we will use with our hardware. The first line tells the LiquidCrystal library which of the pins the LCD is connected to. The backlight pin is the built in LED on the LCD that we will fade in and out. Finally, axisPin[] is a numerical array that contains the three analog input pins used by the accelerometer.

const int threshold = 60; const int brightness = 175;

These two lines are other configurable variables that determine how much movement needs to be detected before an answer is generated and the total brightness in the LCD backlight in the first and second line respectively.

int axis[3]; int oldAxis[3];

The last of our global variable declarations, axis [ ] and oldAxis[ ] are two arrays that we will use to keep track of which axis has been read and what the old values were the last time we read them. This will help to determine how much movement has been detected in shaking or rotating the device.

setup() and loop()

Our setup() function is fairly small:

lcd.begin(16, 2); randomSeed(analogRead(A5)); getReading(); oldReading();

We begin with a function that starts up the LCD and tells it how big it is, in this case 16 x 2 characters. We then seed the random function with a reading from analog in pin A5 with nothing connected to it. Next, we take a quick reading from the accelerometer and set these as the old values using two of our functions, getReading() and oldReading(). We will look at these more after we talk about the loop() function.

getReading();

if (abs(axis[0]-oldAxis[0]) > threshold | abs(axis[1]-oldAxis[1]) > threshold | abs(axis[2]-oldAxis[2]) > threshold) {

Because we have sufficiently compartmentalized our code using functions, the loop() function is also fairly compact. We begin with reading the sensor values first. The second, third, and fourth lines will check to see if our threshold has been exceeded on any axis by subtracting the old values from the newest values and testing the difference. We’ve used the abs() function to get the absolute value of this difference because we could have positive or negative values here, but we only want to know the difference. Using abs() saves us from having to also check if the old values are larger than the new values because it’s only the amount the values change that we are really interested in.

getAnswer();

delay(500);

getReading();

oldReading();

If a sufficient amount of movement has been detected then we will launch our third function getAnswer() to generate a random message on the LCD. After that has completed, we pause for a half second to give the accelerometer a chance to calm down and then we make another reading and move the new values to the old reading just like we did in setup() to create a baseline for our sensor readings.

delay(i25);

The last line in loop() is just a short eighth of a second delay to slow things down just enough. You probably have noticed that nowhere in this sketch do we know what the actual values of the sensor are. That's not really important only the amount of change, either smaller or larger, in the sensor value. So, let's look at our three new functions.

Functions

Of our three new functions, the first one that we've used is getReading(): void getReading() {

for (int i=0; i<3; i++) axis[i] = analogRead(axisPin[i]);

}

This function is a simple for loop that will read each analog pin as defined by the axisPin[] array and assign those readings to each of the 3 positions in the axis[] array. More on how that works briefly.

void oldReading() {

for (int i=0; i<3; i++) oldAxis[i] = axis[i];

}

Like the last function, oldReading() uses a for loop to assign the current values in the axis[] array to the same index positions in the oldAxis[ ] array. This way we can keep track of the old readings so that we can detect when a sufficient amount of change has occurred.

void getAnswer() {

int thisAnswer = random(40);

while (thisAnswer % 2 != 0) thisAnswer = random(40);

Our final function has a lot going on beginning with choosing a random number in the range of 0 to 39. Even though we only have 20 answers, each answer is split into two lines so we want to start with an even number when displaying the first half of an answer. This means that we need to get 40 answers and then using a while loop, we can check to see if the random value is an odd or even number. By dividing the random number by 2, we know that we've got an even number when it is equal to 0. If it were an odd number we would start displaying our decision in the middle of our message so instead we have it try again at getting a random even number before moving on.

lcd.setCursor(0, 0); lcd.print(allAnswers[thisAnswer]); lcd.setCursor(0, 1); lcd.print(allAnswers[thisAnswer+1]);

Once we have a number for our answer, these four lines will print the first line of the answer, move to the second line of the display, add one to our answer index, and then display the second line of our answer.

for (int i=0; i<=brightness; i++) { analogWrite(backLight, i); delay(30);

}

for (int i=brightness; i>=0; i--) { analogWrite(backLight, i); delay(30);

}

lcd.clear();

So far, all of this code has happened and still nothing can be seen on the screen! To actually see the answer, we need to fade the backlight from 0 or off all the way up to the brightness level determined at the top of the sketch. Once we hit the brightness level, we fade it back down to off again and clear the LCD. We could just connect the LED pin to +5v instead, but where would the fun be in that? This little addition to the code creates much more intrigue and theatricality. Try it without it and I bet you won't like it as much.

With that rather convoluted explanation out of the way, let's back up and talk more specifically about how arrays work. We will also look at some different kinds of arrays and then discuss how these arrays affect the memory usage on the Arduino microcontroller. Hopefully after looking at the way the memory works, there will be reason enough for going back to figure out how we can utilize program memory better.

Arrays

At their simplest, arrays can turn a single variable into a list of multiple variables. Another way to look at it is that an array is a collection of common data elements chosen from any of the various data types available to variables. These elements are addressed by an index number that points to a specific data element. We used both numerical and character arrays in our project code to store our answers, pin assignments, and sensor readings. To put an array to use we begin with an array declaration.

Declaring Arrays

Declaring an array is very similar to declaring any other variable. The simplest form of array declaration can be seen in this following example:

int myArray[3];

This basic array creates three separate variables referred to in a sketch as myArray[0], myArray[1], and myArray[2]. Declaring an array in this manner tells the Arduino compiler to set aside a block of memory that we will fill with data at some point later on. To declare an array, we need at the very least three things: the data type of the array; the name for the array; and either a number surrounded by square brackets, [ ], that is known as the index, or a list of values assigned to the array. The index tells the array how many individual variables, or elements, is a part of the array. The size of the array indicated by its index in the array declaration is collectively known as the array's dimension. In this case, this integer type array has a dimension of three variables that are each addressed individually by that element's index.

Arrays are 0 indexed, meaning that the first element begins at position 0 and the last element in the array has an index that is one less than the array's dimension size. That is why, in our last example in an array with three elements, the first element has an index of 0 and the last element is at index 2. Building on this, we might want to keep track of three digital pins connected to a few LEDs or maybe an RGB LED. In that case, we need to declare the array and assign values to each index in the array, something like the following:

int ledPins[3]; ledPin[0] = 9; ledPin[l] = 10; ledPin[2] = 11;

This example works best in a situation where we need to set up the variable at the beginning of the sketch to set aside a chunk of memory, but we won't know what the values will be until later in the sketch. Maybe we are storing multiple sensor readings and need a temporary place to put them like we did with our project code. If, however, we already know what each element should contain, we could condense the four lines of code down to one, as in this following example:

int ledPins[] = {9, 10, ll};

Although functionally equivalent, there are two things different about this declaration from the previous example. First of all, this example is missing the index number inside the square brackets that tells us the dimension of the array. That's okay though because it is only necessary to indicate the size of the array when none of the values are being initialized. In this case because we have three values listed, the Arduino compiler figures out that there must be only three elements in the array and sets the dimension accordingly. It's still okay to throw the index in there, and it might be necessary if you only want to initialize a few of the values in a much larger array, but it is not always needed.

Secondly, the three values assigned to each element in the array are declared at the same time by enclosing them in curly braces, { }, and separating each value with a comma. Because arrays are 0 indexed, the Arduino compiler assigns the first value in the list to index 0, the second to index 1, and so on until it has reached the end of the declared values. When all is said and done, ledPin[0] contains the value 9, ledPin[1] the value 10, and ledPin[2] the value 11. We should now take a moment to look at how to access arrays and how we can better use them in our sketches.

Using Arrays

By knowing how to properly declare an array and assign values to the individual elements, we can put the elements to use in a variety of different ways. To begin with, we can use the array just like any other variable except with an index number, as in this following example:

digitalWrite(ledPin[0], HIGH);

Assuming the previous array declaration with the value 9 assigned to the index of 0, this could be used to turn on an LED connected to I/O pin 9. One thing to mention is that it is possible to refer to an array without the square brackets because the name of the array points to the first element in the array. In that way, both *ledPin and ledPin[0] refer to the same value. Likewise, ledPin[1] is also the same as *ledPin+1. This little trick with the “*” symbol makes the array name into a pointer so that the compiler understands that this is a reference to an index position. Pointers are a more advanced topic that we can't really get into, but it's worth remembering because they could come in handy some time.

Anyway, instead of a number, we can also use a variable in the place of the array's index to access each element using something like a for loop. We have already snuck this in, in a previous example, with the following line of code:

for (int i=0; i<3; i++) digitalWrite(rgb[i], HIGH);

This line is a little tricky, so hang in there. First, we start with a for loop initializing a variable i with a value of 0 that will increment by 1 each time through the loop or three times until i is no longer less than the value 3, when the loop will exit. Each time through the loop we are calling the digitalWrite() function to turn on, or make a pin HIGH, as determined by the index of the array rgb[] that in this case is the same variable i as declared in the local scope of this for loop. In this way, the first time through the loop pin 9 is turned on, the second is pin 10, and the third is pin 11, when the loop will exit. This single line of code will turn on three LEDs connected to three different pins almost nearly simultaneously.

Arrays of Values

Let's say that we had previously recorded data from a sensor and now want to use these values to control the brightness of an LED or maybe alter the speed of a motor. To see this in action, let's connect a single LED with a 220 ohm resistor to digital pin 11, as shown in Figures 8-3 and 8-4.

R1 220 LED

PIN 11

Figure 8-3. Flicker schematic

GND

+ -

Figure 8-4. Flicker illustration

With this connected, Listing 8-2 provides a very simple example of how an array might work to control the brightness of a single LED connected to PWM pin 11 on the Arduino board.

Listing 8-2. Flicker Example Code int ledPin = 11;

int flicker[] = {64, 22, 4, 28, 6, 130, 186, 120}; int i = 0;

void setup() { randomSeed(analogRead(A5));

}

void loop() {

analogWrite(ledPin, flicker[i]); delay(random(100, 500));

i++;

if (i == (sizeof(flicker)/2)) i = 0;

}

This short but compact example sketch will flicker an LED at varying brightness levels as set by data stored in the flicker[] array with a delay between each value. Using the random functions discussed earlier in this chapter, this delay will be randomly determined to be between a tenth to one half of a second in length. The array flicker[] contains eight elements with values between 0 and 255 previously collected from wind speed data. More data would create a better effect but we kept it small for this example. Using actual data in this case instead of randomly generated numbers is essential to creating a more lifelike behavior that makes our flickering LED even more candle-like.

Inside the loop() function, we write the value of each element to the ledPin, in this case pin 11, beginning with the first element at index 0. By using the random() function inside the delay() function, we create a random pause of varying lengths to make things a little more unpredictable. Each time through loop(), the variable i is incremented by one to advance the array index one step moving to the next brightness level.

So our last line in this sketch will perform a simple test to check if our counter i has reached the end of our array, whatever size it might be, and if so it will reset our counter to the value 0. In this way, we start with the value at the beginning of the list, which is 64, and continue each time through the loop until we hit the last value at index 7, which in this case is 120, before starting all over again with the first value. This is pretty useful so we can add values to our array as much as we want either manually through adding extra values in our code, or through storing a multitude of sensor readings in the loop.

Array Limits and sizeof()

We should probably back up a bit here and explain a little more about the limits of an array. It is really important that we are careful to not access an array beyond the number of elements declared in the array's dimension, whether explicitly declared by us in the declaration of the array or implicitly declared by the Arduino compiler counting the values assigned to the array. For example, in Listing 8-2 if we tried to access a ninth element of the flicker[] array at index 8, which does not exist, we would be reading from parts of the program memory that might be in use by other things. Needless to say, this can cause weird things to happen. Just remember that Arduino C has no way to cross-check our array declarations and would allow us to exceed the boundaries of our arrays. It becomes our responsibility to make sure that any calls to an array stay within its dimension.

To be sure we did not exceed the limits of the array's dimension in Listing 8-2, we needed a way at the end of the main loop to know when we reached the end of the array. To do this, we brought in a nice little utility function in the Arduino library called sizeof(). This function will return the total number of bytes used in an array. Because we have an array of the integer data type, sizeof() will return twice the number of elements than are actually in the array. When we perform the operation sizeof(flicker)/2 we will get the value 8. This can be pretty handy for updating the number of elements in an array and not breaking anything in our code in the process.

Assigning Array Values

So far we have looked at various ways to pull data stored in an array using indexed elements. It's also possible to use a for loop to place multiple values into an array, very much like we did with our project source code. Take the following example:

int analogIn[6];

for (int i=0; i<6; i++) analogIn[i] = analogRead(i);

Here in the first, line we initialize an array called analogIn[] with a dimension of six elements, but we don't yet know what the values will be for each element. In the next line, we use a for loop to step through each of the six analog in pins, assigning a reading from each pin to each element in the array respectively. By placing these values into a single array, we can access those values in a multitude of ways later in our code.

Character Arrays

In the last few pages, each of our arrays has been used to store numerical data but it is also possible to store strings of text in an array, like we did earlier with the answers from our code in Project 8. A simple character array might look like the following:

char helloWorld[] = "Hello, world!";

This array is an example of C-style text strings of the char data type and contains the text Hello, world! The Arduino team has recently added a String object that offers more advanced functionality, but that's not really needed here. Essentially, the string of text is a collection of 14 elements as each letter of the string is stored as a separate element in the array. There are technically 14 elements even though there are only 13 characters, because all text strings need a final null character or 0 byte to tell any of the other functions where the string ends. Without this, functions like Serial.print() would continue to access parts of the memory that are used by other things. Fortunately, the Arduino compiler takes care of this for us adding the null character and determining the proper dimension of the array. If somehow we were to define the dimension of an array without considering this null character, the weird things will return and cause some unusual problems with our code.

Because each letter or character in a string of text is an individual element in a larger array, we could declare this array as follows:

char helloWorld[] = {'H', 'e', 'l', 'l', 'o',

>

'l', 'd', '!'};

While functionally the same, I can tell you the second one was no fun to type. You might notice that individual characters declared in a character array use the single quotes as in 'H' where as the text string is defined in double quotes as in "Hello, world!" instead. It is even possible to create an array of text strings as follows:

char* helloWorld[] = {"Hello,", "world!"};

Because text strings are basically arrays of characters, in this example an array of strings would then be effectively an array of arrays. For this to work properly we need to bring back the “*” symbol mentioned briefly earlier for pointers in the declaration of the data type as char* letting the Arduino compiler know that we intended for this to be an array of strings. We used a character array in this manner in our Decision Machine project code like the following:

char* allAnswers[] = {

" As I see it, ",

II tint- II

With all 20 answers declared in a similar fashion, this declaration creates an array of character strings to be displayed on our liquid crystal display. Remember, the Arduino compiler doesn't care if we put our strings in a block like this or put everything all on one line—it's personal choice really. So before we bother with some of the problems with enormous character arrays, let's look at using multidimensional arrays first.

Multidimensional Arrays

While we will usually only need arrays that are one-dimensional like the ones we have discussed so far in this chapter, Arduino C allows for arrays with any number of dimensions. Think of these as arrays of arrays similar to the character arrays used in Project 8, as in the following, for example:

int myArray[2][3];

This is a two-dimensional array, where myArray[0] is an array of three integers and myArray[1] also contains three integers combined in one multidimensional array. Multidimensional arrays are declared by simply adding an additional index in square brackets for each extra dimension, just like the earlier example. To determine how many elements a multidimensional array contains, we just need to multiply the dimensions of the array, so in this case 2 * 3 = 6 total elements. These elements are as follows:

myArray[0][0]

myArray[0][1]

myArray[0][2]

myArray[1][0]

myArray[1][1]

myArray[1][2]

To assign a value to an element in a two-dimensional array, we would just need to know the position or location of each dimension to a specific element, as in this example:

myArray[1][2] = 42;

This statement assigns the value 42 to the last element in our example array, remembering that arrays are 0 indexed. We can also assign values to a multidimensional array at the time we declare it, just like the one-dimensional arrays earlier.

int arrayTwo[2][2] = {{1, 2}, {3, 4}};

Unlike one-dimensional arrays, we should specify each array's dimension even if we are assigning values in the declaration, although strictly speaking, the first dimension does not have to be explicitly declared. With this new array, called arrayTwo, we have two arrays with two elements each for a total of four elements. The values for each array are separated by commas and bracketed with curly braces like normal arrays, but commas also separate each individual array and the whole thing has a pair of curly braces. In this way arrayTwo[0][0] == 1, arrayTwo[0][l] == 2, arrayTwo[l][0] == 3, and arrayTwo[l][l] == 4 are all true. You can think of a two-dimensional array as a tiny spreadsheet with rows and columns of data. We can even rewrite this array declaration as follows:

int arrayTwo[2][2] = {

{l, 2},

{3, 4}

};

Because white space doesn't count, we can use it to lay our code out in a way that makes sense to us. In this case we put the values together in a little table. Building on this idea, two-dimensional arrays are particularly useful on the Arduino for creating patterns or animations using a row or grid of LEDs. Imagine that we have 6 LEDs each individually connected to pins 2–7 and ground. We could set up an array to declare the pin numbers like so:

int ledPins[] = {2, 3, 4, 5, 6, 7};

And then we could use a multidimensional array to create an animation pattern:

int pattern[3][6] = {

{0,0,l,l,0,0},

{0,1,0,0,1,0},

{l,0,0,0,0,l}

};

Ones are used to represent on or HIGH and zeros are off or LOW. This pattern could then be used to create an animation fitting for a Cylon with LEDs that light up in the middle and expand to the outside LEDs. To make it work, we would use something like the following code fragment:

for (int x=0; x<3; x++) { for (int y=0; y<6; y++) { digitalWrite(ledPins[y], pattern[x][y]);

}

delay(250);

}

Here we have two nested for loops, the first is used to increment through the three possible animations using the x counter. The second for loop increments the y counter six times and uses that counter to both increment each element in the ledPin[] array, as well as each of the six elements in the animation arrays. This way when we use the digitalWrite() function, ledPins[y] will set each of the six pins to whatever state is in pattern[x][y]. So beginning with the first loop, x == 0, then the second loop begins with y == 0, so the led at index 0 or pin 2 is set to the condition of pattern at index 0,0, which is 0 or LOW. The counter y is incremented so that y == 1, so the LED at index 1 or pin 3 is set to the condition of pattern at index 0, 1, which is also LOW. y is incremented again, turning pin 4 to the condition at pattern[0][2], which in this case is 1 or HIGH. This repeats until each of the six LEDS has been turned on or off according to the first pattern. All of this happens in rapid succession and if it weren't for the short delay to slow things down when the y loop has completed, we would only see the briefest flicker. Once the delay is over, the x loop is incremented and the y loop begins again with a new pattern.

This is only the briefest introduction to multidimensional arrays. We could use three-dimensional arrays to create a whole set of animations for an 8 x 8 LED matrix or to store the X, Y, and Z readings from an accelerometer. Maybe we could display an entire series of information on an LCD for things

labeled with RFID tags. But before we find any more things to fill up the memory of the Arduino interface board we need to know more about how that memory works in the first place.

Arduino Memory

The challenging thing with arrays is that they can begin to fill up a relatively enormous amount of memory space. For example, if you look at programming arrays in C for a desktop computer, programmers will often use massive data strings in their arrays to take advantage of all of that RAM and hard-drive memory space. Unlike a computer, our microcontroller has to be programmed differently because it only has a very, very small amount of memory in comparison and arrays take up a lot of that memory space. For example, every letter in a character array takes up one byte of memory so a single 32-character message, like the ones we used in our project code, is going to take up 34 bytes of memory when it is split into two arrays. This can quickly add up and create some problems for us if we don't first know a little bit about how memory is structured on the Arduino microcontroller.

There are three separate types of memory found inside the Atmel ATmega328 microcontroller used on the Arduino Uno interface board. These are shown in Table 8-1.

Table 8-1. Arduino Memory

Memory

Size

Storage

Use

Flash

32,768 bytes

non-volatile

Stores the program source code and Arduino bootloader

SRAM

2048 bytes

volatile

Operating space for accessing variables and functions

EEPROM

1024 bytes

non-volatile

Permanent storage for user data like readings or settings

The largest chunk of memory on the microcontroller is flash or program memory. The ATmega328 has a total capacity of 32,768 bytes available to store program instructions, compiled from our source code, with roughly 500 bytes taken up by the Arduino bootloader. The bootloader is a small program that makes programming the microcontroller through a serial port possible rather than requiring additional hardware. Once source code has been uploaded to the microcontroller, its data is read-only and cannot be modified by the running code. It is also a non-volatile memory type meaning that the program stored in this memory is retained even when power is no longer present or the board has been reset.

The program memory is kept in a separate space from the random access memory or RAM— technically this is static or SRAM but it doesn't really matter—that is used for storing data and various microcontroller instructions like keeping track of function calls while the microcontroller is running.

The ATmega328 has 2048 bytes of RAM, which gets gobbled up pretty quickly with global variables, arrays, and text strings. This memory type is volatile, so any data in RAM will be lost when the power has been turned off. During a normal operation, an instruction is fetched from the program memory and loaded into RAM to be executed. In this way, local variables are only loaded into RAM for the duration that they are needed while global variables stay in RAM for the entire running time. We will look at some of the problems this can create in a moment.

The final type of memory on the ATmega328 microcontroller is the 1024 bytes of EEPROM; short for electronically erasable program read-only memory. This memory is also non-volatile, keeping its data even when power is not present, although unlike flash memory, EEPROM has a limited life span where it can only be reprogrammed up to about 100,000 times before it becomes unusable. EEPROM is a byte

addressable memory, making it a little trickier to put into use if we are not using a byte data type for our data and it requires its own library to be accessed in our code.

Now that we have an idea of what we're working with, let's count some bytes. We just mentioned how a single 32-character message when stored in an array will occupy 34 bytes of memory. When multiplied by the 20 total possible answers for our project, this single character array would consume not only 680 bytes of program memory, but when the program starts it will load this array into RAM, taking another 680 bytes of the available RAM space, as well. By the time we add in the rest of the variables and other data used by the program, we would only be left with 1043 bytes of RAM. And that's for our relatively basic example source code; as it is, by using that large of a character array, it would not have been possible to run this code on one of the Arduino Uno's predecessors that uses the ATmega168 with half as much total RAM space. Anything coded more complex with additional strings, or multiple variables, would quickly run out of RAM leading to intermittent operation and strange behavior.

Checking Free RAM

The kicker is that running out of RAM can happen at any time with absolutely no warning. When we upload our source code to the program memory on the interface board, the Arduino programming environment conveniently tells us the binary sketch size out of the maximum program memory, as in 6114 bytes (of a 32256 byte maximum), but we have no idea how much RAM is actually in use. Fortunately, Jean-Claude Wippler of JeeLabs wrote a small function, shown in Listing 8-3, that we can drop into a sketch to find out how much RAM is currently unused.

Listing 8-3. freeRAMO Function Source Code

int freeRAM() {

extern int _heap_start, *_brkval;

int v;

return (int) &v - (_brkval == 0 ? (int) &_heap_start : (int) _brkval);

}

This function uses some of that ridiculous non-Arduino code that we really don't need to understand in depth, so we will just run with it. To make it work, all we need to do is drop this function into a sketch with a function call, and then open the Serial Monitor so that we can see our RAM usage. Here in Listing 8-4 is a slightly absurd example using the Blink sketch to see that even this very simple program takes 212 bytes of RAM.

Listing 8-4. SamplefreeRamO Usage

int ledPin = 13; int time = 1000;

void setup() {

pinMode(ledPin, OUTPUT);

Serial.begin(9600);

Serial.println("\n[memCheck]");

Serial.println(freeRAM(), dEc);

} void loop() {

digitalWrite(ledPin, HIGH); delay(time);

digitalWrite(ledPin, LOW); delay(time);

}

int freeRAM() {

extern int _heap_start, *_brkval;

int v;

return (int) &v - (_brkval == 0 ? (int) &_heap_start : (int) _brkval);

}

In this sample code, we tossed in the freeRAM() function at the bottom of our sketch and in our setup() function we set up serial communications and sent the text string [memCheck] followed by the value of free RAM as returned by the freeRAM() function. After uploading the source code, open up the Serial Monitor to see the unused amount of RAM. This could be a fairly useful function to have in the toolbox when using large arrays or text strings or just trying to track down some unusual problems with our code.

Armed with a basic understanding of the memory types and structure on the Arduino microcontroller and how we might check the free RAM space in our code, let's look at a couple of the libraries used to access some of the other types of memory space. We might even consider a method for rewriting our project code to save a little memory.

Using Program Memory

Instead of using a character array like we did in Project 8 that gets loaded into RAM memory, we could use the pgmspace library that is a part of the standard avr-libc library to keep read-only data stored only in program memory. The benefit to keeping large variables, arrays, or text strings in program memory is that when the program starts, this read-only data is not loaded into the RAM space.

The challenge with this library is that it has not been given the full Arduino treatment, so it's only really worth the hassle of dealing with the more difficult code when we absolutely need to store large chunks of data. It is also a two-step process that requires some extra functions to properly store the data to begin with and then again to read the data in program memory so that we can actually use it. Let's focus this all-too-brief discussion on how we could use program memory to rewrite our project code and save a little memory in the process.

To begin with, we need to let the Arduino compiler know that we want to use the program memory functions that are not a part of the standard Arduino library by specifying the following line at the beginning of our code:

#include <avr/pgmspace.h>

The easiest way to keep a string in program memory and not in RAM is to use the PROGMEM variable qualifier, like in the following example:

const char message[] PROGMEM =

"This message is held entirely in flash memory.";

This wouldn't work for our project code because we want to create an array of strings, which is not entirely supported by the pgmspace library. To get around this, we could make multiple character arrays that are stored in program memory, as follows:

prog_char

ans01a[]

PROGMEM = "

As I see it

it . ";

prog_char

ans01b[]

PROGMEM = "

yes

it . ";

prog_char

ans02a[]

PROGMEM = "

It is

It . ";

prog_char

ans02b[]

PROGMEM = "

certain

It . ";

prog_char ans20a[] PROGMEM = "

Very

It . ";

prog_char ans20b[] PROGMEM = "

doubtful

It . ";

You'll have to imagine the rest of the character arrays declared in a similar manner. Once this is done, we would need to create a lookup table for each one of these messages, as follows:

const char* allAnswers[] PROGMEM = { ansOla, ans01b, ans02a, ans02b, ans03a, ans03b, ans04a, ans04b, ans05a, ans05b,

While we have chopped out most of the array to save paper, this part of the code looks more like the previous example array message[]. This array is set up as a pointer to each of the possible messages and would be accessed later in our code with the following line:

strcpy_P(buffer, (char*)pgm_read_word(&(allAnswers[thisAnswer])));

And here is more of that avr-libc voodoo that we are only going to briefly look at. The strcpy_P() function will copy a target string stored in program memory to a location in RAM memory. In this case we would need to declare an array named buffer[] with a dimension of 30 to temporarily store each individual line of text in RAM. This line then uses the pgm_read_word() macro to access the program memory that stores the array of messages specified in the allAnswers[] array at the location given by the variable thisAnswer to get a string of text. Dump all of that into the buffer array and it can be used by the Arduino functions, including lcd.print(), just like nothing happened. That may not make a whole lot of sense right now, but just remember that sometimes it is necessary to store large arrays of text and this is about the only way to do it without having RAM problems. There is hope because you can pretty much copy this structure verbatim and swap out the array and variable names, as well as the text strings to suit the project at hand whenever you need to.

For more on using PROGMEM on the Arduino, you can start with the Arduino web site at the top of the following list, followed by the avr-libc documentation and a helpful tutorial for putting this stuff in action.

www.arduino.cc/en/Reference/PROGMEM

www.nongnu.org/avr-libc/user-manual/group_avr_pgmspace.html

www.teslabs.com/openplayer/docs/docs/prognotes/Progmem%20Tutorial.pdf

Using EEPROM

Unlike program memory, it's fairly easy to store data in the microcontroller's internal EEPROM, although we have to worry a little about the maximum 100,000 or so read/write cycles before the EEPROM stops working. EEPROM stores data in 1-byte sections with the Arduino Uno storing up to 1024 bytes. Listing 8-5 revisits that flicker sketch from earlier, but this time it puts the brightness levels into EEPROM first and reads the data from that memory instead of the array.

Listing 8-5. Flicker from EEPROM Source Code #include <EEPROM.h> boolean writeEEPROM = true; int ledPin = 11;

byte flicker[] = {64, 22, 4, 28, 6, 130, 186, 120}; int i = 0;

void setup() { randomSeed(analogRead(A5)); if (writeEEPROM == true) for (int i=0; i<(sizeof(flicker)/2); i++) EEPROM.write(i, flicker[i]); i = 0;

}

void loop() {

analogWrite(ledPin, EEPROM.read(i)); delay(random(100, 500));

i++;

if (i == (sizeof(flicker)/2)) i = 0;

}

To read and write data to the EEPROM, we need to use the EEPROM library. This is not included by default in our sketches so we have to use the following line to access the EEPROM functions:

#include <EEPROM.h>

We have also added the following line: boolean writeEEPROM = true;

Since we only need to write the data once, this will allow us to upload and run the code once, then change this variable to false, re-upload the code, and this time it will only read the EEPROM data. That will keep us from using up our total read/write cycles too quickly. The values in the flicker[] array stay the same, but we changed the data type to byte because it is easiest if the information we want to store is already a byte data type. To store the data the first time, we use the following block of code:

if (writeEEPROM == true)

for (int i=0; i<(sizeof(flicker)/2); i++)

EEPROM.write(i, flicker[i]);

This loop, only activated when the variable writeEEPROM is set to true, increments through a for loop until the end of the array has been reached, in this case eight times, although we could easily add more elements. Each time through the loop we use the EEPROM.write() function that has the following syntax:

EEPROM.write(address, value)

The address can be a number from 0 to 1023 corresponding to the location of each byte in memory space, and the value is a number between 0–255. In our example, we start with the first EEPROM location, 0, and write the value of flicker[] at index 0. The loop increments each time, writing the next value in the array to the next space in memory until it has reached the end of the array.

analogWrite(ledPin, EEPROM.read(i));

This is the last line of code that we changed. Instead of writing the value of the flicker[] array to the analog pin, we instead read from each of the EEPROM addresses and write that value. The syntax for the EEPROM.read() is pretty simple, as follows:

EEPROM.read(address)

All we need to do is specify the EEPROM address that we want to read the data from, a number from 0–1023, and that value will be returned back. And that concludes the quick rundown on reading and writing data to our microcontroller's EEPROM. It's probably not the best example because our constant reading of the brightness values will potentially use up our read/write lifetime in about 10 hours, causing our EEPROM to stop working, so I don't recommend leaving the sketch running for too long.

Instead, the EEPROM is a good place to store sensor readings every minute or so over a few hours for a low-cost data logger. Because the memory is non-volatile and is not used by the Arduino otherwise, this data will remain in memory regardless of power or additional code being loaded in program memory. Only until a running program overwrites the data, the EEPROM data will persistently stick around. This is also a good reason for storing certain configuration settings that might need to get dialed in while the code is running, but if the power is reset or the program is reloaded we would still want those settings to remain. The BlinkM from the last chapter uses its EEPROM to store similar configuration settings.

Summary

If you're still hanging on after some of that less-than- friendly code, then good for you. Anytime I have to dredge through any of that fringe stuff that hasn't already been brought into the Arduino fold, I am plenty thankful for everything that has already been wrapped up for us. Arrays are a pretty useful tool in our Arduino projects and we could use two chapters to get through everything that can be done with them, but we still have more things to talk about. Arrays work to make all manner of lists of things from pin numbers, to output levels, sensor readings, and even text messages. They can be rolled into loops and functions with amazing ease because of their indexed data elements. We just need to be careful how we handle our memory usage when using them and stay on the lookout for strange behavior that might be caused by limited RAM.

Next up, we are going to look at a few more libraries written for particular hardware and devices that add functionality to the Arduino platform. We'll begin by revisiting the LiquidCrystal library before moving on to a couple of other libraries for different types of motors before ending the chapter with finding another way to store lots of data on a convenient memory card. What's important here is that we are now going to get back to making things move again, so the next chapter should be a lot of fun!