This chapter introduces different programming strategies to make the BOE Shield-Bot move. Once we understand how basic navigation works, we’ll make functions for each maneuver. In later chapters, we’ll write sketches that call these functions in response to sensor input, which will allow the BOE Shield-Bot to navigate on its own.
The first step is to get oriented! The picture below shows forward, backward, left turn, and right turn from the point of view of the BOE Shield-Bot.
Here are the goals of this chapter’s activities:
Have you ever thought about what direction a car’s wheels have to turn to propel it forward? The wheels turn opposite directions on opposite sides of the car. Likewise, to make the BOE Shield-Bot go forward, its left wheel has to turn counterclockwise, but its right wheel has to turn clockwise.
Remember that a sketch can use the Servo library’s writeMicroseconds function to control the speed and direction of each servo. Then, it can use the delay function to keep the servos running for certain amounts of time before choosing new speeds and directions. Here’s an example that will make the BOE Shield-Bot roll forward for about three seconds, and then stop.
// Robotics with the BOE Shield - ForwardThreeSeconds // Make the BOE Shield-Bot roll forward for three seconds, then stop. #include <Servo.h> // Include servo library 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 // Full speed forward servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(3000); // ...for 3 seconds servoLeft.detach(); // Stop sending servo signals servoRight.detach(); } void loop() // Main loop auto-repeats { // Empty, nothing needs repeating }
First, the Servo library has to be included so that your sketch can access its functions:
#include <Servo.h> // Include servo library
Next, an instance of Servo must be declared and uniquely named for each wheel:
Servo servoLeft; // Declare left & right servos Servo servoRight;
Instance of an Object
An object is a block of pre-written code that can be copied and re-used multiple times in a single sketch. Each copy, called an object instance, can be configured differently. For example, the two Servo declarations create two instances of the object’s code, named servoLeft and servoRight. Then, functions within each instance can be called and configured individually. So, servoLeft.attach(13) configures the servoLeft object instance to send its servo control signals to pin 13. Likewise, servoRight.attach(12) tells the servoRight object instance to send its signals to pin 12.
A sketch automatically starts in its setup function. It runs the code in there once before moving on to the loop function, which automatically keeps repeating. Since we only want the BOE Shield-Bot to go forward and stop once, all the code can be placed in the setup function. This leaves the loop function empty, but that’s okay.
As with all motion sketches, the first action setup takes is making the piezospeaker beep. The tone function call transmits a signal to digital pin 4 that makes the piezospeaker play a 3 kHz tone that lasts for 1 second. Since the tone function works in the background while the code moves on, delay(1000) prevents the BOE Shield-Bot from moving until the tone is done playing.
void setup() // Built-in initialization { tone(4, 3000, 1000); // Play tone for 1 second delay(1000); // Delay to finish tone
Next, the servoLeft object instance gets attached to digital pin 13 and the servoRight instance gets attached to pin 12. This makes calls to servoLeft.writeMicroseconds affect the servo control signals sent on pin 13. Likewise, calls to servoRight.writeMicroseconds will affect the signals sent on pin 12.
servoLeft.attach(13); // Attach left signal to pin 13 servoRight.attach(12); // Attach right signal to pin 12
Remember that we need the BOE Shield-Bot’s left and right wheels to turn in opposite directions to drive forward. The function call servoLeft.writeMicroseconds(1700) makes the left servo turn full speed counterclockwise, and the function call servoRight.writeMicroseconds(1300) makes the right wheel turn full speed clockwise. The result is forward motion. The delay(3000) function call keeps the servos running at that speed for three full seconds. After the delay, servoLeft.detach and servoRight.detach discontinue the servo signals, which bring the robot to a stop.
// Full speed forward servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(3000); // ...for 3 seconds servoLeft.detach(); // Stop sending servo signals servoRight.detach(); }
After the setup function runs out of code, the sketch automatically advances to the loop function, which repeats itself indefinitely. In this case, we are leaving it empty because the sketch is done, so it repeats nothing, over and over again, indefinitely.
void loop() // Main loop auto-repeats { }
Want to change the distance traveled? Just change the time in delay(3000). For example, delay(1500) will make the BOE Shield-Bot go for only half the time, which in turn will make it travel only half as far. Likewise, delay(6000) will make it go for twice the time, and therefore twice the distance.
All it takes to get other motions out of your BOE Shield-Bot are different combinations of us parameters in your servoLeft and servoRight writeMicroseconds calls. For example, these two calls will make your BOE Shield-Bot go backwards:
// Full speed backwards servoLeft.writeMicroseconds(1300); // Left wheel clockwise servoRight.writeMicroseconds(1700); // Right wheel counterclockwise
These two calls will make your BOE Shield-Bot rotate in place to make a left turn:
// Turn left in place servoLeft.writeMicroseconds(1300); // Left wheel clockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise
These two calls will make your BOE Shield-Bot rotate in place for a right turn:
// Turn right in place servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.write Microseconds(1700); // Right wheel counterclockwise
Let’s combine all these commands into a single sketch that makes the BOE Shield-Bot move forward, turn left, turn right, then move backward.
// Robotics with the BOE Shield - ForwardLeftRightBackward // Move forward, left, right, then backward for testing and tuning. #include <Servo.h> // Include servo library 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 // Full speed forward servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(2000); // ...for 2 seconds // Turn left in place servoLeft.writeMicroseconds(1300); // Left wheel clockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(600); // ...for 0.6 seconds // Turn right in place servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1700); // Right wheel counterclockwise delay(600); // ...for 0.6 seconds // Full speed backward servoLeft.writeMicroseconds(1300); // Left wheel clockwise servoRight.writeMicroseconds(1700); // Right wheel counterclockwise delay(2000); // ...for 2 seconds servoLeft.detach(); // Stop sending servo signals servoRight.detach(); } void loop() // Main loop auto-repeats { // Empty, nothing needs repeating }
TIP — To enter this sketch quickly, use the Arduino software’s Edit menu tools (Copy and Paste) to make four copies of the four lines that make up a maneuver (comment, servoLeft.writeMicroseconds, servoRight.writeMicroseconds, and delay). Then, modify each one with individual values
You can make the BOE Shield-Bot turn by pivoting around one wheel. The trick is to keep one wheel still while the other rotates. Here are the four routines for forward and backward pivot turns:
// Pivot forward-left servoLeft.writeMicroseconds(1500); // Left wheel stop servoRight.writeMicroseconds(1300); // Right wheel clockwise // Pivot forward-right servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1500); // Right wheel stop // Pivot backward-left servoLeft.writeMicroseconds(1500); // Left wheel stop servoRight.writeMicroseconds(1700); // Right wheel counterclockwise // Pivot backward-right servoLeft.writeMicroseconds(1300); // Left wheel clockwise servoRight.writeMicroseconds(1500); // Right wheel stop
Imagine writing a sketch that instructs your BOE Shield-Bot to travel full-speed forward for fifteen seconds. What if your robot curves slightly to the left or right during its travel, when it’s supposed to be traveling straight ahead? There’s no need to take the BOE Shield-Bot back apart and re-adjust the servos with a screwdriver to fix this. You can simply adjust the sketch slightly to get both wheels traveling the same speed. While the screwdriver approach could be considered a hardware adjustment, the programming approach would be a software adjustment.
So, would your BOE Shield-Bot travel in an arc instead of in a straight line? Top speed varies from one servo to the next, so one wheel is likely to rotate a little faster than the other, causing the BOE Shield-Bot to make a gradual turn.
To correct this, the first step is to examine your BOE Shield-Bot’s forward travel for a longer period of time to see if it is curving, and which way, and how much.
// Robotics with the BOE Shield - ForwardTenSeconds // Make the BOE Shield-Bot roll forward for ten seconds, then stop. #include <Servo.h> // Include servo library 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 // Full speed forward servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(10000); // ...for 10 seconds servoLeft.detach(); // Stop sending servo signals servoRight.detach(); } void loop() // Main loop auto-repeats { // Empty, nothing needs repeating }
If your BOE Shield-Bot turns slightly when you want it to go straight forward, the solution is fairly simple. Just slow down the faster wheel. Remember from the servo transfer curve graph [2]that you have best speed control over the servos in the 1400 to 1600 µs range.
Top speed clockwise | Linear speed zone starts | Full stop | Linear speed zone ends | Top speed counterclockwise |
1300 | 1400 | 1500 | 1600 | 1700 |
Let’s say that your BOE Shield-Bot gradually turns left. That means the right wheel is turning faster than the left. Since the left wheel is already going as fast as it possibly can, the right wheel needs to be slowed down to straighten out the robot’s path. To slow it down, change the us parameter in servoRight.writeMicroseconds(us) to a value closer to 1500. First, try 1400. Is it still going too fast? Raise it 1410. Keep raising the parameter by 10 until the BOE Shield-Bot no longer curves to the left. If any adjustment overshoots ‘straight’ and your BOE Shield-Bot starts curving to the right instead, start decreasing the us parameter by smaller amounts. Keep refining that us parameter until your BOE Shield-Bot goes straight forward. This is called an iterative process, meaning that it takes repeated tries and refinements to get to the right value.
If your BOE Shield-Bot curved to the right instead of the left, it means you need to slow down the left wheel. You’re starting with servoLeft.writeMicroseconds(1700) so the us parameter needs to decrease. Try 1600, then reduce by increments of 10 until it either goes straight or starts turning the other direction, and increase by 2 if you overshoot.
You might find that there’s an entirely different situation when you program your BOE Shield-Bot to roll backward.
The amount of time the BOE Shield-Bot spends rotating in place determines how far it turns. So, to tune a turn, all you need to do is adjust the delay function’s ms parameter to make it turn for a different amount of time.
Let’s say that the BOE Shield-Bot turns just a bit more than 90° (1/4 of a full circle). Try delay(580), or maybe even delay(560). If it doesn’t turn far enough, make it run longer by increasing the delay function’s ms parameter 20 ms at a time.
The smallest change that actually makes a difference is 20.
Servo control pulses are sent every 20 ms, so adjust your delay function call’s ms parameter in multiples of 20.
If you find yourself with one value slightly overshooting 90° and the other slightly undershooting, choose the value that makes it turn a little too far, then slow down the servos slightly. In the case of rotating left, both writeMicroseconds us parameters should be changed from 1300 to something closer to 1500. Start with 1400 and then gradually increase the values to slow both servos. For rotating right, start by changing the us parameters from 1700 to 1600, and then experiment with reducing in increments of 10 from there.
Carpeting can cause navigation errors.
If you are running your BOE Shield-Bot on carpeting, don’t expect perfect results! The way the carpet pile is laying can affect the way your BOE Shield-Bot travels, especially over long distances. For more precise maneuvers, use a smooth surface.
In many robotics contests, more precise robot navigation means better scores. One popular entry-level robotics contest is called dead reckoning. The entire goal of this contest is to make your robot go to one or more locations and then return to exactly where it started.
You might remember asking your parents this question, over and over again, while on your way to a vacation destination or relatives’ house:
“Are we there yet?”
Perhaps when you got a little older, and learned division in school, you started watching the road signs to see how far it was to the destination city. Next, you checked the car’s speedometer. By dividing the speed into the distance, you got a pretty good estimate of the time it would take to get there. You may not have been thinking in these exact terms, but here is the equation you were using:
U.S. customary units example: If you’re 140 miles away from your destination, and you’re traveling 70 miles per hour, it’s going to take 2 hours to get there.
Metric units example: If you’re 200 kilometers away from your destination, and you’re traveling 100 kilometers per hour, it’s going to take 2 hours to get there.
You can do the same exercise with the BOE Shield-Bot, except you have control over how far away the destination is. Here’s the equation you will use:
// Robotics with the BOE Shield - ForwardOneSecond // Make the BOE Shield-Bot roll forward for one seconds, then stop. #include <Servo.h> // Include servo library 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 // Full speed forward servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(1000); // ...for 1 second servoLeft.detach(); // Stop sending servo signals servoRight.detach(); } void loop() // Main loop auto-repeats { // Empty, nothing needs repeating }
The distance you just recorded is your BOE Shield-Bot’s speed, in units per second. Now, you can figure out how many seconds your BOE Shield-Bot has to travel to go a particular distance.
Inches and centimeters per second
The abbreviation for inches is in, and the abbreviation for centimeters is cm. Likewise, inches per second is abbreviated in/s, and centimeters per second is abbreviated cm/s. Both are convenient speed measurements for the BOE Shield-Bot. There are 2.54 cm in 1 in. You can convert inches to centimeters by multiplying the number of inches by 2.54. You can convert centimeters to inches by dividing the number of centimeters by 2.54
Keep in mind that your calculations will be in terms of seconds, but the delay function will need a parameter that’s in terms of milliseconds. So, take your result, which is in terms of seconds, and multiply it by 1000. Then, use that value in your delay function call. For example, to make your BOE Shield-Bot run for 2.22 seconds, you’d use delay(2220) after your writeMicroseconds calls.
U.S. customary units example: At 9 in/s, your BOE Shield-Bot has to travel for 2.22 s to travel 20 in.
Metric units example: At 23 cm/s, your BOE Shield-Bot has to travel for 2.22 s to travel 51 cm.
Both examples above resolve to the same answer:
So, use delay(2220) after your writeMicroseconds calls:
servoLeft.writeMicroseconds(1700); servoRight.writeMicroseconds(1300); delay(2220);
Now it’s time to try this out with distances that you choose.
Increase the accuracy of your BOE Shield-Bot distances with devices called encoders which count the holes in the BOE Shield-Bot’s wheels as they pass.
Ramping is a way to gradually increase or decrease the speed of the servos instead of abruptly starting or stopping. This technique can increase the life expectancy of both your BOE Shield-Bot’s batteries and your servos.
The diagram below shows an example of how to ramp up to full speed. The for loop declares an int variable named speed, and uses it to repeat the loop 100 times. With each repetition of the loop, the value of speed increases by 2 because of the speed+=2 expression in the for loop’s increment parameter. Since the speed variable is in each writeMicroseconds call’s us parameter, it affects the value each time the for loop repeats. With the 20 ms delay between each repetition, the loop repeats at about 50 times per second. That means it takes speed 1 second to get to 100 in steps of 2, and at that point, both servos will be going about full speed.
Let’s take a closer look at the trips through the for loop from this diagram:
// Robotics with the BOE Shield - StartAndStopWithRamping // Ramp up, go forward, ramp down. #include <Servo.h> // Include servo library 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 for(int speed = 0; speed <= 100; speed += 2) // Ramp up to full speed. { servoLeft.writeMicroseconds(1500+speed); // us = 1500,1502,...1598,1600 servoRight.writeMicroseconds(1500-speed); // us = 1500,1498,...1402,1400 delay(20); // 20 ms at each speed } delay(1500); // Full speed for 1.5 seconds for(int speed = 100; speed >= 0; speed -= 2) // Ramp from full speed to stop { servoLeft.writeMicroseconds(1500+speed); // us = 1600,1598,...1502,1500 servoRight.writeMicroseconds(1500-speed); // us = 1400,1402,...1498,1500 delay(20); // 20 ms at each speed } servoLeft.detach(); // Stop sending servo signals servoRight.detach(); } void loop() // Main loop auto-repeats { // Empty, nothing to repeat }
You can also create routines to combine ramping with other maneuvers. Here’s an example of how to ramp up to full speed going backward instead of forward. The only difference between this routine and the forward ramping routine is that the value of speed starts at zero and counts to –100.
for(int speed = 0; speed >= -100; speed -= 2)// Ramp stop to full reverse { servoLeft.writeMicroseconds(1500+speed); // us = 1500,1498, 1496...1400 servoRight.writeMicroseconds(1500-speed); // us = 1500,1502, 1508...1600 delay(20); // 20 ms at each speed }
You can also make a routine for ramping into and out of a turn. Here is a right-turn ramping example. Notice that instead of 1500+speed for one wheel and 1500–speed for the other, now they are both 1500+speed. For left-turn ramping, they would both be 1500–speed.
for(int speed = 0; speed <= 100; speed += 2) // Ramp stop to right turn { servoLeft.writeMicroseconds(1500+speed); // us = 1500,1502, 1508...1600 servoRight.writeMicroseconds(1500+speed); // us = 1500,1502, 1508...1600 delay(20); // 20 ms at each speed } for(int speed = 100; speed >= 0; speed -= 2)// right turn to stop { servoLeft.writeMicroseconds(1500+speed); // us = 1600,1598, 1597...1500 servoRight.writeMicroseconds(1500+speed); // us = 1600,1598, 1597...1500 delay(20); // 20 ms at each speed }
One convenient way to execute pre-programmed maneuvers is with functions. In the next chapter, your BOE Shield-Bot will have to perform maneuvers to avoid obstacles, and a key ingredient for avoiding obstacles is executing pre-programmed maneuvers.
The setup and loop functions are built into the Arduino language, but you can add more functions that do specific tasks for your sketch. This activity introduces how to add more functions to your sketch as well as a few different approaches to creating reusable maneuvers with those functions.
The diagram below shows part of a sketch that contains a function named example added at the end, below the loop function. It begins and gets named with the function definition void example(). The empty parentheses means that it doesn’t need any parameters to do its job, and void indicates that it does not return a value (we’ll look at functions that return values in a later chapter). The curly braces {} that follow this definition contain the example function’s block of code.
There is a function call to example in the setup function, labeled in the diagram above. That example() line tells the sketch to go find the function with that name, execute its code, and come back when done. So, the sketch jumps down to void example() and executes the two commands in its curly braces. Then, it returns to the function call and continues from there. Here is the order of events you will see when you run the sketch:
// Robotics with the BOE Shield – SimpleFunctionCall // This sketch demonstrates a simple function call. void setup() { Serial.begin(9600); Serial.println("Before example function call."); delay(1000); example(); // This is the function call Serial.println("After example function call."); delay(1000); } void loop() { } void example() // This is the function { Serial.println("During example function call."); delay(1000); }
Remember that a function can have one or more parameters—data that the function receives and uses when it is called. The diagram below shows the pitch function from the next sketch. It is declared with void pitch(int Hz). Recall from Chapter 1, Activity #3 [3] that the Arduino stores variable values in different data types, with int specifying an integer value in the range of -32,768 to 32,767. Here, the term int Hz in the parentheses defines a parameter for the pitch function; in this case, it declares a local variable Hz of data type int.
Local variables, remember, are declared within a function, and can only be seen and used inside that function. If a local variable is created as a parameter in the function declaration, as void pitch(int Hz) is here, initialize it by passing a value to it each time the function is called. For example, the call pitch(3500) passes the integer value 3500 to the pitch function’s int Hz parameter.
So, when the first function call to pitch is made with pitch(3500), the integer value 3500 gets passed to Hz. This initializes Hz to the value 3500, to be used during this trip through the pitch function’s code block. The second call, pitch(2000), initializes Hz to 2000 during the sketch’s second trip through the pitch function’s code block.
// Robotics with the BOE Shield – FunctionCallWithParameter // This program demonstrates a function call with a parameter. void setup() { Serial.begin(9600); Serial.println("Playing higher pitch tone..."); pitch(3500); // pitch function call passes 3500 to Hz parameter delay(1000); Serial.println("Playing lower pitch tone..."); pitch(2000); // pitch function call passes 2000 to Hz parameter delay(1000); } void loop() { } void pitch(int Hz) // pitch function with Hz declared as a parameter { Serial.print("Frequency = "); Serial.println(Hz); tone(4, Hz, 1000); delay(1000); }
Here is a modified pitch function that accepts two parameters: Hz and ms. This new pitch function controls how long the tone lasts.
void pitch(int Hz, int ms) { Serial.print("Frequency = "); Serial.println(Hz); tone(4, Hz, ms); delay(ms); }
Here are two calls to the modified pitch function, one for a 0.5 second 3500 Hz tone, and the other for a 1.5 second 2000 Hz tone:
pitch(3500, 500); pitch(2000, 1500);
Notice that each of these calls to pitch includes two values, one to pass to the Hz parameter, and one to pass to the ms parameter. The number of values in a function call must match the number of parameters in that function’s definition, or the sketch won’t compile.
Let’s try putting the forward, turnLeft, turnRight, and backward navigation routines inside functions. Here’s an example:
// Robotics with the BOE Shield - MovementsWithSimpleFunctions // Move forward, left, right, then backward for testing and tuning. #include <Servo.h> // Include servo library 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 forward(2000); // Go forward for 2 seconds turnLeft(600); // Turn left for 0.6 seconds turnRight(600); // Turn right for 0.6 seconds backward(2000); // go backward for 2 seconds disableServos(); // Stay still indefinitely } void loop() // Main loop auto-repeats { // Empty, nothing needs repeating } void forward(int time) // Forward function { servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(time); // Maneuver for time ms } void turnLeft(int time) // Left turn function { servoLeft.writeMicroseconds(1300); // Left wheel clockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(time); // Maneuver for time ms } void turnRight(int time) // Right turn function { servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1700); // Right wheel counterclockwise delay(time); // Maneuver for time ms } void backward(int time) // Backward function { servoLeft.writeMicroseconds(1300); // Left wheel clockwise servoRight.writeMicroseconds(1700); // Right wheel counterclockwise delay(time); // Maneuver for time ms } void disableServos() // Halt servo signals { servoLeft.detach(); // Stop sending servo signals servoRight.detach(); }
You should recognize the pattern of movement your BOE Shield-Bot makes; it is the same one made by the ForwardLeftRightBackward sketch. This is a second example of the many different ways to structure a sketch that will result in the same movements. There will be a few more examples before the end of the chapter.
Want to keep performing that set of four maneuvers over and over again? Just move those four maneuvering function calls from the setup function into the loop function. Try this:
Cut the function calls to forward(2000), turnLeft(600), turnRight(600), and backward(2000) out of the setup function and paste them into the loop function. It should look like this when you’re done:
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 // disableServos(); // Stay still indefinitely } void loop() // Main loop auto-repeats { forward(2000); // Go forward for 2 seconds turnLeft(600); // Turn left for 0.6 seconds turnRight(600); // Turn right for 0.6 seconds backward(2000); // go backward for 2 seconds
The last sketch, MovementsWithSimpleFunctions, was kind of long and clunky. And, the four functions it uses to drive the robot are almost the same. The TestManeuverFunction sketch takes advantage of those function's similarities and streamlines the code.
TestManeuverFunction has a single function for motion named maneuver that accepts three parameters: speedLeft, speedRight, and msTime:
void maneuver(int speedLeft, int speedRight, int msTime)
The rules for speedLeft and speedRight, listed below, are easy to remember. Best of all, with this maneuver function you don’t have to think about clockwise and counterclockwise rotation anymore.
The rules for msTime are:
Here is what calls to this function will look like for the familiar forward-backward-left-right-stop sequence:
maneuver(200, 200, 2000); // Forward 2 seconds maneuver(-200, 200, 600); // Left 0.6 seconds maneuver(200, -200, 600); // Right 0.6 seconds maneuver(-200, -200, 2000); // Backward 2 seconds maneuver(0, 0, -1); // Disable servos
// Robotics with the BOE Shield - TestManeuverFunction // Move forward, left, right, then backward with maneuver function. #include <Servo.h> // Include servo library 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 maneuver(200, 200, 2000); // Forward 2 seconds maneuver(-200, 200, 600); // Left 0.6 seconds maneuver(200, -200, 600); // Right 0.6 seconds maneuver(-200, -200, 2000); // Backward 2 seconds maneuver(0, 0, -1); // Disable servos } 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 }
With the maneuver function, 0 is stop, 100 to –100 is the speed control range, and 200 and –200 are overkill to keep the servos running as fast as they possibly can.
The TestManeuverFunction sketch makes it easy to define custom maneuvers quickly. Just pass new parameters for each wheel rotation and maneuver duration to each call of the maneuver function. For example, let’s make the left wheel move at half speed while the right wheel moves at full speed to draw an arc for 3 seconds. Here is what that function call would look like:
maneuver(50, 100, 3000);
Here is another example that keeps the left wheel still and moves the right wheel forward for a left pivot turn:
maneuver(0, 200, 1200);
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;
This chapter was all about robot navigation, experimenting with many different programming approaches and employing some robotics and engineering skills:
Programming
Robotics Skills
Engineering Skills
servoLeft.writeMicroseconds(1500); servoRight.writeMicroseconds(1300);
BOE Shield-Bot speed = 11 in/s
BOE Shield-Bot distance = 36 in/s
time = (BOE Shield-Bot distance / BOE Shield-Bot speed) * (1000 ms / s)
= (36 / 11 ) * (1000)
= 3.727272…s * 1000 ms/s
≈ 3727 ms
servoLeft.writeMicroseconds(1300); servoRight.writeMicroseconds(1700); delay(2500);
// 30/180 = 1/6, so use 1200/6 = 200 servoLeft.writeMicroseconds(1700); servoRight.writeMicroseconds(1700); delay(200); // alternate approach servoLeft.writeMicroseconds(1700); servoRight.writeMicroseconds(1700); delay(1200 * 30 / 180); // 45/180 = 1/4, so use 1200/4 = 300 servoLeft.writeMicroseconds(1700); servoRight.writeMicroseconds(1700); delay(300); // 60/180 = 1/3, so use 1200/3 = 400 servoLeft.writeMicroseconds(1700); servoRight.writeMicroseconds(1700); delay(400);
// forward 1 second servoLeft.writeMicroseconds(1700); servoRight.writeMicroseconds(1700); delay(1000); // ramp into pivot for(int speed = 0; speed <= 100; speed+=2) { servoLeft.writeMicroseconds(1500); servoRight.writeMicroseconds(1500+speed); delay(20); }; // ramp out of pivot for(int speed = 100; speed >= 0; speed-=2) { servoLeft.writeMicroseconds(1500); servoRight.writeMicroseconds(1500+speed); delay(20); } // forward again servoLeft.writeMicroseconds(1700); servoRight.writeMicroseconds(1700); delay(1000);
Circle sketch:
// Robotics with the BOE Shield - Chapter 4, project 2 - Circle // BOE Shield-Bot navigates a circle of 1 yard diameter. #include <Servo.h> // Include servo library 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 // Arc to the right servoLeft.writeMicroseconds(1600); // Left wheel counterclockwise servoRight.writeMicroseconds(1438); // Right wheel clockwise slower delay(25500); // ...for 25.5 seconds servoLeft.detach(); // Stop sending servo signals servoRight.detach(); } void loop() // Main loop auto-repeats { // Nothing needs repeating }
Triangle sketch:
// Robotics with the BOE Shield - Chapter 4, project 2 - Triangle // BOE Shield-Bot navigates a triangle with 1 yard sides and 120 // degree angles. Go straight 1 yard, turn 120 degrees, repeat 3 times #include <Servo.h> // Include servo library 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 for(int index = 1; index <= 3; index++) { // Full speed forward servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise slower delay(5500); // ...for 5.5 seconds // Turn left 120 degrees servoLeft.writeMicroseconds(1300); // Left wheel counterclockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise slower delay(700); } servoLeft.detach(); // Stop sending servo signals servoRight.detach(); } void loop() // Main loop auto-repeats { // Nothing needs repeating }
Links
[1] https://learn.parallax.com/sites/default/files/content/shield/robo_ch4/RoboticsBOEShield_Ch4_20120310.zip
[2] https://learn.parallax.com/207
[3] https://learn.parallax.com/node/146
[4] https://learn.parallax.com/187