Some robotics applications require sequences of maneuvers. You’ll actually see some simple sequence examples in the next chapter when the BOE Shield-Bot detects that it has bumped into an obstacle. At that point, it has to back up, and then turn before trying to go forward again. That is a simple sequence with two maneuvers.
Another example is corridor navigation. The BOE Shield-Bot might have to find a corridor and then go through a sequence of maneuvers to enter it, before searching for the corridor’s walls.
Other sequences can be more elaborate. One example of a long and complex maneuver would be for a robotic dance contest. (Robot dance contests have been gaining popularity in recent years.) For dancing to an entire song, the robot might need a pretty long list of maneuvers and maneuver times. If your sketch needs to store lists of maneuvers, the variable array is the best tool for storing these lists.
This activity introduces arrays with some simple musical applications using the piezospeaker. Then, it examines two different approaches for using arrays to store sequences of maneuvers for playback while the sketch is running.
An array is a collection of variables with a common name. Each variable in the array is referred to as an element. Here is an array declaration with eight elements:
int note[] = {1047, 1174, 1319, 1397, 1568, 1760, 1976, 2093};
The array’s name is note, and each element in the array can be accessed by its index number. Arrays are zero indexed, so the elements are numbered 0 to 7. The diagram below shows how note[0] stores 1047, note[1] stores 1174, note[2] stores 1319, and so on, up through note[7], which stores 2093.
Let’s say your code needs to copy note[3], which stores the value 1397, to a variable named myVar. Your code could do it like this:
myVar = note[3];
Your sketch can change array element values too. For example, you could change the value 1976 to 1975 with an expression like this:
note[3] = 1975;
An array does not have to be pre-initialized with values like it is in the diagram above. For example, you could just declare an array with 8 elements like this:
int myArray[8];
Then, your sketch could fill in the values of each element later, perhaps with sensor measurements, values entered from the Serial Monitor, or whatever numbers you need to store.
The diagram below shows the musical notes on the right side of a piano keyboard. Each key press on a piano key makes a string (or a speaker if it’s electric) vibrate at a certain frequency.
Next, let’s use the array to make your BOE Shield-Bot play some musical notes…
An array element doesn’t necessarily need to be copied to another variable to use its value. For example, your sketch could just print the value in note[3] to the Serial Monitor like this:
Serial.print(note[3]);
Since the values in the array are musical notes, we might as well play this note on the BOE Shield-Bot’s piezospeaker! Here's how:
tone(4, note[3], 500);
Here is an example that displays an individual array element’s value in the Serial Monitor, and also uses that value to make the BOE Shield-Bot’s piezospeaker play a musical note.
// Robotics with the BOE Shield – PlayOneNote // Displays and plays one element from note array. int note[] = {1047, 1147, 1319, 1397, 1568, 1760, 1976, 2093}; void setup() { Serial.begin(9600); Serial.print("note = "); Serial.println(note[3]); tone(4, note[3], 500); delay(750); } void loop() { }
Many applications use variables to access elements in an array. The next sketch PlayAnotherNote declares a variable named index and uses it to select an array element by its index number.
The familiar for loop can automatically increment the value of index. The code to play and display notes is inside the for loop, and index is used to select the array element. For the first trip through the loop, index will be 0, so the value stored in note[0] will be used wherever note[index] appears in a print or tone function. With each trip through the loop, index will increment until the sketch has displayed and played all the notes in the array.
// Robotics with the BOE Shield – PlayNotesWithLoop // Displays and plays another element from note array. int note[] = {1047, 1147, 1319, 1397, 1568, 1760, 1976, 2093}; void setup() { Serial.begin(9600); for(int index = 0; index < 8; index++) { Serial.print("index = "); Serial.println(index); Serial.print("note[index] = "); Serial.println(note[index]); tone(4, note[index], 500); delay(750); } } void loop() { }
for(int index = 7; index >= 0; index--);
Let’s say you want to compose a musical melody that has more, or fewer, notes. It’s easy to forget to update the for loop to play the correct number of notes. The Arduino library has a sizeof function that can help with this. It can tell you both the size of the array in bytes, and the size of the array’s variable type (like int). Your code can then divide the number of bytes for the variable type into the number of bytes in the array. The result is the number of elements in the array.
Here is an example of using this technique. It loads a variable named elementCount with the number of elements in the note array:
int note[] = {1047, 1147, 1319, 1397, 1568, 1760, 1976, 2093}; int elementCount = sizeof(note) / sizeof(int);
Later, your for loop can use the elementCount variable to play all the notes in the array, even if you add or delete elements:
for(int index = 0; index < elementCount; index++)
// Robotics with the BOE Shield – PlayAllNotesInArray // Uses sizeof to determine number of elements int he array // and then displays and prints each note value in the sequence. int note[] = {1047, 1147, 1319, 1397, 1568, 1760, 1976, 2093}; void setup() { Serial.begin(9600); int elementCount = sizeof(note) / sizeof(int); Serial.print("Number of elements in array = "); Serial.println(elementCount); for(int index = 0; index < elementCount; index++) { Serial.print("index = "); Serial.println(index); Serial.print("note[index] = "); Serial.println(note[index]); tone(4, note[index], 500); delay(750); } } void loop() { }
int note[] = {1047, 1147, 1319, 1397, 1568, 1760, 1976, 2093, 2349, 2637};
Remember the maneuver function from the last activity? Here are three arrays of values, one for each parameter in the maneuver function. Together, they make up the same forward-left-right-backward-stop sequence we’ve been doing through the chapter.
// Forward left right backward stop // index 0 1 2 3 4 int speedsLeft[] = {200, -200, 200, -200, 0}; int speedsRight[] = {200, 200, -200, -200, 0}; int times[] = {2000, 600, 600, 2000, -1};
A sketch can then use this code in one of its functions to execute all the maneuvers:
// Determine number of elements in sequence list. int elementCount = sizeof(times) / sizeof(int); // Fetch successive elements from each sequence list and feed them // to maneuver function. for(int index = 0; index < elementCount; index++) { maneuver(speedsLeft[index], speedsRight[index], times[index]); }
Each time through the loop, index increases by 1. So, with each maneuver call, the next element in each array gets passed as a parameter to the maneuver function. The first time through the loop, index is 0, so the maneuver call’s parameters become the zeroth element from each array, like this: maneuver(speedsLeft[0], speedsRight[0], times[0]). The actual values that get passed to the maneuver function look like this: maneuver(200, 200, 2000). The second time through the loop, index is 1, so the function looks like this: maneuver(speedsLeft[1], speedsRight[1], times[1]), which becomes maneuver(–200, 200, 2000).
// Robotics with the BOE Shield - ManeuverSequence // Move forward, left, right, then backward with an array and the // maneuver function. #include <Servo.h> // Include servo library // Forward left right backward stop // index 0 1 2 3 4 int speedsLeft[] = {200, -200, 200, -200, 0}; int speedsRight[] = {200, 200, -200, -200, 0}; int times[] = {2000, 600, 600, 2000, -1}; Servo servoLeft; // Declare left and right servos Servo servoRight; void setup() // Built-in initialization block { tone(4, 3000, 1000); // Play tone for 1 second delay(1000); // Delay to finish tone servoLeft.attach(13); // Attach left signal to pin 13 servoRight.attach(12); // Attach right signal to pin 12 // Determine number of elements in sequence list. int elementCount = sizeof(times) / sizeof(int); // Fetch successive elements from each sequence list and feed them // to maneuver function. for(int index = 0; index < elementCount; index++) { maneuver(speedsLeft[index], speedsRight[index], times[index]); } } void loop() // Main loop auto-repeats { // Empty, nothing needs repeating } void maneuver(int speedLeft, int speedRight, int msTime) { // speedLeft, speedRight ranges: Backward Linear Stop Linear Forward // -200 -100......0......100 200 servoLeft.writeMicroseconds(1500 + speedLeft); // Set Left servo speed servoRight.writeMicroseconds(1500 - speedRight); // Set right servo speed if(msTime==-1) // if msTime = -1 { servoLeft.detach(); // Stop servo signals servoRight.detach(); } delay(msTime); // Delay for msTime }
Did your BOE Shield-Bot perform the familiar forward-left-right-backward-stop sequence of movements? Are you thoroughly bored with it by now? Do you want to see your BOE Shield-Bot do something else, or to choreograph your own routine?
Here’s an example of a longer list you can try. It does the four pivots after the forward-left-right-backward sequence. In this example, when index is 4, it’ll use the first number of the second line of each array. When index is 5, it’ll use the second number on the second line of each array, and so on. Notice that each list of comma-separated array elements is contained within braces { }, and it doesn’t matter whether that list is all on one line or spanning multiple lines.
int speedsLeft[] = {200, -200, 200, -200, 0, 200, -200, 0, 0}; int speedsRight[] = {200, 200, -200, -200, 200, 0, 0, -200, 0}; int times[] = {2000, 600, 600, 2000, 1000, 1000, 1000, 1000, -1};
The last example in this activity a sketch for performing maneuvers using a list of characters in an array. Each character represents a certain maneuver, with a 200 ms run time. Since the run time is fixed, it’s not as flexible as the last approach, but it sure makes it simple to build a quick sequence of maneuvers.
f = forward b = backward l = left r = right s = stop
Character arrays use do not use lists of comma-separated elements. Instead, they use a continuous string of characters. Here is an example of the same-old forward-left-right-backward-stop sequence in a character array:
char maneuvers[] = "fffffffffflllrrrbbbbbbbbbbs";
The character array string has 10 f characters. Since each character represents 200 ms of run time, that takes the BOE Shield-Bot forward for 2 seconds. Next, three l characters make 600 ms of left turn. Three r characters make a right turn, followed by ten b characters to go backward, and then an s character for stop completes the sequence.
// Robotics with the BOE Shield – ControlWithCharacters // Move forward, left, right, then backward for testing and tuning. #include <Servo.h> // Include servo library char maneuvers[] = "fffffffffflllrrrbbbbbbbbbbs"; Servo servoLeft; // Declare left and right servos Servo servoRight; void setup() // Built-in initialization block { tone(4, 3000, 1000); // Play tone for 1 second delay(1000); // Delay to finish tone servoLeft.attach(13); // Attach left signal to P13 servoRight.attach(12); // Attach right signal to P12 // Parse maneuvers and feed each successive character to the go function. int index = 0; do { go(maneuvers[index]); } while(maneuvers[index++] != 's');} void loop() // Main loop auto-repeats { // Empty, nothing needs repeating } void go(char c) // go function { switch(c) // Switch to code based on c { case 'f': // c contains 'f' servoLeft.writeMicroseconds(1700); // Full speed forward servoRight.writeMicroseconds(1300); break; case 'b': // c contains 'b' servoLeft.writeMicroseconds(1300); // Full speed backward servoRight.writeMicroseconds(1700); break; case 'l': // c contains 'l' servoLeft.writeMicroseconds(1300); // Rotate left in place servoRight.writeMicroseconds(1300); break; case 'r': // c contains 'r' servoLeft.writeMicroseconds(1700); // Rotate right in place servoRight.writeMicroseconds(1700); break; case 's': // c contains 's' servoLeft.writeMicroseconds(1500); // Stop servoRight.writeMicroseconds(1500); break; } delay(200); // Execute for 0.2 seconds }
char maneuvers[] = "fffffffffflllrrrrrrlllbbbbbbbbbbs";
After the char maneuvers array and the usual initialization, these lines fetch the characters from the array and pass them to the go function (explained later).
int index = 0; do { go(maneuvers[index]); } while(maneuvers[index++] != 's');
First, index is declared and initialized to zero, to be used in a do-while loop. Similar to a regular while loop, do-while repeatedly executes commands inside its code block while a condition is true, but the while part comes after its block so the block always executes at least once. Each time through the loop, go(maneuvers[index]) passes the character at maneuvers[index] to the go function. The ++ in index++ adds one to the index variable for the next time through the loop—recall that this is the post increment operator. This continues while(maneuvers[index] != 's') which means “while the value fetched from the maneuvers array is not equal to 's' .”
Now, let’s look at the go function. It receives each character passed to its c parameter, and evaluates it on a case-by-case basis using a switch/case statement. For each of the five letters in the maneuvers character array, there is a corresponding case statement in the switch(c) block that will be executed if that character is received by go.
If the go function call passes the f character to the c parameter, the code in case f is executed—the familiar full-speed-forward. If it passes b, the full-speed backward code gets executed. The break in each case exits the switch block and the sketch moves on to the next command, which is delay(200). So, each call to go results in a 200 ms maneuver. Without that break at the end of each case, the sketch would continue through to the code for the next case, resulting in un-requested maneuvers.
void go(char c) // go function { switch(c) // Switch to based on c { case 'f': // c contains 'f' servoLeft.writeMicroseconds(1700); // Full speed forward servoRight.writeMicroseconds(1300); break; case 'b': // c contains 'b' servoLeft.writeMicroseconds(1300); // Full speed backward servoRight.writeMicroseconds(1700); break; case 'l': // c contains 'l' servoLeft.writeMicroseconds(1300); // Rotate left in place servoRight.writeMicroseconds(1300); break; case 'r': // c contains 'r' servoLeft.writeMicroseconds(1700); // Rotate right in place servoRight.writeMicroseconds(1700); break; case 's': // c contains 's' servoLeft.writeMicroseconds(1500); // Stop servoRight.writeMicroseconds(1500); break; } delay(200); // Execute for 0.2 s
case 'h': // c contains 'h' servoLeft.writeMicroseconds(1550); // Half speed forward servoRight.writeMicroseconds(1450); break;