122 32335 Robotics BOE Shield Bot Arduino v1.0
122 32335 Robotics BOE Shield Bot Arduino v1.0
122 32335 Robotics BOE Shield Bot Arduino v1.0
by Andy Lindsay
Version 1.0
WARRANTY
Parallax warrants its products against defects in materials and workmanship for a period of 90 days from
receipt of product. If you discover a defect, Parallax will, at its option, repair or replace the merchandise, or
refund the purchase price. Before returning the product to Parallax, call for a Return Merchandise
Authorization (RMA) number. Write the RMA number on the outside of the box used to return the
merchandise to Parallax. Please enclose the following along with the returned merchandise: your name,
telephone number, shipping address, and a description of the problem. Parallax will return your product or
its replacement using the same shipping method used to ship the product to Parallax.
If, within 14 days of having received your product, you find that it does not suit your needs, you may return it
for a full refund. Parallax will refund the purchase price of the product, excluding shipping/handling costs.
This guarantee is void if the product has been altered or damaged. See the Warranty section above for
instructions on returning a product to Parallax.
Copyright 2012-2016 by Parallax Inc. All Rights Reserved. By obtaining a printed copy of this
documentation or software you agree that it is to be used exclusively with Parallax Board of Education Shield
for Arduino products. Any other uses are not permitted and may represent a violation of Parallax copyrights,
legally punishable according to Federal and international copyright or intellectual property laws. Any
duplication of this documentation for commercial uses is expressly prohibited by Parallax Inc. Duplication
for educational use, in whole or in part, is permitted subject to the following conditions: the material is to be
used solely in conjunction with Parallax microcontrollers and products, and the user may recover from the
student only the cost of duplication. Check with Parallax for approval prior to duplicating any of our
documentation in part or whole for any other use.
BASIC Stamp, Board of Education, Stamps in Class, and Boe-Bot are registered trademarks of Parallax Inc.
BOE Shield-Bot, HomeWork Board, PING))), Parallax, the Parallax logo, Propeller, and Spin are trademarks of
Parallax Inc. Arduino is a registered trademark of Arduino, LLC. Other brand and product names herein are
trademarks or registered trademarks of their respective holders.
ISBN 978-1-928-98261-6
1.0.0-160829-MM
DISCLAIMER OF LIABILITY
Parallax Inc. is not responsible for special, incidental, or consequential damages resulting from any breach of
warranty, or under any legal theory, including lost profits, downtime, goodwill, damage to or replacement of
equipment or property, or any costs of recovering, reprogramming, or reproducing any data stored in or
used with Parallax products. Parallax is also not responsible for any personal damage, including that to life
and health, resulting from use of any of our products. You take full responsibility for your microcontroller
application, no matter how life-threatening it may be.
ERRATA
While great effort is made to assure the accuracy of our texts, errors may still exist. Occasionally an errata
sheet with a list of known errors and corrections for a given text will be posted on the related product page
at www.parallax.com. If you find an error, please send an email to editor@parallax.com.
2 Robotics with the BOE Shield-Bot
Table of Contents
About This Tutorial ....................................................................................................................... 6
About This Edition ........................................................................................................................ 6
Why Study Robotics? .................................................................................................................... 6
A Bit of Background ...................................................................................................................... 7
Audience & Support ..................................................................................................................... 8
Author & Contributors.................................................................................................................. 8
Index.................................................................................................................... 268
New to microcontroller programming? This is a good place to start! The code examples
introduce Arduino programming concepts little by little, with each example explained fully.
New to electronics? Each electronic component is described with a circuit symbol and part
drawing. Traditional schematics next to wiring diagrams make it easy to build the circuits.
Version 1.0 is the tutorials first print edition. It is based on the web tutorial posted at
http://learn.parallax.com/shieldrobot as of 6/01/2016. Bit of sprucing up and minor
changes to accommodate the print format were made, but the educational content remains
parallel to the original.
Robots have been in use for all kinds of manufacturing and in all manner of exploration
vehiclesand in many science fiction filmsfor a long time. The word robot first appeared
in a Czechoslovakian satirical play, Rossums Universal Robots, by Karel Capek back in 1920!
Robots in this play tended to be human-like, and much science fiction that followed involved
these robots trying to fit into society and make sense out of human emotions.
Then, General Motors installed the first robots in its manufacturing plant in 1961. Those
automated single-purpose machines presented an entirely different image from the humanlike robots of science fiction. As technology continues to advance, increasingly human-like
robots designed for socially oriented tasks are emerging, and many types of robots co-exist
today.
Regardless of a robot's outer form, building and programming most robots requires a
combination of mechanics, electronics, and problem-solving. What you can learn from this
tutorial will be relevant to real-world robot applications. Of course there will be differences
in size and sophistication, but the underlying mechanical principles, basic circuits, and
programming concepts are used by engineers every day. New uses for robots are emerging
constantly. Roboticists are in demand!
A Bit of Background
The BOE Shield-Bot is a small microcontroller development platform on wheels. It is
designed for experimenting with circuit-building and programming, using standard
electronic components.
It is a variation of the original Boe-Bot robot with its BASIC Stamp 2 brain, shown below. It
was introduced by Parallax Inc. in 1999 and continues to be widely used with schools for
STEM courses, because PBASIC is a very easy language for a first-time, text-based
programming experience.
The Arduino microcontroller arrived on the scene in 2005 and its popularity grew through
the DIY (do-it-yourself) hobby community. Parallax teamed up with SimplyTronics to design
the Board of Education Shield, which makes the Arduino hardware compatible with the
Boe-Bot chassis. The Arduino can do the same tasks as the BASIC Stamp, though in a slightly
different way, but it was still straightforward to rewrite the original Robotics with the BoeBot to support the Shield-Bot. This gives the Arduino hobby community the opportunity to
wrap a robotics shield around an Uno. It also gives teachers a different programming
language option for working with Parallax robots.
Are you a middle-school student? You can be successful by following the check-marked
instructions with your teachers support.
If you are a pre-engineering student, push yourself a little farther and test your
comprehension and problem-solving skills with the questions, exercises, and projects in
each chapter summary (solutions are provided!)
If you are an independent learner working on your own, go at your own pace and check in
with Parallaxs Robotics forum (forums.parallax.com) if you get stuck.
If you are an educator, feel free to contact our team at education@parallax.com for
additional resources and support deploying the Shield-Bot in your STEM program.
Andy Lindsay joined Parallax Inc. in 1999, and has since authored more than a dozen books,
including Whats a Microcontroller? as well as numerous articles, product documents, and
web tutorials for the company. The original Robotics with the Boe-Bot that is the inspiration
for this book was designed and updated based on observations and educator feedback that
Andy collected while traveling the nation and abroad teaching Parallax Educator Courses
and events. Andy studied Electrical and Electronic Engineering at California State University,
Sacramento, and is a contributing author to several papers that address the topic of
microcontrollers in pre-engineering curricula. When hes not writing educational material,
Andy does product and applications engineering for Parallax.
Special Contributors
The Parallax team assembled to prepare this edition includes technical writing by Andy
Lindsay, cover art by Jen Jacobs, graphic illustrations by Courtney Jacobs and Andy Lindsay,
photography by Robert Reimers, editing and layout by Stephanie Lindsay.
Several customers helped test-drive this material. Thanks go to Gordon McComb for testdriving and technical feedback on the original chapter drafts. Special thanks also go to Matt
Zawlocki and his Fall 2015 middle-school students for trying all of the example sketches
with the Codebender browser-based program editor.
Education
Shield
The activities in this tutorial will guide you through building the mechanical parts and
circuits that make the BOE Shield-Bot work. Then, youll write simple programs that make
the Arduino and your robot do four essential robotic tasks:
To do the activities in this tutorial, you will need both hardware and software.
Robot Shield Kit with Arduino (includes Arduino Uno and USB A-B cable;
#32335)
Robotics Shield Kit for Arduino (no Arduino nor USB A-B cable; #130-35000)
Boe-Bot Retrofit: If you have a BASIC Stamp Boe-Bot robot, you can convert it to a Shield-Bot with
a Retrofit Kit, available with an Arduino Uno (#910-32333) or without (#32333); USB programming
cable sold separately (#805-00007).
Robotics with the BOE Shield-Bot 9
This tutorial was also tested with a Duemilanove and original Mega. These Arduino modules
automatically decide whether to draw power from USB or an external source (like the
Shield-Bots battery pack).
Older Arduino Models are not guaranteed to work. If you have one, you may have to set its
power selection jumper. (Dont worry about this if you have an Uno, Duemilanove, or Mega.) The
circuit is labeled PWR_SEL. It is three pins with a small cover called a shunt that slides over two of
three pins. For now, make the shunt cover the USB and center pins. Later, when you switch to
using the Shield-Bots battery pack, move the shunt to cover the EXT pin and center pin instead.
Software Options
This tutorial requires the Arduino language 1.0 or higher. There are two recommended
software options for using this language.
If this is your first time using an Arduino, Activity #1 will help you get started with your
choice of software, connecting your hardware, and testing your programming connection.
The rest of this chapter includes a series of example programs (called sketches) that
introduce common programming concepts. The sketches will do some of the most basic yet
important things for a robot:
Say Hello!
Store and retrieve values
Solve math problems
Make decisions
Count and control repetitions
These examples dont require interaction with external circuits. In later chapters you will
start building circuits and make your robot move. You will also learn additional
programming techniques like keeping lists of values and writing pieces of reusable code.
Before continuing, is a good idea to make sure you have all of the correct parts to build and
program your Shield-Bot. Use the pictures and part numbers on the following pages to
double-check the robot chassis parts, small hardware, and electronic components. If you
need anything, contact sales@parallax.com. (Kit parts and quantities subject to change
without notice).
aluminum chassis
(#700-00022)
Wheels will connect to servo motors to drive the Shield-Bot, and a tail wheel ball will attach
to the chassis with a cotter pin. Note that the wheel and tire styles have changed over time.
Yours may look different; that's okay.
(2) small robot
wheels and O-ring
tires (#28114)
tail wheel
(#700-00009)
Shield-Bot Hardware
A bag of hardware supplies everything you will need to assemble your robot in Chapter 3,
shown below. There will be some spare parts left over and not pictured that is okay! Note
that both regular nuts and Nylon-core lock-nuts are provided; you may choose to use either
one. Replacement Robot Hardware Refresher Packs (#570-35000) are also sold separately.
(1) 1/16 cotter pin
(#700-00023)
Your Infrared Receivers may look different, since suppliers change over time. If you ever
need replacements, be sure to order the Infrared Receiver for Boe-Bot and Shield-Bot
(#350-00039).
(8) 220 ohm resistors, 1/4 W, 5% (#150-02210)
220 = RED, RED, BROWN
(1) piezospeaker
(#900-000101)
(2) phototransistors
(#350-00029)
Arduino IDE software and drivers install on your Windows, Mac, or Linux computer. You do
not need to be online to use it.
Follow this link to the Codebender website and click on the Register button.
(https://codebender.cc/?referrer=Parallax%20Shield-Bot)
Follow their instructions for registering a new account, or for signing up with an
existing Google account.
Then, follow the instructions for installing the Codebender plugin for your
browser, and downloading USB drivers if needed.
Make sure you follow the instructions through the part where you connect your Arduino Uno
module to your computer's USB port with the USB A to B cable, and try the sample sketch to
confirm your programming connection.
Codebender user Parallax Shield-Bot lists all the sketches in the book. The names will be slightly
different, to keep the them organized. Check the descriptions if you are in doubt. Though you will
learn faster if you type them in yourself, you may clone and edit or download the sketches as
desired. Use the URL: https://codebender.cc/user/Parallax%20Shield-Bot
Status
Serial Monitor
Run
Message pane
void setup()
{
Serial.begin(9600);
Serial.print("Hello!");
}
void loop()
{
//Add code that repeats automatically here.
}
Type the sketches if you can! If you type in all of the example sketches by hand, you'll develop
your programming skills faster. But, sometimes it's helpful to have tested sketches on hand for
troubleshooting circuits or finding bugs. You can download the sketches for Arduino IDE from
http://learn.parallax.com/shieldrobot
Be sure you have capitalized Serial both times, or the sketch wont work.
Also, notice in the figure that the sketch uses parentheses() and curly braces {}.
Be sure to use the right ones in the right places!
Click the Verify button to make sure your code doesnt have any typing errors.
Look for the Binary sketch size text in the message pane.
If its there, your code compiled and is ready to load to the Arduino.
If theres a list of errors instead, its trying to tell you it cant compile your code.
So, find the typing mistake and fix it!
Click the arrow button to upload the sketch and run it on the Arduino. The status
line under your code will display Compiling sketch, Uploading, and then
Done uploading.
After the sketch is done uploading, click the Serial Monitor button.
If the Hello message doesnt display as soon as the Serial Monitor window opens,
check for the 9600 baud setting in the lower right corner of the monitor.
Use File Save to save your sketch. Give it the name HelloMessage.
If youve successfully run the code and verified the output in the Serial Terminal, you are
ready to see How the Hello Sketch Code Works on page 21.
Take a look at the screen capture of the Codebender environment and its Serial
Monitor on the next page.
FYI: To keep things simple, the rest of the Shield Robot tutorials will feature directions and screen
captures for the Arduino IDE software.
If your BOE Shield is not already plugged into your computer, do so now.
Click on "Create a Sketch" to create a blank sketch.
Name it Hello, and optionally enter a short description of your choice.
Type in the following code:
void setup()
{
Serial.begin(9600);
Serial.print("Hello!");
}
void loop()
{
//Add code that repeats automatically here.
}
Be sure you have capitalized Serial both times, or the sketch wont work.
Also, notice that the sketch uses parentheses() and curly braces {}. Be sure to
use the right ones in the right places!
First, verify that the code is written correctly by clicking the Verify button.
Make sure your Arduino's COM port is selected from the dropdown.
Then, open the Serial Monitor pane by clicking the Serial Monitor button.
Once the above steps are completed successfully and your code is verified to be
correct, click Run.
Your Serial Monitor should be set to 9600 baud (below), then click the Connect
button.
Watch the Hello message appear in the Serial Monitor!
Robotics with the BOE Shield-Bot 19
CHROME ALERT! The screen capture above is from Firefox. If you are using Chrome, instead of
"connected at 9600" you will see the warning below. You must click Disconnect before physically
unplugging your board.
In this example, both statements are function calls to functions in the Arduinos built-in, prewritten Serial code library: Serial.begin(speed) and Serial.print(val). Here, speed
and val are parameters, each describing a value that its function needs passed to it to do its
job. The sketch provides these values inside parentheses in each function call.
Serial.begin(9600); passes the value 9600 to the speed parameter. This tells the
Arduino to get ready to exchange messages with the Serial Monitor at a data rate of 9600
bits per second. Thats 9600 binary ones or zeros per second, and is commonly called a baud
rate.
Serial.print(val); passes the message Hello! to the val parameter. This tells the
Arduino to send a series of binary ones and zeros to the Serial Monitor. The monitor decodes
and displays that serial bitstream as the Hello! message.
setup function
Function
definition
Code block
contained in
curly braces { }
void setup()
{
Serial.begin(9600);
Serial.print("Hello!");
}
Statements
After the setup function is done, the Arduino automatically skips to the loop function and
starts doing what the statements in its curly braces tell it to do. Any statements in loop will
be repeated over and over again, indefinitely. Since all this sketch is supposed to do is print
one "Hello!" message, the loop function doesnt have any actual commands. Theres just a
notation for other programmers to read, called a comment. Anything to the right of // on a
given line is for programmers to read, not for the Arduino softwares compiler. (A compiler
takes your sketch code and converts it into numbersa microcontrollers native language.)
Comment for
you and other
coders to read
void loop()
{
What is void? Why do these functions end in ()? The first line of a function is its definition, and
it has three parts: return type, name, and parameter list. For example, in the function void
setup() the return type is void, the name is setup, and the parameter list is empty theres
nothing inside the parentheses (). Void means nothingwhen another function calls setup or
loop, these functions would not return a value. An empty parameter list means that these
functions do not need to receive any values when they are called to do their jobs.
Compare your changes to the figure and verify that they are correct.
Run the sketch on the Arduino and then open the Serial Monitor again.
The added line delay(1000) passes the value 1000 to the delay functions ms parameter.
Its requesting a delay of 1000 milliseconds. 1 ms is 1/1000 of a second. So, delay(1000)
makes the sketch wait for 1000/1000 = 1 second before letting it move on to the next line of
code.
How about having each "Hello!" message on a new line? That would make the messages
scroll down the Serial Monitor, instead of across it. All you have to do is change print to
println, which is short for print line.
Run the modified sketch and watch it print each "Hello!" message on a new line.
If youre looking for links to print or println, youll have to find and follow the Serial link
first.
Variables are names you can create for storing, retrieving, and using values in the Arduino
microcontrollers memory. Here are three example variable declarations from the next
sketch:
int a = 42;
char c = 'm';
float root2 = sqrt(2.0);
The declaration int a = 42 creates a variable named a. The int part tells the Arduino
software what type of variable (or data type) its dealing with. The int type can store
integer values ranging from -32,768 to 32,767. The declaration also assigns a an initial
value of 42. (The initial value is optional, you could instead just declare int a, and then
later assign the value 42 to a with a = 42.)
Next, char c = 'm' declares a variable named c of the type char (which is for storing
characters) and then assigns it the value 'm'.
Then, float root2 = sqrt(2.0) declares a variable named root2. The variable type is
float, which can hold decimal values. Here, root2 is initialized to the floating-point
representation of the square root of two: sqrt(2.0).
Now that your code has stored values to memory, how can it retrieve and use them? One
way is to simply pass each variable to a functions parameter. Here are three examples,
where the Serial.println(val) function displays the value of the variable inside the
parentheses.
Serial.println(a);
Serial.println(c);
Serial.println(root2);
One nice thing about variable types is that Serial.println recognizes each type and
displays it correctly in the Serial Monitor. (Also, the C++ compiler in the Arduino software
requires all declared variables to have a type, so you cant leave it out.)
ASCII stands for American Standard Code for Information Exchange. Its a common code system
for representing computer keys and characters in displays. For example, the declaration char c
= 'm' makes the Arduino store the number 109 in the c variable. Serial.println(c) makes
the Arduino send the number 109 to the Serial Monitor. When the Serial Monitor receives that
109, it automatically displays the letter m. See http://learn.parallax.com/ascii for codes 1-127.
Robotics with the BOE Shield-Bot 25
Try both approaches. So, do you think the letters l, m, n, o, and p would be
represented by the ASCII codes 108, 109, 110, 110, 111, and 112?
Modify your sketch to find out the decimal ASCII codes for l, m, n, o, p.
If you can, go to http://learn.parallax.com/ascii and try other ASCII characters.
So far, weve declared variables inside a function block (inside the functions curly braces),
which means they are local variables. Only the function declaring a local variable can see or
modify it. Also, a local variable only exists while the function that declares it is using it. After
that, it gets returned to unallocated memory so that another function (like loop) could use
that memory for a different local variable.
If your sketch has to give more than one function access to a variables value, you can use
global variables. To make a variable global, just declare it outside of any function, preferably
before the setup function. Then, all functions in the sketch will be able to modify or retrieve
its value. The next example sketch declares global variables and assigns values to them from
within a function.
This example sketch declares a, c, and root2 as global variables (instead of local). Now that
they are global, both the setup and loop functions can access them.
Modify your sketch to match the one below, and save it as StoreRetrieveGlobal.
Run it on the Arduino, and then open the Serial Monitor and verify that the
correct values are displayed repeatedly by the loop function.
Open the Arduino Language Reference, and check out the Data Types list.
Follow the float link and learn more about this data type.
The long data type will be used in a later chapter; open both the long and int
sections. How are they similar? How are they different?
Arithmetic operators are useful for doing calculations in your sketch. In this activity, well
focus on the basics: assignment (=), addition (+), subtraction (-), multiplication (*),
division (/), and modulus (%, the remainder of a division calculation).
Open up the Arduino Language Reference, and take a look at the list of Arithmetic
Operators.
The next example sketch, SimpleMath, adds the variables a and b together and stores the
result in c. It also displays the result in the Serial Monitor.
Notice that c is now declared as an int, not a char variable type. Another point, int c =
a + b uses the assignment operator (=) to copy the result of the addition operation that
adds a to b. The next screen capture shows the expected result of 89 + 42 = 131 in the Serial
Monitor.
Fit your variables to the result values you need to store. This will use less memory so you
can write larger sketches that will execute more efficiently.
Replace the addition (+) operator with the subtraction () operator. Replace
both instances of + in the sketch.
Run the modified sketch and verify the result in the Serial Monitor.
Repeat for the multiplication (*), division (/) and modulus (%) operators.
Imagine entering your BOE Shield-Bot in a contest where you have to make it travel in a
circle, but the radius will only be announced a few minutes before the contest. Youd have an
advantage if your code could calculate the circumference of the circle. The formula is 2
r, where r is the circles radius and 3.14159. This calculation would be a lot easier to do
with floating point math.
Here is a snippet of code that gets the job done. Notice that it uses PI instead of 3.14159. PI
is a built-in C language constant (a named value that does not change throughout the sketch).
Also notice that all the values have decimal points. That makes them all floating-point
values.
float r = 0.75;
float c = 2.0 * PI * r;
Create the SimpleDecisions sketch, save it, and run it on the Arduino.
Open the Serial Monitor and test to make sure you got the right message.
Try swapping the values for a and b.
Re-load the sketch and verify that it printed the other message.
Maybe your sketch needs to monitor for three conditions: greater than, less than, or equal.
Then, you could use an ifelse ifelse statement.
if(a > b)
{
Serial.print("a is greater than b");
}
else if(a < b)
{
Serial.print("a is not greater than b");
}
else
{
Serial.print("a is equal to b");
}
A sketch can also have multiple conditions with the Arduino's Boolean operators, such as &&
and ||. The && operator means AND; the || operator means OR. For example, this
statements block will execute only if a is greater than 50 AND b is less than 50:
if((a > 50) && (b < 50))
{
Serial.print("Values in normal range");
}
This example prints the warning message if a is greater than 100 OR b is less than zero.
if((a > 100) || (b < 0))
{
Serial.print("Danger Will Robinson!");
}
One last example: if you want to make a comparison to find out if two values are equal, you
have to use two equal signs next to each other: ==.
More conditions: You can chain more else if statements after the initial if.
The example in this activity only uses one else if, but you could use more.
The rest of the statement gets left behind after it finds a true condition.
If the if statement turns out to be true, its code block gets executed and the rest of the chain
of else ifs gets passed by.
A for loop is typically used to make the statements in a code block repeat a certain number
of times. For example, your BOE Shield-Bot will use five different values to make a sensor
detect distance, so it needs to repeat a certain code block five times. For this task, we use a
for loop. Here is an example that uses a for loop to count from 1 to 10 and display the
values in the Serial Monitor.
Create and save the CountToTen sketch, and run it on your Arduino.
Open the Serial Monitor and verify that it counted from one to ten.
Initialization: the starting value for counting. Its common to declare a local variable for the
job as we did here with int i = 1; naming it i for index.
Condition: what the for loop checks between each repetition to make sure the condition is
still true. If its true, the loop repeats again. If not, it allows the code to move on to the next
statement that follows the for loops code block. In this case, the condition is if i is less
than or equal to 10.
Increment: how to change the value of i for the next time through the loop. The expression
i++ is equivalent to i = i + 1. It makes a nice shorthand approach for adding 1 to a
variable. Notice that ++ comes after i, meaning use i as-is this time through the function,
and then increment it afterward. This is the post increment use of the operator.
The first time though the loop, the value of i starts at 1. So, Serial.println(i) displays
the value 1 in the Serial Monitor. The next time through the loop, i++ has made the value of
i increase by 1. After a delay (so you can watch the individual values appear in the Serial
Monitor), the for statement checks to make sure the condition i <= 10 is still true. Since i
now stores 2, it is true since 2 is less than 10, so it allows the code block to repeat again. This
keeps repeating, but when i gets to 11, it does not execute the code block because its not
true according to the i <= 10 condition.
As mentioned earlier, i++ uses the ++ increment operator to add 1 to the i variable each
time through the for loop. There are also compound operators for decrement --, and
compound arithmetic operators like +=, -=, *=, and /=. For example, the += operator can be
used to write i = i + 1000 like this: i+=1000.
Run the modified sketch and watch the output in the Serial Monitor.
In later chapters, youll use a while loop to keep repeating things while a sensor returns a
certain value. We dont have any sensors connected right now, so lets just try counting to
ten with a while loop:
int i = 0;
while(i < 10)
{
i = i + 1;
Serial.println(i);
delay(500);
}
Want to condense your code a little? You can use the increment operator (++) to increase
the value of i inside the Serial.println statement. Notice that ++ is on the left side of the
i variable in the example below. When ++ is on the left of a variable, it adds 1 to the value of
i before the println function executes. If you put ++ to the right, it would add 1 after
println executes, so the display would start at zero.
int i = 0;
while(i < 10)
{
Serial.println(++i);
delay(500);
}
The loop function, which must be in every Arduino sketch, repeats indefinitely. Another
way to make a block of statements repeat indefinitely in a loop is like this:
int i = 0;
while(true)
{
Serial.println(++i);
delay(500);
}
So why does this work? A while loop keeps repeating as long as what is in its parentheses
evaluates as true. The word 'true' is actually a pre-defined constant, so while(true) is
always true, and will keep the while loop looping. Can you guess what while(false)
would do?
Try these three different while loops in place of the for loop in the CountToTen
sketch.
Also try one instance using Serial.println(++i), and watch what happens in
the Serial Monitor.
Try while(true) and while(false) also.
/*
Robotics with the BOE Shield CountToTenDocumented
This sketch displays an up-count from 1 to 10 in the Serial Monitor
*/
const int startVal = 1;
const int endVal = 10;
const int baudRate = 9600;
void setup()
{
Serial.begin(baudRate);
}
void loop()
{
// Empty, no repeating code.
}
Documenting Code
Documenting code is the process of writing notes about what each part of the program does.
You can help make your code self-documenting by picking variable and constant names that
help make the program more self-explanatory. If you are thinking about working in a field
that involves programming, its a good habit to start now. Why?
Folks who write code for a living, like software developers and robotics
programmers, are usually under orders to document their code.
Other people might need to make updates to your code or use it for another
project, and they need to understand what the code does.
Documented code can save you lots of time trying to remember what your code
does, and how it does it, after you havent looked at it for a long time.
In addition to making your code easier to read, constants allow you to adjust an
often-used value quickly and accurately by updating a single constant
declaration. Trying to find and update each instance of an unnamed value by
hand is an easy way to create bugs in your sketch!
Now it is your turn to give it a try, and develop a new, good habit.
Read through the sketch to see how constants and comments are used.
Open up the SimpleDecisions sketch and document it, using the sketch
CountToTenDocumented as an example.
Add a title and detailed description of the sketch, enclosed in a block comment.
Use const declarations to name the values of 89 and 42.
Use the names from your const declarations instead of 89 and 42 in the setup
function.
Add line comments to the right of each line.
Chapter 1 Summary
After going to the Arduino site to install and test your software and programming
connection, this chapter guided you through several programming activities. These example
sketches showed you how to make your microcontroller do some common tasks, introduced
many programming concepts, and suggested a couple of good habits to develop your
computer skills.
Microcontroller Tasks
Programming Concepts
Computer Skills
What a baud rate is, and how to set it in your sketch and your Serial Monitor
What ASCII characters are, and what they are used for
Using your microcontrollers language reference
Why documenting code is important, and how to help make your code selfdocumenting
Chapter 1 Challenges
Questions
1.
2.
3.
4.
5.
6.
7.
8.
9.
Exercises
1.
2.
3.
4.
5.
6.
Write a piece of code that displays the value of i = followed by the value of
stored in the variable i in the Serial Monitor. Both should display on the same
line, and then move the cursor to the beginning of the next line for displaying
more messages.
Declare a long variable named bigVal, and initialize it to 80 million.
Write an ifelse statement that takes the modulus of a variable divided by 2
and compares it to zero. If the result is zero, display The variable is even. If not,
display The variable is odd.
Write a for loop that starts counting at 21 and stops at 39, and counts in steps of
3.
Write a piece of code that displays the character stored by a variable stores along
with its ASCII value.
Write a for loop, but instead of counting from one value to another, make it
count from 'A' to 'Z' and display the letters in the alphabet.
Projects
1.
2.
Write a sketch to display the printable ASCII characters. The first printable
character is the space character, which is one press/release of your keyboards
space bar between apostrophes, like this: . The last printable character is the
tilde character ~. Alternately, you could use 32 for the loops start value and
126 for the end value.
Write a sketch that tells you if a variable is odd or even. Hint: when a number is
even, the remainder of the number divided by 2 is 0. Hint: variable % 2 == 0.
Question Solutions
1.
2.
3.
4.
5.
6.
7.
8.
9.
Exercise Solutions
1.
Solution:
2.
Solution:
3.
Solution:
4.
Solution:
5.
Solution:
6.
Solution:
if(myVar % 2 == 0)
{
Serial.println("The variable is even. ");
}
else
{
Serial.println("The variable is odd. ");
}
char c = "a";
Serial.print("Character = ");
Serial.print(c);
Serial.print("
ASCII value = ");
Serial.println(c, DEC);
Project Solutions
1.
2.
continuous
rotation servo
resistor
and
LED
Parts List
(1)
(1)
(4)
(4)
(3)
(3)
(3)
Arduino module
Board of Education Shield
1 round aluminum standoffs
pan head screws, 1/4 4-40 (metal, and rounded on top with a +)
1/2 round Nylon spacers*
Nylon nuts, 4-40*
pan head screws, 7/8, 4-40*
Instructions
The four groups of pins under the Board of Education Shield plug into the four Arduino
socket headers. There are also three board-connection holes in the shield that line up with
holes in the Arduino module, designed to connect the two boards together with screws and
Nylon spacers.
If you have a revision 3 Arduino, it will be labeled UNO R3 or MEGA R3 on the back. R3
boards will have two empty pairs of sockets, closest to the USB and power connectors, after
socketing the shield. Earlier versions, such as 2, 1, and Duemilanove, have the same number
of sockets as the shield has pins, so there will be no empty sockets left over. If you have an
Arduino Mega, the four pin groups will fit into the four headers closest to the USB and power
connectors, as shown next.
Uno R3
Mega R3
Look closely at your Arduino module and the pins on the Board of Education Shield to see
how the sockets and pins will line up for your particular boards. Note that if you have an
Arduino Mega, its USB port and power jack will be close to the edge of the shield, like the
image above-right, and below-left.
Component placement varies a little bit for the different Arduino models; some can only fit
one or two Nylon standoffs for holding the boards together. This is okay, but you need to find
out which holes you can use before socketing the Board of Education Shield.
Arduinos
power and
programming
ports are away
from the BOE
Shields
breadboard
Hold a Nylon spacer over each mounting hole on your Arduino module, and look
through it to see if the spacer can line up with the hole completely. For the
mounting hole by the Arduino Uno reset button, a spacer may not fit.
Insert a 7/8 screw through the corresponding board-connection holes in your
Board of Education Shield.
Slide Nylon
spacers over
screws. A
spacer may
not fit with
the screw
that goes by
the Arduino
Unos reset
button.
After
socketing
shield, thread
and tighten
Nylon nuts
Slide a Nylon spacer over each screw you used in mounting holes where a spacer
will fit. It is okay to use the screw without the spacer where needed.
Line up the Arduino modules sockets with the Board of Education Shields pins.
Also line up the 7/8 screws with the mounting holes in the Arduino board.
Gently press the two boards together until the pins are firmly seated in their
sockets. The sockets will not cover the pins completely; there will be about 3/8
(~5 mm) of the pins still exposed between the bottom of the shield and the top of
the sockets.
Check to make ABSOLUTELY SURE your pins are seated in the sockets correctly.
It is possible to misalign the pins, which can damage your board when it is
powered.
Uno R3
To keep the connected boards up off of the table, well mount tabletop standoffs to each
corner of the Board of Education Shield.
Thread a 1/4 steel pan-head screw through a corner hole on the Board of
Education Shield from the top side, and secure it with a 1 aluminum standoff.
Repeat until all four standoffs are installed.
A resistor is a component that resists the flow of electricity. This flow of electricity is called
current. Each resistor has a value that tells how strongly it resists current flow. This
resistance value is called the ohm. The sign for the ohm is the Greek letter omega: . (Later
on you will see the symbol k, meaning kilo-ohm, which is one thousand ohms.)
This resistor has two wires (called leads and pronounced leeds), one coming out of each
end. The ceramic case between the two leads is the part that resists current flow. Most
circuit diagrams use the jagged line symbol with a number label to indicate a resistor of a
certain value, a 470 resistor in this case. This is called a schematic symbol. The part
drawing on the right is used in some beginner-level texts to help you identify the resistors in
your kit, and where to place them when you build circuits.
Gold
Silver
or
Blank
470
Yellow
Violet
Brown
The resistors in your parts kit have colored stripes that indicate what their resistance values
are. There is a different color combination for each resistance value. For example, the color
code for the 470 resistor is yellow-violet-brown.
There may be a fourth stripe that indicates the resistors tolerance. tolerance is measured in
percent, and it tells how far off the parts true resistance might be from the labeled
resistance. The fourth stripe could be gold (5%), silver (10%) or no stripe (20%). For the
activities in this book, a resistors tolerance does not matter, but its value does.
Color black brown red orange yellow green blue violet gray white
Tolerance
Code
First Digit
Number of Zeros
Second Digit
Heres how to find the resistors value, in this case proving that yellow-violet-brown is really
470 :
1.
2.
3.
Your Turn
Use the table and picture above to figure out the color code for the 220
resistors you will need for the indicator lights.
A diode is a one-way electric current valve, and a light-emitting diode (LED) emits light when
current passes through it. Since an LED is a one-way current valve, you have to make sure to
connect it the right way for it to work as intended.
An LED has two terminals: the anode and the cathode. The anode lead is labeled with the
plus-sign (+) in the part drawing, and it is the wide part of the triangle in the schematic
symbol. The cathode lead is the pin labeled with a minus-sign (-), and it is the line across the
point of the triangle in the schematic symbol.
48 Robotics with the BOE Shield-Bot
Always check the LEDs plastic case. Usually, the longer lead is connected to
the LEDs anode, and the shorter lead is connected to its cathode. But
sometimes the leads have been clipped to the same length, or a
manufacturer does not follow this convention. So, its best to always look
for the flat spot on the case. If you plug an LED in backwards, it will not hurt
it, but it wont emit light until you plug it in the right way.
When you build an LED circuit, you will have to make sure the anode
and cathode leads are connected to the circuit properly. You can tell
them apart by the shape of the LEDs plastic case. Look closely at the
caseits mostly round, but there is a small flat spot right near one of
the leads, and that tells you its the cathode. Also note that the LEDs
leads are different lengths. Usually, the shorter lead is connected to
the cathode.
LED
Need to connect
two or more
leads or wires
together? Just
plug them into
the same row of
five sockets.
The prototyping area also has black sockets along the top, bottom, and left.
Top: these sockets have three supply voltages for the breadboard: 3.3 V, Vin (input voltage
from either battery pack or programming cable), and 5 V.
Bottom-left: The first six sockets along the bottom-left are ground terminals, labeled GND;
think of them as a supply voltage thats 0 V. Collectively, the 3.3V, Vin (voltage-in), 5V and
GND are called the power terminals, and they will be used to supply your circuits with
electricity.
Bottom-right: The ANALOG IN sockets along the bottom-right are for measuring variable
voltages; these connect to the Arduino modules ANALOG IN sockets.
Left: The DIGITAL sockets on the left have labels from 0 to 13. You will use these to connect
your circuit to the Arduino modules digital input/output pins.
Digital and analog pins are the small pins on the Arduino modules Atmel microcontroller
chip. These pins electrically connect the microcontroller brain to the board.
A sketch can make the digital pins send high (5 V) or low (0 V) signals to circuits. In this
chapter, well do that to turn lights on and off. A sketch can also make a digital pin monitor
high or low signals coming from a circuit; well do that in another chapter to detect whether
a contact switch has been pressed or released.
A sketch can also measure the voltages applied to analog pins; well do that to measure light
with a phototransistor circuit in another chapter.
Build the circuit shown below. If you are new to building circuits, try to follow the
wiring diagram exactly.
Make sure your LED cathode leads are connected to GND. Remember, the cathode
leads are the shorter pins that are closer to the flat spot on the LEDs plastic case.
Each cathode lead should be plugged into the same 5-socket row as the wires that
run to the GND sockets.
Make sure that each longer anode lead is connected to the same 5-socket row as a
resistor lead.
Schematic
Wiring Diagram
Cathode
leads
Lookthis
5-socket row
connects two
wires and one
LED cathode
together.
The next picture will give you an idea of what is going on when you program the Arduino to
control the LED circuit. Imagine that you have a 5 volt (5 V) battery. The Board of Education
Shield has a device called a voltage regulator that supplies 5 volts to the sockets labeled 5V.
When you connect the anode end of the LED circuit to 5 V, its like connecting it to the
positive terminal of a 5 V battery. When you connect the circuit to GND, its like connecting
to the negative terminal of the 5 V battery.
On the left side of the picture, one LED lead is connected to 5 V and the other to GND. So, 5 V
of electrical pressure causes electrons to flow through the circuit (electric current), and that
current causes the LED to emit light. The circuit on the right side has both ends of the LED
circuit connected to GND. This makes the voltage the same (0 V) at both ends of the circuit.
No electrical pressure = no current = no light.
You can connect the LED to a digital I/O pin and program the Arduino to alternate the pins
output voltage between 5 V and GND. This will turn the LED light on/off, and thats what
well do next.
Volts is abbreviated V. When you apply voltage to a circuit, its like applying electrical pressure.
By convention, 5 V means 5 V higher than ground. Ground, often abbreviated GND, is
considered 0 V.
Ground is abbreviated GND. The term ground originated with electrical systems where this
connection is actually a metal rod that has been driven into the ground. In portable electronic
devices, ground is commonly used to refer to connections that go to the battery supplys negative
terminal.
Current refers to the rate at which electrons pass through a circuit. You will often see
measurements of current expressed in amps, which is abbreviated A. The currents you will use
here are measured in thousandths of an amp, or milliamps. For example, 10.3 mA passes through
the circuit shown previously.
Now that digital pin 13 is set to output, we can use digitalWrite to turn the LED light on
and off. Take a look at the next picture. On the left, digitalWrite(13, HIGH) makes the
Arduinos microcontroller connect digital pin 13 to 5 V, which turns on the LED. On the
right, it shows how digitalWrite(13, LOW) makes it connect pin 13 to GND (0 V) to turn
the LED off.
digitalWrite(13, HIGH);
digitalWrite(13, LOW);
Heres the loop function from the next sketch. First, digitalWrite(13, HIGH) turns the
light on, delay(500) keeps it on for a half-second. Then digitalWrite(13, LOW) turns it
off, and thats also followed by delay(500). Since its inside the loop functions block, the
statements will repeat automatically. The result? The light will flash on/off once every
second.
void loop()
{
digitalWrite(13, HIGH);
delay(500);
digitalWrite(13, LOW);
delay(500);
}
void loop()
{
digitalWrite(13, HIGH);
delay(500);
digitalWrite(13, LOW);
delay(500);
}
//
//
//
//
Your Turn Experiment with the Blink Rates and Both LEDs
How would you make the LED blink twice as fast? How about reducing the delay functions
ms parameters by half?
54 Robotics with the BOE Shield-Bot
Try modifying your sketch to use delay(250). Dont forget to change it in both
places!
How far can you reduce the delay before it just looks like the LED is dim instead
of blinking on/off? Try it!
Blinking the pin 12 LED is a simple matter of changing the pin parameter in the pinMode
and two digitalWrite function calls.
Modify the sketch so that pinMode in the setup function uses pin 12 instead of
pin 13.
Also modify both digitalWrite statements in the loop function to use pin 12.
pinMode(13, OUTPUT);
pinMode(12, OUTPUT);
HIGH);
HIGH);
LOW);
LOW);
//
//
//
//
//
//
Pin 13 = 5 V, LED
Pin 12 = 5 V, LED
..for 0.5 seconds
Pin 13 = 0 V, LED
Pin 12 = 0 V, LED
..for 0.5 seconds
emits light
emits light
no light
no light
Run the modified sketch. Do both LEDs blink on and off together?
How would you modify the sketch again to turn one LED on while the other turns off? One
circuit will need to receive a HIGH signal while the other receives a LOW signal.
Try it!
/*
void loop()
{
digitalWrite(13, HIGH);
delay(170);
digitalWrite(13, LOW);
delay(1830);
}
//
//
//
//
Pin 13 = 5
..for 0.17
Pin 13 = 0
..for 1.83
Reduce delay(170) to delay(17), and delay(1830) to delay(183), and reload the sketch.
Is the LED blinking 10 times faster now? Divide by 10 again for a full speed servo
signalwell have to round the numbers a bit:
Change delay(17) to delay(2), and delay(183) to delay(18), then run the
modified sketch.
Now you can see what the servo signal looks like with the indicator LED. The LED is
flickering so fast, its just a glow. Since the high signal is 2 ms instead of 1.7 ms, itll be a little
brighter than the actual servo control signalthe light is spending more time on. We could
use this signal and programming technique to control a servo, but theres an easier, more
precise way. Lets try it with LEDs first.
To see a list of Arduino libraries, click the Arduino softwares Help menu and select
Reference. Or, if using Codebender, go to the Arduino Library reference page:
https://www.arduino.cc/en/Reference/Libraries
writeMicroseconds()
detach()
Servos have to receive high-pulse control signals at regular intervals to keep turning. If the
signal stops, so does the servo. Once your sketch uses the Servo library to set up the signal, it
can move on to other code, like delays, checking sensors, etc. Meanwhile, the servo keeps
turning because the Servo library keeps running in the background. It regularly interrupts
the execution of other code to initiate those high pulses, doing it so quickly that its
practically unnoticeable.
Using the Servo library to send servo control signals takes four steps:
1.
Tell the Arduino editor that you want access to the Servo library functions with the
#include declaration at the start of your sketch, before the setup function.
#include <Servo.h>
2.
Declare and name an instance of the Servo library for each signal you want to send,
between #include and the setup function.
Servo servoLeft;
3.
In the setup function, use the name you gave the servo signal followed by a dot,
and then the attach function call to attach the signal pin. This example is telling
the system that the servo signal named servoLeft should be transmitted by
digital pin 13.
servoLeft.attach(13);
4.
Use the writeMicroseconds function to set the pulse time. You can do this
inside either the setup or loop function:
Create and save the LeftServoStayStill sketch, and run it on your Arduino. The
pin 13 LED should glow, about halfway between the two brightness levels you
observed earlier.
/*
Robotics with the BOE Shield LeftServoStayStill
Generate signal to make the servo stay still for centering.
*/
#include <Servo.h>
Servo servoLeft;
void setup()
{
servoLeft.attach(13);
servoLeft.writeMicroseconds(1500);
}
Your Turn Check a Second Control Signal with the Pin 12 LED
Youll be using this code a lot, so its a good idea to practice declaring an instance of Servo,
attaching the signal to a pin, and setting the pulse duration.
Servo servoRight;
servoRight.attach(12);
/*
Robotics with the BOE Shield BothServosStayStill
Generate signals to make the servos stay still for centering.
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
{
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.writeMicroseconds(1500);
servoRight.writeMicroseconds(1500);
}
void loop()
{
control
horn
Phillips
screw
power
&
signal
cable
must read
continuous
rotation
case contains
motor, circuits,
and gears
access to
adjust
feedback
potentiometer
mounting
flange
In this activity, you will connect your servos to the Board of Education Shields servo ports,
which will connect them to supply voltage, ground, and a signal pin. You will also connect a
battery supply to your Arduino because, under certain conditions, servos can end up
demanding more current than a USB supply is designed to deliver.
Servo Control Horn, 4-point Star vs. Round
It doesnt make a difference. So long as it is labeled continuous rotation its the servo for your
BOE Shield-Bot. Youll remove the control horn and replace it with a wheel.
Parts List
Instructions
Disconnect all sources of power from the Arduino including the USB cable.
Between the servo headers on the BOE Shield is a jumper that connects the servo power
supply to either Vin or 5V. To move it, pull it upwards and off the pair of pins it covers, then
push it onto the pair of pins you want it to rest on. The BOE Shield-Bots battery pack will
supply 7.5 V. Since the servos are rated for 46 V, we want to make sure the jumper is set to
5V. Also, a steady 5 V voltage supply will support a consistent servo speed, and more
accurate navigation, than voltage that varies as batteries discharge.
Make sure your BOE Shields power jumper is set to 5V; if not, set it now.
The picture below shows the schematic of the circuit you create by plugging the servos into
ports 13 and 12 on the BOE Shield. Pay careful attention to wire color as you plug in the
cables: the black wire should be at the bottom, and the white one should be at the top.
Connect your servos to your BOE Shield as shown in the diagram below. The left
servo connects to port 13 and the right servo connects to port 12.
Make sure that you follow the cable colors shown in the figure, with the black
wire closer to the breadboard and the white wire closer to the boards edge.
2.
ALWAYS unplug the battery pack when you are done experimenting for a
while. Even when the power switch on your BOE Shield is off (position-0), the
Arduino module will still draw power from the batteries.
Unplug the programming cable too, whenever you unplug the battery pack.
That way, you wont accidentally try to run the servos off of USB power.
CAUTION: AC powered DC supplies are not recommended for the BOE Shield-Bot.
Some DC supplies provide much higher voltage than their rating. The BOE Shield-bot is designed
for use with a 7.27.5 V battery supply. It will work with higher supply voltages at low loads, but
the servo loads can heat up the regulator until it shuts off to protect itself.
62 Robotics with the BOE Shield-Bot
Parts List
(5) 1.5 volt AA batteries
(1) 5-cell battery pack
Tool Required
Youll need a Phillips #1 point screwdriver with a 1/8 (3.18 mm) or smaller shaft.
Verify that the pin 13 LED signal monitor circuit is showing activity. It should
glow like it did when you ran LeftServoStayStill the first time.
Whats a Potentiometer? It is kind of like an adjustable resistor with a moving part, such as a
knob or a sliding bar, for setting the resistance. The Parallax continuous rotation servos
potentiometer is a recessed knob that can be adjusted with a small Phillips screwdriver tip
Repeat the process for the pin 12 servo using the sketch RightServoStayStill.
/*
Robotics with the BOE Shield RightServoStayStill
Transmit the center or stay still signal on pin 12 for center adjustment.
*/
#include <Servo.h>
Servo servoRight;
void setup()
{
servoRight.attach(12);
servoRight.writeMicroseconds(1500);
}
void loop()
{
}
Engineers use this essential skill to develop everything from toys, cars, and video games to space
shuttles and Mars roving robots. Especially in more complex devices, it can become nearly
impossible to figure out a problem if the individual components havent been tested beforehand.
In aerospace projects, for example, disassembling a prototype to fix a problem can cost hundreds
of thousands, or even millions, of dollars. In those kinds of projects, subsystem testing is rigorous
and thorough.
1.3 ms
Vdd (5 V)
standard servo
www.parallax.com
Vss (0 V)
20 ms
Whats RPM? Revolutions Per Minutethe number of full rotations turned in one minute.
Whats a pulse train? Just as a railroad train is a series of cars, a pulse train is a series of pulses
(brief high signals).
Create and save the LeftServoClockwise sketch, and run it on your Arduino.
Verify that the servos horn is rotating between 50 and 60 RPM clockwise.
/*
Robotics with the BOE Shield LeftServoClockwise
Generate a servo full speed clockwise signal on digital pin 13.
*/
#include <Servo.h>
Servo servoLeft;
void setup()
{
servoLeft.attach(13);
servoLeft.writeMicroseconds(1300);
}
void loop()
{
}
1.7 ms
Vdd (5 V)
standard servo
www.parallax.com
GND (0 V)
20 ms
Save and run the modified sketch and verify that the pin 12 servo is rotating
between 50 and 60 RPM clockwise.
/*
Robotics with the BOE Shield RightServoClockwise
Generate a servo full speed clockwise signal on digital pin 12.
*/
#include <Servo.h>
Servo servoRight;
void setup()
{
servoRight.attach(12);
servoRight.writeMicroseconds(1300);
}
void loop()
{
}
Save the sketch under a new name and run it on your Arduino.
Verify that the pin 12 servo turns full-speed counterclockwise, 5060 RPM.
/*
Robotics with the BOE Shield ServosOppositeDirections
Generate a servo full speed counterclockwise signal with pin 13 and
full speed clockwise signal with pin 12.
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
{
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1300);
}
void loop()
{
}
This opposite-direction control will be important soon. Think about it: when the servos are
mounted on either side of a chassis, one will have to rotate clockwise while the other rotates
counterclockwise to make the BOE Shield-Bot roll in a straight line. Does that seem odd? If
you cant picture it, try this:
Pulse Width Modulation: Adjusting the property of a signal to carry information is called
modulation. Weve discovered that servo control signals are a series of high pulses separated by
low resting states. How long the high pulse lastshow wide the high pulse looks in a timing
diagramdetermines the speed and direction that the servo turns. That adjustable pulse width
carries the servo setting information. Therefore, we can say that servos are controlled with pulse
width modulation, abbreviated PWM.
Pin 13
servoLeft
Pin 12
servoRight
Description
1700
1300
1300
1700
1700
1700
1300
1300
1500
1700
1300
1500
1500
1500
1520
1480
1540
1460
1700
1450
1550
1300
Behavior
Create and save the ServoRunTimes sketch, and run it on your Arduino.
Verify that both servos turn full speed clockwise for 3 seconds, then
counterclockwise for 3 seconds, then stop.
/*
Robotics with the BOE Shield ServoRunTimes
Generate a servo full speed counterclockwise signal with pin 13 and
full speed clockwise signal with pin 12.
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
{
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.writeMicroseconds(1300);
servoRight.writeMicroseconds(1300);
delay(3000);
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1700);
delay(3000);
servoLeft.writeMicroseconds(1500);
servoRight.writeMicroseconds(1500);
Pin 13 clockwise
Pin 12 clockwise
..for 3 seconds
Pin 13 counterclockwise
Pin 12 counterclockwise
..for 3 seconds
Pin 13 stay still
Pin 12 stay still
}
void loop()
{
}
Chapter 2 Summary
The focus of this chapter was calibrating and testing the servos, and building indicator lights
to monitor the servo signals. In addition to some hardware setup, many concepts related to
electronics, programming, and even a few good engineering concepts were introduced along
the way.
Hardware Setup
Electronics
What a resistor does, what its schematic symbol looks like, and how to read its
value by decoding the color-bands on its case
What tolerance is, in relation to a resistors stated value
What an LED does, what the schematic symbol for this one-way current valve
looks like, and how to identify its anode and cathode
What a solderless breadboard is for, and how it connects electronic devices
How to build LED indicator light circuits
The parts of a servo motor, how to connect it to the Board of Education Shield
What a potentiometer is, and how to calibrate a Parallax continuous rotation
servo by adjusting its potentiometer
What a pulse train is, and how to control a servo with pulse width modulation
Programming
How to use the Arduinos pinMode and digitalWrite functions to send high and
low output signals
How to use #include to add the Servo library to a sketch
Using the Servo librarys functions to attach and control a servo
Writing sketches to control the servos speed, direction, and run time
Engineering
Chapter 2 Challenges
Questions
1.
2.
3.
4.
5.
6.
7.
Exercises
1.
2.
3.
Write a loop function that makes an LED blink 5 times per second, with an on
time thats 1/3rd of its off time. (Move power switch to position 1 to turn off the
servos for this exercise!)
Write a setup function that makes the pin 13 servo turn full speed clockwise for
1.2 seconds, while the pin 12 servo stays still. After that, set both servos to stop.
Write a setup function that makes one servo turn the same direction for 3
seconds. The other servo should turn the opposite direction for the first 1.5
seconds and the same direction for the second 1.5 seconds. Then, make both
servos stop.
Projects
1.
2.
Look up the servo librarys detach function and use it in place of servoLeft
and servoRight.writeMicroseconds(1500) to stop servos after they turn for
3 seconds.
Write a program that makes the pin 13 servo turn counterclockwise while the pin
12 servo turns clockwise. After 3 seconds, make both servos turn
counterclockwise for 0.6 seconds. Next, make both turn clockwise for 0.6
seconds. Then, make the pin 13 servo turn clockwise and the pin 12 servo turn
counterclockwise for 3 seconds.
Question Solutions
1.
2.
3.
Plug the two leads into the same 5-socket row on the breadboard.
The pinMode function.
The digitalWrite function does both, depending on its value parameter:
4.
Assuming a pin has just been set high, the delay call can keep it high for a certain
amount of time. Then, a digitalWrite call can set it low.
(a)1.3 ms pulses for full speed clockwise, (b)1.7 ms pulses for full speed clockwise,
and (c)1.5 ms pulses for stay still.
(b) servoLeft.writeMicroseconds(1420). Full speed clockwise is
servoLeft.writeMicroseconds(1300), and stop is
servoLeft.WriteMicroseconds(1500). Since 1420 is further from stop and
closer to full speed, its the correct value for faster clockwise rotation even though it
is smaller.
Servo.writeMicroseconds(value) followed by delay(ms) followed by
Servo.writeMicroseconds(newValue) or Servo.detach(pin) will keep the
servo turning for ms milliseconds.
digitalWrite(13, HIGH) // 5 V
digitalWrite(13, LOW) // 0 V
5.
6.
7.
Exercise Solutions
1.
The total on + off time has to be 200 ms, which is 1/5th of a second. So, on for 50
ms, off for 150 ms:
void loop()
{
digitalWrite(13, HIGH);
delay(50);
digitalWrite(13, LOW);
delay(150);
}
2.
//
//
//
//
//
//
Set pin the 13 servo to full speed clockwise and the pin 12 servo to stop. Then,
delay for 1200. Since servoRight is already stopped, all the code has to do is
stop servoLeft.
void setup()
{
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.writeMicroseconds(1300);
3.
In this example, the pin 13 servo starts counterclockwise and the pin 12 servo
starts out clockwise. This goes on for 1.5 seconds. Then, the pin 12 servo is
changed to counterclockwise, and this goes on for another 1.5 seconds. After
that, both servos are stopped.
void setup()
{
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1300);
delay(1500);
servoRight.writeMicroseconds(1700);
delay(1500);
servoLeft.writeMicroseconds(1500);
servoRight.writeMicroseconds(1500);
//
//
//
//
1.7 ms ->
1.3 ms ->
..for 1.5
1.7 ms ->
cc-wise
clockwise
seconds
cc-wise
Project Solutions
1.
The detach function detaches the instance of Servo from its pin. This sketch
verifies that it stops the servo after 3 seconds of run time. The chapter examples
sent pulses telling the servo to stay still. In contrast, detach stops sending
signals to the servothe pin doesnt tell the servo to do anything, so it goes
dormant instead of holding the stop speed. The end result is the same, the
servo motor stops. The advantage to detach is that it prevents the servos from
turning slowly if the servo is not precisely calibrated.
/*
Robotics with the BOE Shield Chapter 2, Project 1
Generate a servo full speed counterclockwise signal with pin 13 and
full speed clockwise signal with pin 12.
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
{
servoLeft.attach(13);
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1300);
delay(3000);
servoLeft.detach();
servoRight.detach();
//
//
//
//
//
Pin 13 counterclockwise
Pin 12 clockwise
..for 3 seconds
Stop servo signal to pin 13
Stop servo signal to pin 12
}
void loop()
{
}
2.
Tools
All of the tools needed are common and can be found in most households and school shops.
They can also be purchased at local hardware stores. The Parallax screwdriver is included in
the Robotics Shield Kit, and the other two are optional but handy to have.
(1) screwdriver, Phillips #1
(1) 1/4 combination wrench (recommended if you
choose to use the Nylon-core lock nuts)
(1) needle-nose pliers (optional)
robot chassis
1 standoffs (removed from BOE Shield)
pan-head screws, 1/4 4-40 (removed from BOE Shield)
rubber grommet, 13/32
Instructions
Remove the 1 aluminum standoffs from the BOE Shield, and save the standoffs
and screws.
Insert the 13/32 rubber grommet into the hole in the center of the chassis.
Make sure the groove in the outer edge of the rubber grommet is seated on the
metal edge of the hole.
Use two of the 1/4 4-40 screws to attach two of the standoffs to the top front of
the chassis as shown.
Save the other two standoffs and screws for a later step.
Remove
screw and
control horn
Instructions
Use a Phillips screwdriver to remove the screws that hold the servo control horns
on the output shafts.
Pull each horn upwards and off the servo output shaft.
Save the screws; you will need them again soon!
output
shaft
Instructions
STOP! Before taking the next step, you must have completed these Chapter 2 activities: Activity 4:
Connect Servo Motors and Batteries on page 60, and Activity 5: Centering the Servos on 63.
Decide how you want to mount your servos from the two options described and pictured
below. Option 2 (right) is considered our standard small robot configuration, and our
navigation code examples assume you have set up your robot this way. Code examples may
need to be adjusted if you choose Option 1 (left).
Nuts Note: You can choose to use either hex nuts or locknuts to mount your servos, both are
provided. Locknuts provide a tighter connection, but if your robots need frequent repair or part
replacements then hex nuts are the easiest to remove and reattach quickly.
Outside-forward (left) the servos' mounting tabs seat outside the chassis, with their
potentiometer access ports facing toward the front of the chassis. This allows easy access to
adjust the potentiometers on an assembled robot, and also makes servo replacement quick.
However, this gives the BOE Shield-Bot a longer, wider wheel base, so it will be a little less
nimble on maneuvers and may need more pulses to make turns.
Inside-backward (right) the servos' mounting tabs seat inside the chassis, with their
potentiometer access ports facing towards the battery pack. This positions the axles close to
the center of the BOE Shield-Bot, for maximum agility. If you are diligent about centering
your servos before building your BOE Shield-Bot, this causes no problems.
Attach the servos to the chassis using the Phillips screws and nuts.
Use pieces of masking tape to label the servos left (L) and right (R), as shown.
Instructions
Place the empty battery pack inside the chassis positioned as shown. The easiest
method is to insert one side, then press down on the other side until it is flush
with the chassis. The fit may be snug, but it should snap into place using a small
amount of force (below).
Insert the two Nylon flat-head screws through the inside of the battery pack. Use
the smaller set of holes that line up with the chassis mounting holes for the front
standoffs.
From the top of the chassis, thread a 1" standoff on each screw and tighten.
Pull the battery packs power cord and servo lines through the rubber grommet
hole in the center of the chassis, as shown below.
Instructions
The robots tail wheel is merely a plastic ball with a hole through the center. A cotter pin
holds it to the chassis and functions as an axle for the wheel.
Line up the hole in the tail wheel with the holes in the tail portion of the chassis.
Run the cotter pin through all three holes (chassis left, tail wheel, chassis right).
Bend the ends of the cotter pin apart so that it cant slide back out of the hole.
Press each plastic wheel onto a servo output shaft, making sure the shaft lines up
with, and sinks into, the wheels recess, then secure with the saved servo screws.
Set an O-ring tire on the outer edge of each wheel, in the channel between the
teeth.
When you are done, your completed chassis will look like one of the pictures below. The left
shows the outside-forward servos, the right shows inside-backward servos.
Instructions
Set the BOE Shield on the four standoffs, lining them up with the four mounting
holes on the outer corner of the board.
Make sure the white breadboard is closer to the drive wheels, not the tail wheel.
Attach the board to the standoffs with the pan-head screws.
Reconnect the servos to the servo headers.
Using Different Pins for the Servos: The Arduino toggles Pin 13 briefly upon startup or reset.
If this causes problems for a particular application, you can use Pins 11 and 12 instead of 12
and 13. Be sure to adjust your code accordingly.
ROBOTC: If you are building the BOE Shield-Bot to use with ROBOTC instead, follow the
directions here: http://learn.parallax.com/tutorials/setting-boe-shield-bot-servos-robotc
From the underside of the chassis, pull any excess servo and battery cable through the
rubber grommet hole, and tuck the excess cable lengths between the servos and the chassis.
In this activity, you will test to make sure that the electrical connections between your board
and the servos are correct. The picture below shows your BOE Shield-Bots front, back, left,
and right. We need to make sure that the right-side servo turns when it receives pulses from
pin 12 and that the left-side servo turns when it receives pulses from pin 13.
Left
Front
Back
Right
Stop 1 second
Clockwise 3 seconds
Counterclockwise 3 seconds
Set the BOE Shield-Bot on its nose so that the drive wheels are suspended above
the ground.
Connect the programming cable and battery pack to the Arduino.
Create and save the RightServoTest sketch, and run it on your Arduino.
Set the 3-position switch to position-2 and press/release the RESET button.
Verify that the right wheel turns clockwise for three seconds, stops for one
second, then turns counterclockwise for three seconds.
If the right wheel/servo does not behave as predicted, see Servo
Troubleshooting, page 88.
If the right wheel/servo does behave properly, then keep going.
/*
* Robotics with the BOE Shield - RightServoTest
* Right servo turns clockwise three seconds, stops 1 second, then
* counterclockwise three seconds.
*/
#include <Servo.h>
Servo servoRight;
void setup()
{
servoRight.writeMicroseconds(1300);
delay(3000);
servoRight.writeMicroseconds(1500);
delay(1000);
// Stay still
// ...for 3 seconds
servoRight.writeMicroseconds(1700);
delay(3000);
servoRight.writeMicroseconds(1500);
}
void loop()
{
}
Stop 1 second
Clockwise 3 seconds
Counterclockwise 3 seconds
If the left wheel/servo does behave properly, then your BOE Shield-Bot is
functioning properly. You are ready to move on to Activity 3: Start-Reset
Indicator on page 89.
Servo Troubleshooting
Try adjusting in hardware: Go back and re-do Chapter 2, Activity 5: Centering the
Servos on page 63. If the servos are not mounted to give easy access to the
potentiometer ports, use a long-shaft screwdriver or consider re-orienting them
for re-assembly.
Try adjusting in software: If the wheel turns slowly counterclockwise, use a value
thats a little smaller than 1500. If its turning clockwise, use a value thats a little
larger than 1500. This new value will be used in place of 1500 for all
writeMicroseconds calls for that wheel as you do the experiments in this book.
Try adjusting in hardware: Go back and re-do Chapter 2, Activity 5: Centering the
Servos on page 63. If the servos are not mounted to give easy access to the
potentiometer ports, use a long-shaft screwdriver or consider re-orienting them
for re-assembly.
If the servo speed and direction never varies while trying to center it, it may be
damaged. Contact support@parallax.com for help.
When the voltage supply drops below the level a device needs to function properly, its called
brownout. The device (your Arduino) typically shuts down until the supply voltage returns
to normal. Then, it will restart whatever sketch it was running.
Brownouts typically happen when batteries are already running low, and the servos
suddenly demand more power. For example, if the BOE Shield-Bot changes from full speed
forward to full speed backward, the servos have to do extra work to stop the servos and then
go the other direction. For this, they need more current, and when they try to pull that
current from tired batteries, the output voltage dips enough to cause brownout.
Now, imagine your BOE Shield-Bot is navigating through a routine, and suddenly it stops for
a moment and then goes in a completely unexpected direction. How will you know if it is a
mistake in your code, or if its a brownout? One simple, effective solution is to add a speaker
to your BOE Shield-Bot and make it play a start tone at the beginning of every sketch. That
way, if your BOE Shield-Bot has a brownout while its navigating, youll know right away
because itll play the start tone.
Well use a device called a piezoelectric speaker (piezospeaker) that can make different tones
depending on the frequency of high/low signals it receives from the Arduino. The schematic
symbol and part drawing are shown below.
Frequency is the measurement of how often something occurs in a given amount of time.
A piezoelectric element is a crystal that changes shape slightly when voltage is applied to it.
Applying high and low voltages at a rapid rate causes the crystal to rapidly change shape. The
resulting vibration in turn vibrates the air around it, and this is what our ear detects as a tone.
Every rate of vibration makes a different tone.
Piezoelectric elements have many uses. When force is applied to a piezoelectric element, it can
create voltage. Some piezoelectric elements have a frequency at which they naturally vibrate.
These can be used to create voltages at frequencies that function as the clock oscillator for many
computers and microcontrollers.
The next picture shows a wiring diagram for adding a piezospeaker to the breadboard.
Build the circuit shown next. Position the piezospeaker just as shown on the
breadboard. The speaker should stay in place for the rest of the book, while other
circuits are added or removed around it.
There are two options for calling the tone function. One allows you to specify the pin and
frequency (pitch) of the tone. The other allows you to specify pin, frequency, and
duration (in milliseconds). Well be using the second option since we dont need the tone
to go on indefinitely.
tone(pin, frequency)
tone(pin, frequency, duration)
This piezospeaker is designed to play 4.5 kHz tones for smoke alarms, but it can also play a
variety of audible tones and usually sounds best in the 1 kHz to 3.5 kHz range. The startalert tone well use is:
tone(4, 3000, 1000);
delay(1000);
That will make pin 4 send a series of high/low signals repeating at 3 kHz (3000 times per
second). The tone will last for 1000 ms, which is 1 second. The tone function continues in
the background while the sketch moves on to the next command. We dont want the servos
to start moving until the tone is done playing, so the tone command is followed by
delay(1000) to let the tone finish before the sketch can move on to servo control.
Frequency can be measured in hertz (Hz) which is the number of times a signal repeats itself in
one second. The human ear is able to detect frequencies in a range from very low pitch (20 Hz) to
very high pitch (20 kHz or 20,000 Hz). One kilohertz (kHz) is one-thousand-times-per-second.
Verify that, after each reset, the piezospeaker makes a clearly audible tone for
one second, and then the Waiting for reset messages resumes.
Try disconnecting and reconnecting your battery supply and programming cable,
and then plugging them back in. This should also trigger the start-alert tone.
/*
* Robotics with the BOE Shield - StartResetIndicator
* Test the piezospeaker circuit.
*/
void setup()
{
Serial.begin(9600);
Serial.println("Beep!");
tone(4, 3000, 1000);
delay(1000);
}
void loop()
{
Serial.println("Waiting for reset...");
delay(1000);
}
When the tone is done, the sketch enters the loop function, which displays the same
Waiting for reset message over and over again. Each time the reset button on the BOE
Shield is pressed or the power is disconnected and reconnected, the sketch starts over again
with the Beep! message and the 3 kHz tone.
We'll use tone at the beginning of every example sketch from here onward. So, its a good
idea to get in the habit of putting tone and delay statements at the beginning of every
sketchs setup function.
Copy the tone and delay function calls from the StartResetIndicator sketch into
the beginning of the RightServoTest sketchs setup function.
Run the modified sketch and verify that it responds with the sketch starting
tone every time the Arduino is reset.
Rotational
Velocity,
RPM
0
-20
-40
-60
1300
1350
1400
1450
1500
1550
1600
1650
1700
Pulse Width, s
Right Servo
You can get a good idea of what to expect from your servo for a certain pulse width.
Follow the vertical line up from 1500 to where the graph crosses it, then follow the
horizontal line over and youll see that there is zero rotation for 1500 s pulses. We
already knew servoLeft.writeMicroseconds(1500) stops a servo, but try some
other values.
Compare servo speeds for 1300 and 1350 s pulses. Does it really make
any difference?
o What speed would you expect from your servos with 1550 s pulses?
How about 1525 s pulses?
Speed doesnt change much between 1300 and 1400 s pulses. So, 1300 s pulses
for full speed clockwise is overkill; the same applies to 1600 vs. 1700 s pulses for
counterclockwise rotation. These overkill speed settings are useful because they are
o
2.
3.
more likely to result in closely matched speeds than picking two values in the 1400
to 1600 s range.
Between 1400 and 1600 s, speed control is more or less linear. In this range, a
certain change in pulse width will result in a corresponding change in speed. Use
pulses in this range to control your servo speeds.
With this sketch, you can check servo RPM speed (and direction) for pulse values from
1375 s to 1625 s in steps of 25 s. These speed measurements will help make it clear
how servo control pulse durations in the 1400 to 1600 s range control servo speed. This
sketch starts by displaying the pulse duration that its ready to send as a servo control
signal. Then, it waits for you to send the Arduino a character with the Serial Monitor
before it runs the servo. It runs the servo for six seconds, and during that time you can
count the number of full turns the wheel makes. After that, the for loop repeats itself,
and increases the pulse duration by 25 for the next test.
Arduino IDE
Click Send
Type one
character
Codebender
Type one
character
Click
Send
Place a mark (like a piece of masking tape) on the wheel so that you can see how
many revolutions it turns during the wheel speed tests.
Set the BOE Shield-Bot on its nose so that the wheels can spin freely.
Create and save the TestServoSpeed sketch, and run it on the Arduino.
Open the Serial Monitor. Set the menus to 9600 baud and no line ending."
Click the transmit pane at the top, type any character, and click the Send button.
Count the number of turns the wheel makes, and multiply by 10 for RPMs. (Dont
forget to make a note of direction; it will change after the 5th test.)
If you were to add your data points to the graph, would they fit the overall shape?
/*
Robotics with the BOE Shield TestServoSpeed
Send a character from the Serial Monitor to the Arduino to make the
left servo run for 6 seconds. Starts with 1375 us pulses and increases by
25 us with each repetition, up to 1625 us. This sketch is useful for
graphing speed vs. pulse width.
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
Serial.begin(9600);
servoLeft.attach(13);
}
void loop()
// Main loop auto-repeats
{
// Loop counts with pulseWidth from 1375 to 1625 in increments of 25.
for(int pulseWidth = 1375; pulseWidth <= 1625; pulseWidth += 25)
{
Serial.print("pulseWidth = ");
// Display pulseWidth value
Serial.println(pulseWidth);
Serial.println("Press a key and click"); // User prompt
Serial.println("Send to start servo...");
while(Serial.available() == 0);
Serial.read();
Serial.println("Running...");
servoLeft.writeMicroseconds(pulseWidth); // Pin13 servo speed = pulse
delay(6000);
// ..for 6 seconds
servoLeft.writeMicroseconds(1500);
// Pin 13 servo speed = stop
}
}
With each repetition of the for loop, it displays the value of the next pulse width that it will
send to the pin 13 servo, along with a user prompt.
Serial.print("pulseWidth = ");
// Display pulseWidth value
Serial.println(pulseWidth);
Serial.println("Press a key and click"); // User prompt
Serial.println("Send to start servo...");
After Serial.begin in the setup loop, the Arduino sets aside some memory for characters
coming in from the Serial Monitor. This memory is typically called a serial buffer, and thats
where ASCII values from the Serial Monitor are stored. Each time you use Serial.read to
get a character from the buffer, the Arduino subtracts 1 from the number of characters
waiting in the buffer.
A call to Serial.available will tell you how many characters are in the buffer. This sketch
uses while(Serial.available() = = 0) to wait until the Serial Monitor sends a
character. Before moving on to run the servos, it uses Serial.read( ) to remove the
character from the buffer. The sketch could have used int myVar = Serial.read( ) to
copy the character to a variable. Since the code isnt using the characters value to make
decisions, it just calls Serial.read, but doesnt copy the result anywhere. The important
part is that it needs to clear the buffer so that Serial.available( ) returns zero next time
through the loop.
while(Serial.available() == 0);
Serial.read();
Where is the while loops code block? The C language allows the while loop to use an empty
code block, in this case to wait there until it receives a character. When you type a character into
the Serial Monitor, Serial.available returns 1 instead of 0, so the while loop lets the sketch
move on to the next statement. Serial.read removes that character you typed from the
Arduinos serial buffer to make sure that Serial.available returns 0 next time through the
loop. You could have typed this empty while loop other ways:
while(Serial.available() == 0) {}
...or:
while(Serial.available() == 0) {};
96 Robotics with the BOE Shield-Bot
After the Arduino receives a character from the keyboard, it displays the Running
message and then makes the servo turn for 6 seconds. Remember that the for loop this
code is in starts the pulseWidth variable at 1375 and adds 25 to it with each repetition. So,
the first time through the loop, servoLeft is 1375, the second time through its 1400, and so
on all the way up to 1625.
Each time through the loop, servoLeft.writeMicroseconds(pulseWidth) uses the value
that pulseWidth stores to set servo speed. Thats how it updates the servos speed each
time you send a character to the Arduino with the Serial Monitor.
Serial.println("Running...");
servoLeft.writeMicroseconds(pulseWidth); // Pin 13 speed=pulse
delay(6000);
// ..for 6 seconds
servoLeft.writeMicroseconds(1500);
// Pin 13 speed=stop
to:
Load the modified sketch into the Arduino and use it to fill in every other table
entry. (If you want to fill in every table entry, use pulseWidth += 10 in the
for statements increment parameter.)
Use graphing software of your choice to plot the pulse width vs. wheel RPM.
To repeat these measurements for the right wheel, replace all instances of 13
with 12 in the sketch.
Rotational
Velocity
(RPM)
Pulse
Width
(s)
Rotational
Velocity
(RPM)
Pulse
Width
(s)
Rotational
Velocity
(RPM)
Pulse
Width
(s)
1300
1400
1500
1600
1310
1410
1510
1610
1320
1420
1520
1620
1330
1430
1530
1630
1340
1440
1540
1640
1350
1450
1550
1650
1360
1460
1560
1660
1370
1470
1570
1670
1380
1480
1580
1680
1390
1490
1590
1690
1700
Rotational
Velocity
(RPM)
Chapter 3 Summary
This chapter covered BOE Shield-Bot assembly and testing. Assembly involved both
mechanical construction and circuit-building, and testing used some new programming
concepts. Here are the highlights:
Hardware Setup
Electronics
What a piezoelectric speaker is, and how to add one to the BOE Shield-Bots
breadboard circuits
What frequency is, the units for measuring it, and what frequency range is
audible to human hearing
What a low-battery brownout condition is, and how certain servo maneuvers can
cause it
Programming
Engineering Skills
Chapter 3 Challenges
Questions
1.
2.
3.
4.
5.
1.
Write a statement that makes a tone, one that sounds different from the startalert tone, to signify the end of a sketch.
Write a statement that plays a speaker tone to signify an intermediate step in the
sketch. This tone should be different from a start-alert or end tone.
Exercises
2.
Projects
1.
2.
Modify the TestServoSpeed sketch so that it makes a tone signifying each test is
complete.
Modify the TestServoSpeed sketch so that it runs both wheels instead of just one
with each test. Make the right wheel turn the opposite direction from the left
wheel.
Question Solutions
1.
2.
3.
4.
5.
Exercise Solutions
1.
2.
Project Solutions
1.
/*
Robotics with the BOE Shield Chapter 3, Project 1
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
Serial.begin(9600);
servoLeft.attach(13);
}
void loop()
{
Serial.println("Running...");
servoLeft.writeMicroseconds(pulseWidth);
delay(6000);
servoLeft.writeMicroseconds(1500);
tone(4, 4000, 75);
//
//
//
//
}
}
2.
/*
Robotics with the BOE Shield Chapter 3, Project 2
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
Serial.begin(9600);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
Serial.println("Running...");
servoLeft.writeMicroseconds(pulseWidth); // Pin13 servo speed = pulse
// Pin 12 servo opposite direction of pin 13 servo.
servoRight.writeMicroseconds(1500 + (1500 - pulseWidth));
delay(6000);
// ..for 6 seconds
servoLeft.writeMicroseconds(1500);
// Pin 13 servo speed = stop
servoRight.writeMicroseconds(1500);
// Pin 12 servo speed = stop
tone(4, 4000, 75);
// Test complete
}
}
Forward
Backward
Right turn
Perform basic maneuvers: forward, backward, rotate left, rotate right, pivoting
turns to the left and right, and stop.
Tune the maneuvers from Activity #1 so that they are more precise.
Use math to calculate servo run times to make the BOE Shield-Bot travel a
predetermined distance.
Write sketches that make the BOE Shield-Bot gradually accelerate into and
decelerate out of maneuvers.
Write functions to perform the basic maneuvers, and incorporate function calls
into the sketches wherever theyre needed.
Design a function for quickly defining custom maneuvers.
Store complex maneuvers in arrays and write sketches that play back these
maneuvers.
Left side
Right side
Clockwise forward
Counterclockwise forward
Remember that a sketch can use the Servo librarys 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.
Heres an example that will make the BOE Shield-Bot roll forward for about three seconds,
and then stop.
Make sure the BOE Shields power switch is set to 1 and the battery pack is
plugged into the Arduino.
Create and save the ForwardThreeSeconds sketch, and run it on the Arduino.
Disconnect the programing cable and put the BOE Shield-Bot on the floor.
While holding down the Reset button, move the switch to position 3, and then let go. The
BOE Shield-Bot should drive forward for three seconds.
// Robotics with the BOE Shield - ForwardThreeSeconds
// Make the BOE Shield-Bot roll forward for three seconds, then stop.
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.detach();
servoRight.detach();
}
void loop()
{
}
Next, an instance of Servo must be declared and uniquely named for each wheel:
Servo servoLeft;
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 objects 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 thats okay.
Robotics with the BOE Shield-Bot 105
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()
{
tone(4, 3000, 1000);
delay(1000);
// Built-in initialization
// Play tone for 1 second
// 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, servoRight.writeMicroseconds calls
will affect the signals sent on pin 12.
servoLeft.attach(13);
servoRight.attach(12);
Remember that we need the BOE Shield-Bots 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();
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()
{
}
Change delay(3000) to delay(1500) and re-load the sketch. Did the BOE
Shield-Bot travel only half 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);
servoRight.writeMicroseconds(1700);
These two calls will make your BOE Shield-Bot rotate in place to make a left turn:
// Turn left in place
servoLeft.writeMicroseconds(1300);
servoRight.writeMicroseconds(1300);
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.writeMicroseconds(1700); // Right wheel counterclockwise
Lets combine all these commands into a single sketch that makes the BOE Shield-Bot move
forward, turn left, turn right, then move backward.
Servo servoLeft;
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.detach();
servoRight.detach();
}
void loop()
{
}
QUICK TIP To enter this sketch quickly, copy and paste to make four copies of the four lines
that make up a maneuver. Then, modify each one with individual values.
// Pivot forward-right
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1500);
// Pivot backward-left
servoLeft.writeMicroseconds(1500);
servoRight.writeMicroseconds(1700);
// Pivot backward-right
servoLeft.writeMicroseconds(1300);
servoRight.writeMicroseconds(1500);
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
its supposed to be traveling straight ahead? Theres 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.
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.detach();
servoRight.detach();
}
void loop()
{
}
Your Turn Adjusting Servo Speed to Straighten the BOE Shield-Bots Path
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 that you have best speed control over the servos in the 1400 to 1600 s range.
The us parameters in writeMicroseconds(us)
Top speed
clockwise
Linear speed
zone starts
Full stop
Linear speed
zone ends
Top speed
counterclockwise
1300
1400
1500
1600
1700
Lets 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 robots 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. Youre 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.
If your BOE Shield-Bot already travels straight forward, try the modifications just
discussed anyway, to see the effect. It should cause the BOE Shield-Bot to travel
in a curve instead of a straight line.
You might find that theres 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 functions ms parameter to make it
turn for a different amount of time.
Lets 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 doesnt turn far enough, make it run longer
by increasing the delay functions 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 calls 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, dont
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.
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 cars
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:
time =
distance
speed
U.S. customary units example: If youre 140 miles away from your destination, and youre
traveling 70 miles per hour, its going to take 2 hours to get there.
140 miles
70 miles/hour
1 hour
= 140 miles
70 miles
= 2 hours
time =
Metric units example: If youre 200 kilometers away from your destination, and youre
traveling 100 kilometers per hour, its going to take 2 hours to get there.
200 kilometers
100 kilometers/hour
1 hour
= 200 km
100 km
= 2 hours
time =
You can do the same exercise with the BOE Shield-Bot, except you have control over how far
away the destination is. Heres the equation you will use:
robot distance
robot speed
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1300);
delay(1000);
servoLeft.detach();
servoRight.detach();
}
void loop()
{
}
The distance you just recorded is your BOE Shield-Bots 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 thats 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, youd use delay(2220) after
your writeMicroseconds calls.
114 Robotics with the BOE Shield-Bot
U.S. customary units example: At 9 in/s, your BOE Shield-Bot has to travel for 2.22 s to
travel 20 in.
time =
20 in
9 in/s
= 20 in
1s
9 in
= 2.22 s
Metric units example: At 23 cm/s, your BOE Shield-Bot has to travel for 2.22 s to travel 51
cm.
time =
51 cm
23 cm/s
= 51 cm
1s
23 cm
= 2.22 s
Both examples above resolve to the same answer:
1000 ms
s
1000 ms
= 2.22s
s
= 2220 ms
time(ms ) = time( s )
robot speed
s
Encoders: Increase the accuracy of your BOE Shield-Bot distances with devices called encoders
which count the holes in the BOE Shield-Bots wheels as they pass.
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 loops increment parameter. Since the speed variable is in each
writeMicroseconds calls 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.
for(int speed = 0; speed <= 100; speed+=2)
{
servoLeft.writeMicroseconds(1500+speed);
servoRight.writeMicroseconds(1500-speed);
delay(20);
}
Lets take a closer look at the trips through the for loop from this diagram:
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
// us = 1500,1502,... 1600
// us = 1500,1498,... 1400
// 20 ms at each speed
}
void loop()
{
}
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 1500speed for the other,
now they are both 1500+speed. For left-turn ramping, they would both be 1500speed.
for(int speed = 0; speed <= 100; speed += 2)
{
servoLeft.writeMicroseconds(1500+speed);
servoRight.writeMicroseconds(1500+speed);
delay(20);
}
for(int speed = 100; speed >= 0; speed -= 2)// right turn to stop
{
servoLeft.writeMicroseconds(1500+speed);
// us = 1600...1500
servoRight.writeMicroseconds(1500+speed); // us = 1600...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
118 Robotics with the BOE Shield-Bot
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 doesnt need any parameters to do its job,
and void indicates that it does not return a value (well look at functions that return values
in a later chapter). The curly braces {} that follow this definition contain the example
functions block of code.
void setup() {
Serial.begin(9600);
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:
1.
2.
3.
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 functions 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 functions code block. The second call, pitch(2000), initializes Hz to 2000 during
the sketchs second trip through the pitch functions code block.
void setup() {
Serial.begin(9600);
void loop()
{
(6) Function
}
executes with
Hz = 2000
Read the sketch, and predict what you will see and hear when you run it.
Create, save, and run FunctionCallWithParameter, then open the Serial Monitor.
Watch your terminal and listen to the tones.
delay(1000);
Serial.println("Playing lower pitch tone...");
pitch(2000);
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);
}
Was your prediction correct? If so, great! If not, take a closer look at the sketch and make
sure you can follow the code from each function call to the function and back. For
clarification, take another look at the previous diagram.
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:
122 Robotics with the BOE Shield-Bot
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 functions definition, or the sketch wont
compile.
Replace the single parameter pitch calls with the two-parameter calls.
Lets try putting the forward, turnLeft, turnRight, and backward navigation routines
inside functions. Heres an example:
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
forward(2000);
turnLeft(600);
turnRight(600);
backward(2000);
//
//
//
//
disableServos();
}
void loop()
{
// Forward function
// Left wheel counterclockwise
// Right wheel clockwise
// Maneuver for time ms
// Backward function
void disableServos()
{
servoLeft.detach();
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:
Remove the // Empty comment from the loop functionit wont be correct!
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
// disableServos();
}
void loop()
{
forward(2000);
turnLeft(600);
turnRight(600);
backward(2000);
Run the modified sketch and verify that it repeats the sequence of four
maneuvers indefinitely.
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 are easy to remember. With this maneuver
function you dont have to think about clockwise and counterclockwise rotation anymore.
Here is what calls to this function will look like for the familiar forward-backward-left-rightstop sequence:
maneuver(200, 200, 2000);
maneuver(-200, 200, 600);
maneuver(200, -200, 600);
maneuver(-200, -200, 2000);
maneuver(0, 0, -1);
//
//
//
//
//
Forward 2 seconds
Left 0.6 seconds
Right 0.6 seconds
Backward 2 seconds
Disable servos
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
//
//
//
//
//
Forward 2 seconds
Left 0.6 seconds
Right 0.6 seconds
Backward 2 seconds
Disable servos
}
void loop()
{
}
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, lets 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);
Try adding the other three wheel-pivot turns to the sequence: forward-right,
backward-right, and backward-left.
Some robotics applications require sequences of maneuvers. Youll 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 corridors
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.
Whats an Array?
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 arrays 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.
note[0]
note[1]
note[2]
note[3]
note[6]
note[7]
int note[] = {1047, 1174, 1319, 1397, 1568, 1760, 1976, 2093};
note[4]
note[5]
Lets 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 its electric) vibrate at a certain
frequency.
Compare the frequencies of the leftmost eight white keys to the values in the note array.
Since the values in the array are musical notes, we might as well play this note on the BOE
Shield-Bots piezospeaker! Here's how:
tone(4, note[3], 500);
Create, save, and run the PlayOneNote sketch, and run it on the Arduino.
Open the Serial Monitor as soon as the sketch is done loading.
Verify that the Serial Monitor displays note = 1397.
Verify that the speaker played a tone.
Modify the sketch to play and print the value of 1568 using note[4].
Test your modified sketch.
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.
Create, save, and run PlayNotesWithLoop, then open the Serial Monitor as soon
as the sketch is done loading.
Verify that the Serial Monitor displays each note in the array as the speaker plays
it.
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()
{
}
What do you think will happen if you change the for loop to match the one
below? Try it!
int note[] = {1047, 1147, 1319, 1397, 1568, 1760, 1976, 2093};
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++)
Try adding the next two notes, D7 and E7, using the frequencies (rounded down)
from the keyboard diagram. Your array can use more than one line, like this:
If you are musically inclined, try writing an array that will play a very short tune.
Forward
0
= {200,
= {200,
= {2000,
left
1
-200,
200,
600,
right
2
200,
-200,
600,
backward
3
-200,
-200,
2000,
stop
4
0};
0};
-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.
Robotics with the BOE Shield-Bot 133
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 calls 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).
left
1
-200,
200,
600,
right
2
200,
-200,
600,
backward
3
-200,
-200,
2000,
stop
4
0};
0};
-1};
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
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]);
}
134 Robotics with the BOE Shield-Bot
Heres an example of a longer list you can try. It does the four pivots after the forward-leftright-backward sequence. In this example, when index is 4, itll use the first number of the
second line of each array. When index is 5, itll 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 curly braces { }, and it doesnt matter whether that list is all on one line or spanning
multiple lines.
int speedsLeft[]
= {200,
0,
int speedsRight[] = {200,
200,
int times[]
= {2000,
1000,
-200,
200,
200,
0,
600,
1000,
200,
-200,
-200,
0,
600,
1000,
-200,
0,
-200,
-200,
2000,
1000,
0};
0};
-1};
b = backward
l = left
r = right
s = stop
Character arrays 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-rightbackward-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.
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
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
}
Try this arraycan you guess what it will make the BOE Shield-Bot do?
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 looprecall 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, lets 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
executedthe 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)
{
switch(c)
{
case 'f':
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1300);
break;
case 'b':
servoLeft.writeMicroseconds(1300);
servoRight.writeMicroseconds(1700);
break;
case 'l':
servoLeft.writeMicroseconds(1300);
servoRight.writeMicroseconds(1300);
break;
case 'r':
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1700);
break;
case 's':
servoLeft.writeMicroseconds(1500);
servoRight.writeMicroseconds(1500);
break;
}
delay(200);
// go function
// Switch to based on c
// c contains 'f'
// Full speed forward
// c contains 'b'
// Full speed backward
// c contains 'l'
// Rotate left in place
// c contains 'r'
// Rotate right in place
// c contains 's'
// Stop
// c contains 'h'
// Half speed forward
Chapter 4 Summary
This chapter was all about robot navigation, experimenting with many different
programming approaches and employing some robotics and engineering skills:
Programming
Robotics Skills
Controlling robot maneuver run-time to make the BOE Shield-Bot travel a predetermined distance or to rotate to a particular angle
Compensating for hardware variance by adjusting servo speeds for straight travel
Engineering Skills
Chapter 4 Challenges
Questions
1.
2.
3.
4.
5.
6.
7.
8.
9.
What direction does the left wheel have to turn to make the BOE Shield-Bot go
forward? What direction does the right wheel have to turn?
When the BOE Shield-Bot pivots on one wheel to the left, what are the wheels
doing? What code do you need to make the BOE Shield-Bot pivot left?
If your BOE Shield-Bot veers slightly to one side when you are running a sketch to
make it go straight ahead, how do you correct this? What command needs to be
adjusted and what kind of adjustment should you make?
If your BOE Shield-Bot travels 11 in/s, how many milliseconds will it take to
make it travel 36 inches?
Why does a for loop that ramps servo speed need delay(20) in it?
What kind of variable is great for storing multiple values in lists?
What kind of loops can you use for retrieving values from lists?
What statement can you use to select a particular variable and evaluate it on a
case-by-case basis and execute a different code block for each case?
What condition can you append to a do-loop?
Exercises
1.
2.
3.
Write a routine that makes the BOE Shield-Bot back up at full speed for 2.5
seconds.
Lets say that you tested your servos and discovered that it takes 1.2 seconds to
make a 180 turn with right-rotate. With this information, write routines to
make the BOE Shield-Bot perform 30, 45, and 60 degree turns.
Write a routine that makes the BOE Shield-Bot go straight forward, then ramp
into and out of a pivoting turn, and then continue straight forward.
Projects
1.
2.
Question Solutions
1.
2.
3.
Slow down the right wheel to correct a veer to the left, and slow down the left
wheel to correct a veer to the right. Slow down a wheel by changing its servos
writeMicroseconds us parameter, using values closer to 1500. Start at the
appropriate end of the linear speed control range (14001600), gradually move
towards 1500 in increments of 10, and go back in smaller increments if you
overshoot.
servoLeft.writeMicroseconds(1500);
servoRight.writeMicroseconds(1300);
4.
Given the data below, it should take about 3727 milliseconds to travel 36 inches:
Exercise Solutions
1.
Solution:
2.
Solution:
servoLeft.writeMicroseconds(1300);
servoRight.writeMicroseconds(1700);
delay(2500);
3.
Solution:
// 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);
Project Solutions
1.
Solution (though the table looks a little different than the one you may have
printed out.)
Servo ports
connected to:
2.
Description
Behavior
Pin 13
Pin 12
1700
1300
Forward
1300
1700
Backward
1700
1700
Right rotate
1300
1300
Left rotate
1500
1700
1300
750
1500
1500
Stopped
1520
1480
Forward slow
1540
1460
Forward medium
1700
1450
Veer right
1550
1300
Veer left
The circle can be implemented by veering right continuously. Trial and error,
and a yard or meter stick, will help you arrive at the right us parameters for
writeMicroseconds(us) and the right ms parameter for delay(ms). Below is a
solution that worked for a particular pair of servos and set of batteries. Your
values may vary considerably from whats in the Circle sketch.
Servo servoLeft;
Servo servoRight;
void setup()
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
}
For the triangle, first calculate the required travel time in ms for a 1 meter or 1 yard
straight line, as in Question 4, and fine-tune for your BOE Shield-Bot and particular
surface. The BOE Shield-Bot must travel 1 meter/yard forward, and then make a
120 turn, repeated three times for the three sides of the triangle. You may have to
adjust the delay call in the Turn Left 120 routine to get a precise 120 turn.
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
}
Tactile Navigation
Whisker switches give the BOE Shield-Bot the ability to sense its surroundings through
touch as it roams around, much like a cats whiskers. The activities in this chapter use the
whiskers by themselves, but they can also be combined with other sensors you will learn
about in later chapters.
Parts List
Remove the LED circuits that were used as signal monitors while testing the
servo navigation.
Remove the two front screws that hold your board to the front standoffs.
Thread a Nylon washer and then a round spacer on each of the 7/8 screws.
Attach the screws through the holes in your board and into the standoffs below,
but do not tighten them all the way yet.
Slip the hooked ends of the whisker wires around the screws, one above a washer
and the other below a washer, positioning them so they cross over each other
without touching.
Tighten the screws into the standoffs.
Use the 220 resistors (red-red-brown) to connect digital pins 5 and 7 to their
corresponding 3-pin headers.
Use the 10 k resistors (brown-black-orange) to connect 5 V to each 3-pin
header.
Make sure to adjust each whisker so that it is close to, but not touching, the 3-pin
header on the breadboard. A distance of about 1/8 (3 mm) is about right.
Take a look at the figure below. On the left, the circuit applies 5 V when the whisker is not
pressed, so digitalRead(7) returns 1 (HIGH). On the right, the circuit applies 0 V when the
whisker is pressed, so digitalRead(7) returns 0 (LOW).
Most importantly, your sketch can store the return values in variables, such as wLeft and
wRight, and then use them to trigger actions or make decisions. The next example sketch
will demonstrate how.
Whisker Circuit
Applies 5 V
digitalRead(7)
returns 1
Whisker Circuit
Applies 0 V
digitalRead(7)
returns 0
Switch Lingo: Each whisker is both the mechanical extension and the ground electrical connection
of a normally open (off until pressed) momentary (on only while pressed) single-pole (one set of
electrical contact points), single-throw (only one position conducts) switch.
When neither whisker is pressed up against its 3-pin header, you can expect your Serial
Monitor to display two columns of 1s, one for each whisker. If you press just the right
whisker, the right column should report 0, and the display should read 10. If you press just
the left whisker, the left column should report 1 and the display should read 01. Of course, if
you press both whiskers, it should display 00.
No whiskers pressed
Active-low Output : The whisker circuits are wired for active-low output, which means that they
each send a low signal when they are pressed (active) and a high signal when they are not
pressed. Since digitalRead returns 0 for a low signal and 1 for a high signal, 0 is what tells your
sketch that a whisker is pressed, and 1 tells it that a whisker is not pressed.
/*
* Robotics with the BOE Shield - DisplayWhiskerStates
* Display left and right whisker states in Serial Monitor.
* 1 indicates no contact; 0 indicates contact.
*/
void setup()
{
tone(4, 3000, 1000);
delay(1000);
pinMode(7, INPUT);
pinMode(5, INPUT);
152 Robotics with the BOE Shield-Bot
Serial.begin(9600);
}
void loop()
{
byte wLeft = digitalRead(5);
byte wRight = digitalRead(7);
Serial.print(wLeft);
Serial.println(wRight);
delay(50);
// Pause for 50 ms
Look at the values displayed in the Serial Monitor. With no whiskers pressed, it
should display 11, indicating 5 V is applied to both digital inputs (5 and 7).
Press the right whisker into its three-pin header, and note the values displayed in
the Serial Monitor. It should now read 10.
Release the right whisker and press the left whisker into its three-pin header, and
note the value displayed in the Serial Monitor again. This time it should read 01.
Press both whiskers against both three-pin headers. Now it should read 00.
If the whiskers passed all these tests, youre ready to move on. If not, check your
sketch and circuits for errors.
These steps are important! Seriously, youve got to make sure your circuit and code pass these
tests before continuing. The rest of the examples in this chapter rely on the whiskers working
correctly. If you havent tested and corrected any errors, the rest of the examples wont work.
In the loop function, each call to digitalRead returns a 0 if the whisker is pressed or 1 if it
is not. Those values get copied to variables named wLeft and wRight, which are short for
whisker-left and whisker-right.
byte wLeft = digitalRead(5);
byte wRight = digitalRead(7);
Next, Serial.print displays the value of wLeft to the Serial Monitor, and Serial.println
displays the value of wRight and a carriage return.
Serial.print(wLeft);
Serial.println(wRight);
Before the next repetition of the loop function, theres a delay(50). This slows down the
number of messages the Serial Monitor receives each second. Although its probably not
needed, we leave it in to prevent possible computer buffer overruns (too much data to store)
for older hardware and certain operating systems.
Your sketch doesnt actually need to use variables to store the values from digitalRead.
Instead, the (1 or 0) value that digitalRead returns can be used directly by nesting the
function call inside Serial.print and sending its return value straight to the Serial
Monitor. In that case, your loop function would look like this:
void loop()
{
Serial.print(digitalRead(5));
Serial.println(digitalRead(7));
delay(50);
// Display wLeft
// Display wRight
// Pause for 50 ms
Replace the loop function with the one above, load the sketch, and test the
whiskers to verify that it functions the same.
What if you have to test the whiskers at some later time away from a computer? In that case,
the Serial Monitor wont be available, so what can you do? One solution would be to use LED
circuits to display the whisker states. All it takes is a simple sketch that turns an LED on
when a whisker is pressed or off when its not pressed.
Parts List:
Flat spot
on plastic
case
indicates
cathode.
This
lead is
the
anode.
pinMode(8, OUTPUT);
To make the whisker input states control the LEDs, insert these two if...else statements
between the Serial.println(wRight) and delay(50) commands:
if(wLeft == 0)
{
digitalWrite(8, HIGH);
}
else
{
digitalWrite(8, LOW);
}
if(wRight == 0)
{
digitalWrite(2, HIGH);
}
else
{
digitalWrite(2, LOW);
}
// Left LED on
// If no left whisker contact
// Left LED off
// Right LED on
// If no right whisker contact
// Right LED off
Recall that if...else statements execute blocks of code based on conditions. Here, if
wLeft stores a zero, it executes the digitalWrite(8, HIGH) call. If wLeft instead stores a
1, it executes the digitalWrite(8, LOW) call. The result? The left LED turns on when the
left whisker is pressed or off when its not pressed. The second ifelse statement does the
same job with wRight and the right LED circuit.
/*
* Robotics with the BOE Shield - TestWhiskersWithLeds
* Display left and right whisker states in Serial Monitor.
* 1 indicates no contact; 0 indicates contact.
* Display whisker states with LEDs. LED on indicates contact;
* off indicates none.
*/
//
//
//
//
Serial.begin(9600);
}
void loop()
{
byte wLeft = digitalRead(5);
byte wRight = digitalRead(7);
if(wLeft == 0)
{
digitalWrite(8, HIGH);
}
else
{
digitalWrite(8, LOW);
}
if(wRight == 0)
{
digitalWrite(2, HIGH);
}
else
{
digitalWrite(2, LOW);
}
Serial.print(wLeft);
Serial.println(wRight);
delay(50);
// Display wLeft
// Display wRight
// Pause for 50 ms
// Right LED on
// If no right whisker contact
// Right LED off
Lets try the sketch first, and then take a closer look at how it works.
Servo servoLeft;
Servo servoRight;
void setup()
{
pinMode(7, INPUT);
pinMode(5, INPUT);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
byte wLeft = digitalRead(5);
byte wRight = digitalRead(7);
if((wLeft == 0) && (wRight == 0))
158 Robotics with the BOE Shield-Bot
// Back up 1 second
// Turn left about 120 degrees
// If only left whisker contact
// Back up 1 second
// Turn right about 60 degrees
// If only right whisker contact
// Back up 1 second
// Turn left about 60 degrees
// Otherwise, no whisker contact
// Forward 1/50 of a second
}
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)
{
servoLeft.writeMicroseconds(1300);
servoRight.writeMicroseconds(1300);
delay(time);
}
// Backward function
== 0)). Translated to English, it reads if the wLeft variable AND the wRight variable both
equal zero. If both variables are zero, the two calls in the if statements code block get
executed: backward(1000) and turnLeft(800).
if((wLeft == 0) && (wRight == 0)) // If both whiskers contact
{
backward(1000);
// Back up 1 second
turnLeft(800);
// Turn left about 120 degrees
}
In the ifelse ifelse statement, the sketch skips code blocks with conditions that are
not true, and keeps checking until it either finds a condition thats true or runs out of
conditions. When the sketch finds a true statement, it executes whatever is in its code block,
then it skips to the end of the ifelse ifelse statement without checking any more
conditions, and moves on to whatever else comes next in the sketch.
So, if both whiskers are not pressed, that first if statement is not true and its code block is
skipped. The sketch will check the first else if statement. So, maybe the left whisker is
pressed and the calls in this statements code block will run. After backing up for one second
and turning right for 0.4 seconds, the sketch skips the rest of the conditions and moves on to
whatever comes after that last else statement.
else if(wLeft == 0)
{
backward(1000);
turnRight(400);
}
// Back up 1 second
// Turn right about 60 degrees
If its the right whisker that detects an obstacle, the first two code blocks will be skipped, and
the if(wRight == 0) block will run.
else if(wRight == 0)
{
backward(1000);
turnLeft(400);
}
// Back up 1 second
// Turn left about 60 degrees
An else condition functions as a catch-all for when none of the statements preceding it were
true. Its not required, but in this case, its useful for when no whiskers are pressed. If thats
the case, it allows the BOE Shield-Bot to roll forward for 20 ms. Why so little time before the
loop repeats? The small forward time before rechecking allows the BOE Shield-Bot to
respond quickly to changes in the whisker sensors as it rolls forward.
else
{
The forward, backward, turnLeft and turnRight functions were introduced in Chapter 4,
Activity 5: Simplify Navigation with Functions on page 118, and are used in the
MovementsWithSimpleFunctions sketch. These functions certainly simplified the coding.
(Hopefully, they also help demonstrate that all the navigation coding practice from Chapter 4
has its uses!)
Your Turn
You can also modify the sketchs if...else if...else statements to make the LED
indicators broadcast which maneuver the BOE Shield-Bot is running. Just add calls to
digitalWrite that send HIGH and LOW signals to the indicator LED circuits. Here is an
example:
if((wLeft == 0) && (wRight == 0)) // If both whiskers contact
{
digitalWrite(8, HIGH);
// Left LED on
digitalWrite(2, HIGH);
// Right LED on
backward(1000);
// Back up 1 second
turnLeft(800);
// Turn left about 120 degrees
}
else if(wLeft == 0)
// If only left whisker contact
{
digitalWrite(8, HIGH);
// Left LED on
digitalWrite(2, LOW);
// Right LED off
backward(1000);
// Back up 1 second
turnRight(400);
// Turn right about 60 degrees
}
else if(wRight == 0)
// If only right whisker contact
{
digitalWrite(8, LOW);
// Left LED off
digitalWrite(2, HIGH);
// Right LED on
backward(1000);
// Back up 1 second
turnLeft(400);
// Turn left about 60 degrees
}
else
// Otherwise, no whisker contact
{
digitalWrite(8, LOW);
// Left LED off
digitalWrite(2, LOW);
// Right LED off
forward(20);
// Forward 1/50 of a second
}
Remember to set the digital pins to outputs in the setup function so they can
actually supply current to the LEDS:
pinMode(8, OUTPUT);
pinMode(2, OUTPUT);
RoamingWithWhiskers can be expanded to detect this problem and act upon it. The trick is
to count the number of times that alternate whiskers make contact with objects. To do this,
the sketch has to remember what state each whisker was in during the previous contact.
Then, it has to compare those states to the current whisker contact states. If they are
opposite, then add 1 to a counter. If the counter goes over a threshold that you (the
programmer) have determined, then its time to do a U-turn and escape the corner, and also
reset the counter.
This next sketch relies on the fact that you can nest if statements, one inside another. The
sketch checks for one condition, and if that condition is true, it checks for another condition
within the first if statements code block. Well use this technique to detect consecutive
alternate whisker contacts in the next sketch.
This sketch will cause your BOE Shield-Bot to execute a reverse and U-turn to escape a
corner at either the fourth or fifth alternate whisker press, depending on which one was
pressed first.
With the power switch in Position 1, create, save, and run EscapingCorners.
Test this sketch pressing alternate whiskers as the BOE Shield-Bot roams. It
should execute its reverse and U-turn maneuver after either the fourth or fifth
consecutive, alternate whisker contact.
/*
* Robotics with the BOE Shield - EscapingCorners
* Count number of alternate whisker contacts; if it exceeds 4, get out
* of the corner.
*/
162 Robotics with the BOE Shield-Bot
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
byte wLeftOld;
byte wRightOld;
byte counter;
void setup()
{
pinMode(7,
pinMode(5,
pinMode(8,
pinMode(2,
//
//
//
//
servoLeft.attach(13);
servoRight.attach(12);
wLeftOld = 0;
wRightOld = 1;
counter = 0;
}
void loop()
{
// Corner Escape
byte wLeft = digitalRead(5);
byte wRight = digitalRead(7);
if(wLeft != wRight)
// One whisker pressed?
{
// Alternate from last time?
if ((wLeft != wLeftOld) && (wRight != wRightOld))
{
counter++;
// Increase count by one
wLeftOld = wLeft;
// Record current for next rep
wRightOld = wRight;
if(counter == 4)
// Stuck in a corner?
{
wLeft = 0;
// Set up for U-turn
wRight = 0;
counter = 0;
// Clear alternate corner count
}
}
else
// Not alternate from last time
{
counter = 0;
// Clear alternate corner count
}
}
Robotics with the BOE Shield-Bot 163
// Whisker Navigation
if((wLeft == 0) && (wRight == 0))
{
backward(1000);
turnLeft(800);
}
else if(wLeft == 0)
{
backward(1000);
turnRight(400);
}
else if(wRight == 0)
{
backward(1000);
turnLeft(400);
}
else
{
forward(20);
}
}
void forward(int time)
{
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1300);
delay(time);
}
// Forward function
// Backward function
// Left wheel clockwise
// Right wheel counterclockwise
// Maneuver for time ms
First, three global byte variables are added: wLeftOld, wRightOld, and counter. The
wLeftOld and wRightOld variables store the whisker states from a previous whisker
contact so that they can be compared with the states of the current contact. Then counter is
used to track the number of consecutive, alternate contacts.
byte wLeftOld;
byte wRightOld;
byte counter;
These variables are initialized in the setup function. The counter variable can start with
zero, but one of the old variables has to be set to 1. Since the routine for detecting corners
always looks for an alternating pattern, and compares it to the previous alternating pattern,
there has to be an initial alternate pattern to start with. So, wLeftOld and wRightOld are
assigned initial values in the setup function before the loop function starts checking and
modifying their values.
wLeftOld = 0;
wRightOld = 1;
counter = 0;
The first thing the code below // Corner Escape has to do is check if one or the other
whisker is pressed. A simple way to do this is to use the not-equal operator (!= ) in an if
statement. In English, if(wLeft != wRight) means if the wLeft variable is not equal to
the wRight variable
// Corner Escape
if(wLeft != wRight)
If they are not equal it means one whisker is pressed, and the sketch has to check whether
its the opposite pattern as the previous whisker contact. To do that, a nested if statement
checks if the current wLeft value is different from the previous one and if the current
wRight value is different from the previous one. Thats if((wLeft != wLeftOld) &&
(wRight != wRightOld)). If both conditions are true, its time to add 1 to the counter
variable that tracks alternate whisker contacts. Its also time to remember the current
whisker pattern by setting wLeftOld equal to the current wLeft and wRightOld equal to the
current wRight.
Robotics with the BOE Shield-Bot 165
If this is the fourth consecutive alternate whisker contact, then its time to reset the counter
variable to 0 and execute a U-turn. When the if(counter == 4) statement is true, its code
block tricks the whisker navigation routine into thinking both whiskers are pressed. How
does it do that? It sets both wLeft and wRight to zero. This makes the whisker navigation
routine think both whiskers are pressed, so it makes a U-turn.
if(counter == 4)
{
wLeft = 0;
wRight = 0;
counter = 0;
}
// Stuck in a corner?
One thing that can be tricky about nested if statements is keeping track of opening and
closing braces for each statements code block. The picture below shows some nested if
statements from the last sketch. In the Arduino and Codebender editors, you can doubleclick on a brace to highlight its code block. But sometimes, printing out the code and simply
drawing lines to connect opening and closing braces helps to see all the blocks at once, which
is useful for finding bugs in deeply nested code.
In this picture, the if(wLeft != wRight) statements code block contains all the rest of the
decision-making code. If it turns out that wLeft is equal to wRight, the Arduino skips to
whatever code follows that last closing brace }. The second level if statement compares
the old and new wLeft and wRight values with if ((wLeft != wLeftOld) && (wRight
!= wRightOld)). Notice that its code block ending brace is just below the one for the
if(counter==4) block. The if ((wLeft != wLeftOld) && (wRight != wRightOld))
166 Robotics with the BOE Shield-Bot
statement also has an else condition with a block that sets counter to zero if the whisker
values are not opposite from those of the previous contact.
// Corner Escape
Your Turn
Try increasing the value to 5 and 6 and test the effect. Keep in mind that it will
either count to the number of alternate whisker contacts, or maybe one more
than that depending on which side you start.
Chapter 5 Summary
This chapter introduced the first sensor system for the BOE Shield-Bot, and allowed the
robot to roam around on its own and navigate by touch. The project built on skills acquired
in the last chapter, and employed a variety of new ones:
Electronics
Programming
Robotics Skills
Engineering Skills
Chapter 5 Challenges
Questions
1.
2.
3.
4.
5.
What statements did this chapter use to call different navigation functions based
on whisker states?
What is the purpose of having nested if statements?
Exercises
1.
2.
3.
4.
Write a routine that uses a single variable named whiskers to track whisker
contacts. It should store a 3 when no whiskers are contacted, a 2 if the right
whisker is contacted, a 1 if the left whisker is contacted, or 0 if both whiskers are
contacted. Hint: multiply the result by two.
Modify the loop function in RoamingWithWhiskers so that it makes the BOE
Shield-Bot stop and not restart when both whiskers contact at the same time.
Add a function named pause to RoamingWithWhiskers. It should make the BOE
Shield-Bot stay still for a certain amount of time.
Modify the loop function so that the BOE Shield-Bot stays still for 0.5 seconds
before backing up and turning.
Projects
1.
2.
Question Solutions
1.
2.
3.
4.
5.
If one condition turns out to be true, the code might need to evaluate another
condition with a nested if statement.
Exercise Solutions
1.
pinMode(7, INPUT);
pinMode(5, INPUT);
Serial.begin(9600);
}
void loop()
// Main loop auto-repeats
{
byte whiskers = 2 * digitalRead(5);
whiskers += digitalRead(7);
Serial.println(whiskers);
// Display wLeft
delay(50);
// Pause for 50 ms
2.
void loop()
{
byte wLeft = digitalRead(5);
byte wRight = digitalRead(7);
if((wLeft == 0) && (wRight == 0))
{
pause(500);
170 Robotics with the BOE Shield-Bot
// Back up 1 second
// Turn left about 120 degrees
// If only left whisker contact
// Pause motion for 0.5 seconds
// Back up 1 second
// Turn right about 60 degrees
// If only right whisker contact
// Pause motion for 0.5 seconds
// Back up 1 second
// Turn left about 60 degrees
// Otherwise, no whisker contact
// Forward 1/50 of a second
3.
Solution:
4.
Make sure not to call this pause function in the else condition because the
forward function is only supposed to go forward for 20 ms before checking
the whiskers again.
void loop()
{
byte wLeft = digitalRead(5);
byte wRight = digitalRead(7);
Project Solutions
1.
The key to solving this problem is to write a statement that makes a beep with
the required parameters. As soon as the beep starts, call the pause function to
keep the BOE Shield-Bot still while it beeps. Make sure not to add any pause calls
to the else statements code block. It needs to repeatedly go forward for 20 ms,
without any pauses.
Servo servoLeft;
Servo servoRight;
void setup()
{
pinMode(7, INPUT);
pinMode(5, INPUT);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
byte wLeft = digitalRead(5);
byte wRight = digitalRead(7);
//
//
//
//
//
//
Play
Stop
Play
Stop
Back
Turn
a 0.1 ms tone
for 0.2 seconds
a 0.1 ms tone
for 0.2 seconds
up 1 second
left about 120 degrees
Play
Stop
Back
Turn
a 0.1 ms tone
for 0.2 seconds
up 1 second
right about 60 degrees
Play
Stop
Back
Turn
a 0.1 ms tone
for 0.2 seconds
up 1 second
left about 60 degrees
}
void pause(int time)
{
servoLeft.writeMicroseconds(1500);
servoRight.writeMicroseconds(1500);
delay(time);
}
// Backward function
// Forward function
2.
//
//
//
//
// Backward function
// Left wheel clockwise
// Right wheel counterclockwise
// Maneuver for time ms
Start with the Circle sketch, from the Chapter 4 Project Solutions that begin on
page 144. Comment the detach calls and move the circle code to the loop
function and reduce the delay to 50 ms so that it can check the whiskers for
contacts 20 times per second. Then, add the whisker monitoring code with an if
statement that reduces or increases a variable that slows the right wheel when
the right whisker is pressed, or speeds up the right wheel if the left whisker is
pressed.
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
int turn;
void setup()
{
pinMode(7, INPUT);
pinMode(5, INPUT);
servoLeft.attach(13);
servoRight.attach(12);
turn = 0;
//
//
}
servoLeft.detach();
servoRight.detach();
if(wLeft == 0)
{
turn -= 10;
}
else if(wRight == 0)
{
turn += 10;
}
// Arc to the right
servoLeft.writeMicroseconds(1600); // Left wheel counterclockwise
servoRight.writeMicroseconds(1438 + turn); // Rt wheel clockwise slower
delay(50);
// ...for 25.5 seconds
}
Chapter 6. Light-Sensitive
Navigation with Phototransistors
Light sensors have many applications in robotics and industrial control: finding the edge of a
roll of fabric in a textile mill, determining when to activate streetlights at different times of
the year, when to take a picture, or when to deliver water to a crop of plants.
The light sensors in your Robotics Shield Kit respond to visible light, and also to an invisible
type of light called infrared. These sensors can be used in different circuits that the Arduino
can monitor to detect variations in light level. With this information, your sketch can be
expanded to make the BOE Shield-Bot navigate by light, such as driving toward a flashlight
beam or an open doorway letting light into a dark room.
Light-sensitive phototransistors
let the BOE Shield-Bot
navigate by driving towards a
bright light source.
Collector
C
Base
B
E
Current
Emitter
E
C
The phototransistor looks a little bit like an LED. The two devices do have two similarities.
First, if you connect the phototransistor in the circuit backwards, it wont work right.
Second, it also has two different length pins and a flat spot on its plastic case for identifying
its terminals. The longer of the two pins indicates the phototransistors collector terminal.
The shorter pin indicates the emitter, and it connects closer to a flat spot on the
phototransistors clear plastic case.
Light Waves
In the ocean, you can measure the distance between the peaks of two adjacent waves in feet
or meters. With light, which also travels in waves, the distance between adjacent peaks is
measured in nanometers (nm) which are billionths of meters. The figure below shows the
wavelengths for colors of light we are familiar with, along with some the human eye cannot
detect, such as ultraviolet and infrared.
You can see this image in color in the free PDF version of this book, availabe for download from
the #122-32335 product page at www.parallax.com.
450
495
Violet
Ultraviolet
Blue
Orange
Yellow
750100,000
Infrared
Red
The phototransistor in the Robotics Shield Kit is most sensitive to 850 nm wavelengths,
which is in the infrared range. Infrared light is not visible to the human eye, but many
different light sources emit considerable amounts of it, including halogen and incandescent
lamps, and especially the sun. This phototransistor also responds to visible light, though its
less sensitive, especially to wavelengths below 450 nm.
The phototransistor circuits in this chapter are designed to work well indoors, with
fluorescent or incandescent lighting. Make sure to avoid direct sunlight and direct halogen
lights; they would flood the phototransistors with too much infrared light.
In your robotics area, close window blinds to block direct sunlight, and point any halogen
lamps upward so that the light is reflected off the ceiling.
Imagine that your BOE Shield-Bot is navigating a course, and theres a bright light at the end.
Your robots final task in the course is to stop underneath that bright light. Theres a simple
phototransistor circuit you can use that lets the Arduino know it detected bright light with a
binary-1, or ambient light with a binary-0. Incandescent bulbs in desk lamps and flashlights
make the best bright-light sources. Compact fluorescent and LED light sources are not as
easy for the circuit in this activity to recognize.
178 Robotics with the BOE Shield-Bot
Ambient means existing or present on all sides according to Merriam Websters dictionary. For
the light level in a room, think about ambient light as the overall level of brightness.
Parts List
(1) phototransistor
(2) jumper wires
(1) resistor, 2 k (red-black-red)
(1) incandescent or fluorescent flashlight or desk lamp
After some testing, and depending on the light conditions in your robotics area, you might
end up replacing the 2 k resistor with one of these resistors, so keep them handy:
The next drawing will help you tell apart the phototransistor and infrared LED, since they
look similar.
USE THIS ONE! Phototransistor
Flatter on top
Infrared LED
More Rounded Dome
The schematic and wiring diagram below show a circuit very similar to the ones
in streetlights that turn on automatically at night. The circuit outputs a voltage
that varies depending on how much light shines on the phototransistor. The
Arduino will monitor the voltage level with one of its analog input pins.
Flat Spot,
Shorter Pin
Disconnect the battery pack and programming cable from your Arduino, and set
the BOE Shields switch to 0.
Remove the whisker circuits, but leave the piezospeaker circuit in place.
Build the circuit shown, using the 2 k resistor.
Double-check to make sure you connect the phototransistors emitter lead (by
the flat spot) to the resistor, and its collector to 5V.
Also double-check that the phototransistors leads are not touching each other.
The PhototransistorVoltage sketch makes the Serial Monitor display the voltage measured at
A3one of the Arduinos five analog input channels that are accessible through the BOE
Shield. In the circuit you just built, a wire connects A3 to the row where the
phototransistors emitter and resistor meet. The voltage at this part of the circuit will
change as the light level sensed by the phototransistor changes. The Serial Monitor screen
capture below shows some example voltage measurements.
180 Robotics with the BOE Shield-Bot
Low light
Bright light
/*
* Robotics with the BOE Shield - PhototransistorVoltage
* Display voltage of phototransistor circuit output connected to A3 in
* the serial monitor.
*/
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.print("A3 = ");
Serial.print(volts(A3));
Serial.println(" volts");
delay(1000);
}
//
//
//
//
Calculate the half-way point between the ambient and bright light voltages you
recorded from the last sketch.
In the HaltUnderBrightLight sketch, substitute your half way point value in place
of 3.5 in the statement if(volts(A3) > 3.5.
Run your modified version of HaltUnderBrightLight on the Arduino.
Hold your flashlight or lamp about a foot off of the floor, and put the BOE ShieldBot on the floor a couple feet away but pointed so it will go straight under the
light.
Move the power switch to position 2 so the BOE Shield-Bot will drive forward.
How close did it get to stopping directly under the light?
Try making adjustments to the threshold you set in the if(volts(A3) >)
statement to get the BOE Shield-Bot to park right underneath that bright light.
/*
* Robotics with the BOE Shield - HaltUnderBrightLight
* Display voltage of phototransistor circuit output connected to A3 in
* the serial monitor.
*/
#include <Servo.h>
182 Robotics with the BOE Shield-Bot
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1300);
}
void loop()
{
if(volts(A3) > 3.5)
{
servoLeft.detach();
servoRight.detach();
}
}
By default, the Arduinos analogRead function is configured to use the 01023 values to
describe where a voltage measurement falls in a 5 V scale. If you split 5 V into 1024 different
levels, each level is 5/1024ths of a volt apart. 5/1024ths of a volt is approximately
0.004882813 V, or about 4.89 thousandths of a volt. So, to convert a value returned by
analogRead to a voltmeter-style value, all the volts function has to do is multiply by 5 and
divide by 1024.
Example: the analogRead function returns 645; how many volts is that? Answer:
5V
1024
= 3.1494140625 V
3.15 V
V = 645
The sketches have been calling the volts function with volts(A3). When they do that, they
pass A3 to its adPin parameter. Inside the function, analogRead(adPin) becomes
analogRead(A3). It returns a value in the 0 to 1023 range that represents the voltage
applied to A3. The analogRead call returns an integer, but since it is nested in
float(analogRead(adPin), that integer value gets converted to floating point. Then, its
multiplied by the floating point value 5.0 and divided by 1024.0, which converts it to a
voltmeter value (just like we converted 645 to 3.15 V).
float volts(int adPin)
// Measures volts at adPin
{
// Returns floating point voltage
return float(analogRead(adPin)) * 5.0 / 1024.0;
}
Since return is to the left of the calculation in the volts function block, the result gets
returned to the function call. The sketch PhototransistorVoltage displays the value returned
by the volts function with Serial.print(volts(A3)).
HaltUnderBrightLight uses that value in the if(volts(A3) > 3.5) expression to bring the
BOE Shield-Bot to a halt under the light.
Binary vs. Analog and Digital
A binary sensor can transmit two different states, typically to indicate the presence or absence of
something. For example, a whisker sends a high signal if it is not pressed, or a low signal if it is
pressed.
An analog sensor sends a continuous range of values that correspond to a continuous range of
measurements. The phototransistor circuits in this activity are examples of analog sensors. They
provide continuous ranges of values that correspond to continuous ranges of light levels.
A digital value is a number expressed by digits. Computers and microcontrollers store analog
measurements as digital values. The process of measuring an analog sensor and storing that
measurement as a digital value is called analog to digital conversion. The measurement is called a
digitized measurement. Analog to digital conversion documents will also call them quantized
measurements.
The reason the voltage at A3 (VA3) changes with light is because the phototransistor lets
more current pass when more light shines on it, or less current pass with less light. That
current, which is labeled I in the circuit below, also has to pass through the resistor. When
more current passes through a resistor, the voltage across it will be higher. When less
current passes, the voltage will be lower. Since one end of the resistor is tied to GND = 0 V,
the voltage at the VA3 end goes up with more current and down with less current.
If you replace the 2 k resistor with a 1 k resistor, VA3 will see smaller values for the same
currents. In fact, it will take twice as much current to get VA3 to the same voltage level, which
means the light will have to be twice as bright to reach the 3.5 V level, the default voltage in
HaltUnderBrightLight to make the BOE Shield-Bot stop.
So, a smaller resistor in series with the phototransistor makes the circuit less sensitive to
light. If you instead replace the 2 k resistor with a 10 k resistor, VA3 will be 5 times larger
with the same current, and itll only take 1/5th the light to generate 1/5th the current to get
VA3 past the 3.5 V level. So, a larger resistor makes the circuit more sensitive to light.
Connected in Series: When two or more elements are connected end-to-end, they are connected
in series. The phototransistor and resistor in this circuit are connected in series.
Ohm's Law
Two properties affect the voltage at VA3: current and resistance, and Ohms Law explains
how it works. Ohms Law states that the voltage (V) across a resistor is equal to the current
(I) passing through it multiplied by its resistance (R). So, if you know two of these values,
you can use the Ohms Law equation to calculate the third:
V=IR
E = I x R: In some textbooks, you will see E = I R instead. E stands for electric potential, which is
another way to say volts.
Voltage (V) is measured in units of volts, which are abbreviated with an upper-case V.
Current (I) is measured in amperes, or amps, which are abbreviated A. Resistance (R) is
186 Robotics with the BOE Shield-Bot
measured in ohms which is abbreviated with the Greek letter omega (). The current levels
you are likely to see through this circuit are in milliamps (mA). The lower-case m indicates
that its a measurement of thousandths of amps. Similarly, the lower-case k in k indicates
that the measurement is in thousands of ohms.
Lets use Ohms Law to calculate VA3 in with the phototransistor, letting two different
amounts of current flow through the circuit:
The examples below show the conditions and their solutions. When you try these
calculations, remember that milli (m) is thousandths and kilo (k) is thousands when you
substitute the numbers into Ohms Law.
Example 1: I = 1.75 mA and R = 2 k
V A3 = I R
= 1. 75mA 2 k
1. 75
A 2000
1000
= 1. 75 A 2
= 3. 5 A
= 3. 5 V
=
V A3 = I R
= 0. 25mA 2 k
0. 25
A 2000
1000
= 0. 25 A 2
= 0. 5 A
= 0. 5 V
=
Question: What could you do to bring the circuits voltage response back down
to 3.5 V for bright light and 0.5 V for dim?
The circuit in the previous activity only works over a limited light range. You might get the
Activity #1 circuit all nice and calibrated in one room, then take it to a brighter room and
find that all the voltage measurements will sit at the maximum value. Or, maybe youll take
it into a darker room, and the voltages will end up never making it past 0.1 V.
This activity introduces a different phototransistor circuit that the Arduino can use to
measure a much wider range of light levels. This circuit and sketch can return values
ranging from 0 to over 75,000. Be aware: this time the smaller values indicate bright
light, and large values indicate low light.
188 Robotics with the BOE Shield-Bot
How much charge a capacitor can store is measured in farads (F). A farad is a very large
value thats not practical for use with these BOE Shield-Bot circuits. The capacitors in your
kit store fractions of millionths of farads. A millionth of a farad is called a microfarad, and it
is abbreviated F. This one stores one tenth of one millionth of a farad: 0.1 F.
Common Capacitance Measurements
1 F = 110-6 F
-9
1 nF = 110 F
-12
1 pF = 110 F
The 104 on the 0.1 F capacitors case is a measurement in picofarads (pF). In this labeling
system, 104 is the number 10 with four zeros added, so the capacitor is 100,000 pF, or 0.1 F.
(100,000) (1 10-12) F
= 100 10-9 F
= 0.1 F.
=
=
Parts List
(2) phototransistors
(2) capacitors, 0.1 F (104)
(2) resistors, 1 k (brown-black-red)
(2) jumper wires
Flat
spots,
shorter
pins
connect
to GND
The roaming examples in this chapter will depend on the phototransistors being pointed
upward and outward to detect differences in light levels from different directions.
This kind of phototransistor/capacitor circuit is called a charge transfer circuit. The Arduino
will determine the rate at which each capacitor loses its charge through its phototransistor
by measuring how long it takes the capacitors voltage to decay, that is, to drop below a
certain voltage value. The decay time corresponds to how wide open that current valve is,
which is controlled by the brightness of the light reaching the phototransistors base. More
light means faster decay, less light means slower decay.
QT Circuit: A common abbreviation for charge transfer is QT. The letter Q refers to electrical
charge (an accumulation of electrons), and T is for transfer.
Connected in Parallel: The phototransistor and capacitor shown above are connected in parallel;
each of their leads are connected to common terminals (also called nodes). The phototransistor
and the capacitor each have one lead connected to GND, and they also each have one lead
connected to the same 1 k resistor lead.
Well be using this light-sensing technique for the rest of the chapter, so you can take the
BOE Shield-Bot from one room to another without having to worry about finding the right
resistors for different ambient light levels.
If there is direct sunlight shining in through the windows, close the blinds.
Create, save, and run LeftLightSensor, and open the Serial Monitor.
Make a note of the value displayed in the Serial Monitor.
If the Serial Monitor does not display values or seems to get stuck after just one
or two, it may mean that theres an error in your circuit. If you see these
symptoms, check your wiring and try again.
Use your hand or a book to cast a shadow over the pin 8 phototransistor circuit.
Check the measurement in the Serial Monitor again. The value should be larger
than the first one. Make a note of it too.
/*
* Robotics with the BOE Shield - LeftLightSensor
* Measures and displays microsecond decay time for left light sensor.
*/
void setup()
{
tone(4, 3000, 1000);
delay(1000);
Serial.begin(9600);
}
void loop()
{
long tLeft = rcTime(8);
Serial.print("tLeft = ");
Serial.print(tLeft);
Serial.println(" us");
delay(1000);
// 1 second delay
}
long rcTime(int pin)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(1);
pinMode(pin, INPUT);
digitalWrite(pin, LOW);
long time = micros();
while(digitalRead(pin));
time = micros() - time;
return time;
}
Charge capacitor
..by setting pin ouput-high
..for 5 ms
Set pin to input
..with no pullup
Mark the time
Wait for voltage < threshold
Calculate decay time
Return decay time
Run the sketch, and verify that the pin 6 light sensor circuit is working.
It would also be nice to have a third sketch that tests both phototransistor
circuits.
Re-save the sketch as BothLightSensors, and update the comments.
Replace the loop function with the one below.
Try rotating your BOE Shield-Bot until one side is pointing toward the brightest light source
in the room and the other is pointing away from it. What is the largest difference you can get
between tLeft and tRight in the Serial Monitor?
void loop()
{
long tLeft = rcTime(8);
Serial.print("tLeft = ");
Serial.print(tLeft);
Serial.print(" us
");
//
//
//
//
delay(1000);
// 1 second delay
tRight
label
value
units + newline
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(1);
pinMode(pin, INPUT);
digitalWrite(pin, LOW);
long time = micros();
while(digitalRead(pin));
time = micros() - time;
return time;
//
//
//
//
//
//
//
//
Step
Step
Step
Step
Step
Step
Step
Step
1, part
1, part
2
3 part
3, part
4
5
6 & 7
1
2
1
2
In this sketch, Step 1 has two sub-steps. First, pinMode(pin, OUPUT) sets the I/O pin to an
output, then digitalWrite(pin, HIGH) makes it supply 5 V to the circuit.
Step 3 also has two sub-steps, because the I/O pin is sending a high signal. When the sketch
changes the I/O pins direction from output-high to input, it adds 10 k of resistance to the
circuit, which must be removed. Adding digitalWrite(pin, LOW) after pinMode(pin,
INPUT) removes that resistance and allows the capacitor to drain its charge normally
through the phototransistor.
digitalWrite(8, HIGH)
5V
delay(1)
tLeft
2.1 V
0V
tRight = rcTime(6)
digitalWrite(6, HIGH)
delay(1)
tRight
5V
2.1 V
0V
The upper trace in the graph plots the capacitors voltage in the pin 8 QT circuit; thats the
left light sensor. In response to digitalWrite(8, HIGH), the voltage quickly rises from 0
V to almost 5 V at about the 1 ms mark. The signal stays at around 5 V for the duration of
delay(1). Then, at the 2 ms mark, the rcTime call causes the decay to start. The rcTime
function measures the time it takes the voltage to decay to about 2.1 V and stores it in the
tLeft variable. In the plot, it looks like that decay took about 1 ms, so the tLeft variable
should store a value close to 1000.
The lower trace in the graph plots the pin 6 QT circuits capacitor voltagethe right light
sensor. This measurement starts after the left sensor measurement is done. The voltage
varies in a manner similar to the upper trace, except the decay time takes about 2 ms. We
would expect to see tRight store a value in the 2000 neighborhood. This larger value
corresponds to a slower decay, which in turn corresponds to a lower light level.
Maybe that statement would work well for turning away from shadows in one room, but
take it to another with brighter lights, and it might never detect a shadow. Or, take it to a
darker room, and it might think its seeing shadows all the time. For navigation, what
matters is not an actual number reporting the light level over each sensor. What matters is
the difference in how much light the two sensors detect, so the robot can turn toward the
sensor seeing brighter light (or away from it, depending on what you want.)
The solution is simple. Just divide the right sensor measurement into the sum of both. Your
result will always be in the 0 to 1 range. This technique is an example of a normalized
differential measurement Heres what it looks like as an equation:
tRight
tRight + tLeft
For example, a normalized differential measurement of 0.25 would mean the light is 1/2 as
bright over the right sensor as it is over the left. The actual values for tRight and tLeft
might be small in a bright room or large in a dark room, but the answer will still be 0.25 if
the light is 1/2 as bright over the right sensor. A measurement of 0.5 would mean that the
tRight and tLeft values are equal. They could both be large, or both be small, but if the
result is 0.5, it means the sensors are detecting the same level of brightness.
Heres another trick: subtract 0.5 from the normalized differential shade measurement. That
way, the results range from 0.5 to +0.5 instead of 0 to 1, and a measurement of 0 means
equal brightness. The result is a zero-justified normalized differential shade measurement.
tRight
0.5
tRight + tLeft
But why do it? The value range 0.5 to +0.5 is great for navigation sketches because the
positive and negative values can be used to scale the wheels speeds. Here is how the zerojustified normalized differential shade equation appears in the next sketch:
float ndShade;
// Normalized differential shade
ndShade = tRight / (tLeft + tRight) - 0.5; // Calculate & subtract 0.5
The final measurement will be stored in a floating point variable named ndShade, so that
gets declared first. Then, the next line does the zero-justified normalized differential shade
math. The result will be a value in the 0.5 to +0.5 range that represents the fraction of total
shade that tRight detects, compared to tLeft. When ndShade is 0, it means tRight and
tLeft are the same values, so the sensors are detecting equally bright light. The closer
ndShade gets to 0.5, the darker the shade over the right sensor. The closer ndShade gets to
0.5 the darker the shade over the left sensor. This will be very useful for navigation. Lets
test it first with the Serial Monitor.
This screen capture shows a Serial Monitor example with the LightSensorValues sketch
running. With shade over the right sensor, the ndShade value is about 0.4. With shade over
the left sensor, its about 0.4.
Serial.begin(9600);
}
void loop()
{
float tLeft = float(rcTime(8));
float tRight = float(rcTime(6));
float ndShade;
// Normalized differential shade
ndShade = tRight / (tLeft + tRight) - 0.5; // Calculate & subtract 0.5
// Display heading
Serial.println("tLeft
ndShade
tRight");
Serial.print(tLeft);
Serial.print("
");
Serial.print(ndShade);
Serial.print("
");
Serial.println(tRight);
Serial.println(' ');
//
//
//
//
//
//
delay(1000);
// 1 second delay
}
long rcTime(int pin)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(5);
pinMode(pin, INPUT);
digitalWrite(pin, LOW);
long time = micros();
while(digitalRead(pin));
time = micros() - time;
return time;
}
Charge capacitor
..by setting pin ouput-high
..for 5 ms
Set pin to input
..with no pullup
Mark the time
Wait for voltage < threshold
Calculate decay time
Returns decay time
/*
* Robotics with the BOE Shield - LightSensorDisplay
* Displays a scrolling graph of ndShade. The asterisk positions ranges
* from 0 to 40 with 20 (middle of the display) indicating same light on
* both sides.
*/
void setup()
{
200 Robotics with the BOE Shield-Bot
}
void loop()
{
float tLeft = float(rcTime(8));
float tRight = float(rcTime(6));
float ndShade;
// Normalized differential shade
ndShade = tRight / (tLeft+tRight) - 0.5;
// Calculate & subtract 0.5
for(int i = 0; i<(ndShade * 40) + 20; i++) // Place asterisk in 0 to 40
{
Serial.print(' ');
// Pad (ndShade * 40) + 20 spaces
}
Serial.println('*');
// Print asterisk and newline
delay(100);
}
long rcTime(int pin)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(5);
pinMode(pin, INPUT);
digitalWrite(pin, LOW);
long time = micros();
while(digitalRead(pin));
time = micros() - time;
return time;
}
Charge capacitor
..by setting pin ouput-high
..for 5 ms
Set pin to input
..with no pullup
Mark the time
Wait for voltage < threshold
Calculate decay time
Returns decay time
After declaring ndShade as a floating-point variable, tLeft and tRight are used in an
expression to get that zero-justified normalized differential measurement. The result will be
between 0.5 and +0.5, and gets stored in ndShade.
float ndShade;
Next, this for loop places the cursor in the right place for printing an asterisk. Take a close
look at the for loops condition. It takes ndShade and multiples it by 40. It also has to add
20 to the value because if ndShade is 0.5, we want that to print with zero leading spaces.
So (0.5 40) + 20 = 0. Now, if ndShade is 0, we want it to print 20 spaces over: (0 40) +
20 = 20. If its +0.5 we want it to print 40 spaces over: (0.5 40) + 20 = 40. Of course, if its
something in between, like 0.25, we have (0.25 40) + 20 = 30. So, itll print half way
between center and far right.
for(int i = 0; i<(ndShade * 40) + 20; i++) // Place asterisk in 0 to 40
{
Serial.print(' ');
// Pad (ndShade * 40) + 20 spaces
}
After printing the spaces, a single asterisk prints on the line. Recall that println prints and
also adds a newline so that the next time through the loop, the asterisk will display on the
next line.
Serial.println('*');
delay(100);
Here is an if statement that works well for turning away from shade on the right side of the
BOE Shield-Bot. It starts by declaring two int variables, speedLeft and speedRight. They
are not declared within the ifelse block because other blocks in the loop function will
need to check their values too. Next, if(ndShade > 0.0) has a code block that will be
executed if shade is detected on the robots right side, slowing down the left wheel to make
the BOE Shield-Bot turn away from the dark. To do this, ndShade * 1000.0 is subtracted
from 200. Before assigning the result to speedLeft, int(200.0(ndShade1000.0)
converts the answer from a floating point value back to an integer. Were doing this to make
the value compatible with our custom maneuver function , which needs an int value.
202 Robotics with the BOE Shield-Bot
This diagram shows an example of how this works when ndShade is 0.125. The left wheel
slows down because 200 (0.1251000) = 75. Since linear speed control is in the 100
to -100 range, it puts the wheel at about of full speed. Meanwhile, on the other side,
speedRight is set to 200 for full speed forward.
The larger ndShade is, the more it subtracts from 200. Thats not a problem in this example,
but if ndShade were 0.45, it would try to store 250 in the speedLeft variable. Since the
speeds well want to pass to the maneuver function need to be in the -200 to 200 range, well
use the Arduinos constrain function to prevent speedLeft from going out of bounds:
speedLeft = constrain(speedLeft, 200, 200).
Here is an else statement that works well for turning away from shade on the left. It slows
down the right wheel and keeps the left wheel going full speed forward. Notice that it adds
(ndShade*1000) to 200. Reason being, this is the else statement for if(ndShade > 0.0),
so it will get used when ndShade is equal to or smaller than zero. So, if ndShade is 0.125,
speedRight = int(200.0 + (ndShade * 1000.0)) would evaluate to 200 + (1.25
1000) = 200 125 = 75. The constrain function is used again, to limit speedRight.
else
// Shade on Left?
{
// Slow down right wheel
speedRight = int(200.0 + (ndShade * 1000.0));
speedRight = constrain(speedRight, -200, 200);
speedLeft = 200;
// Full speed left wheel
}
//
//
//
//
//
Display speedLeft
Spaces
Display ndShade
More spaces
Display speedRight
delay(2000);
// 1 second delay
The print and println calls should result in a display like the one below. It shows the value
of speedLeft in the left column, speedRight in the right column, and ndShade between
them. Watch it carefully. The side with brighter light will always display 200 for full-speedforward, and the other will be slowing down with values less than 200the darker the
shade, the smaller the number.
ndShade < 0
shade over left
Slow down
speedRight
ndShade > 0
shade over right
Slow down
speedLeft
Back to almost
equal light and
nearly full speed
forward
/*
* Robotics with the BOE Shield - LightSeekingDisplay
* Displays speedLeft, ndShade, and speedRight in Serial Monitor. Verifies
* that wheel speeds respond correctly to left/right light/shade conditions.
*/
void setup()
{
tone(4, 3000, 1000);
delay(1000);
Serial.begin(9600);
}
void loop()
{
float tLeft = float(rcTime(8));
float tRight = float(rcTime(6));
float ndShade;
// Normalized differential shade
ndShade = tRight / (tLeft+tRight) - 0.5;
// Calculate & subtract 0.5
int speedLeft, speedRight;
//
//
//
//
//
Display speedLeft
Spaces
Display ndShade
More spaces
Display speedRight
delay(1000);
// 1 second delay
}
long rcTime(int pin)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(5);
pinMode(pin, INPUT);
digitalWrite(pin, LOW);
long time = micros();
while(digitalRead(pin));
time = micros() - time;
return time;
}
Charge capacitor
..by setting pin ouput-high
..for 5 ms
Set pin to input
..with no pullup
Mark the time
Wait for voltage < threshold
Calculate decay time
Returns decay time
Add a call to the loop function to pass speedLeft and speedRight to the
maneuver function.
/*
* Robotics with the BOE Shield - LightSeekingShieldBot
* Roams toward light and away from shade.
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
float tLeft = float(rcTime(8));
float tRight = float(rcTime(6));
float ndShade;
// Normalized differential shade
ndShade = tRight / (tLeft+tRight) - 0.5;
// Calculate & subtract 0.5
int speedLeft, speedRight;
// Shade on right?
// Slow down left wheel
Robotics with the BOE Shield-Bot 207
}
long rcTime(int pin)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(5);
pinMode(pin, INPUT);
digitalWrite(pin, LOW);
long time = micros();
while(digitalRead(pin));
time = micros() - time;
return time;
}
Charge capacitor
..by setting pin ouput-high
..for 5 ms
Set pin to input
..with no pullup
Mark the time
Wait for voltage < threshold
Calculate decay time
Returns decay time
// maneuver function
void maneuver(int speedLeft, int speedRight, int msTime)
{
servoLeft.writeMicroseconds(1500 + speedLeft);
// Left servo speed
servoRight.writeMicroseconds(1500 - speedRight); // Right servo speed
if(msTime==-1)
// If msTime = -1
{
servoLeft.detach();
// Stop servo signals
servoRight.detach();
}
delay(msTime);
// Delay for msTime
}
If you want more sensitivity to light, change 1000 to a larger value in these two
commands:
Try it.
Here are several more light-sensing navigation ideas for your BOE Shield-Bot that can be
made with adjustments to the loop function:
To make your BOE Shield-Bot follow shade instead of light, place ndShade
= -ndShade right before the ifelse statement. Curious about how or why this
works? Check out Project 2 at the end of this chapter.
End roaming under a bright light or in a dark cubby by detecting very bright or
very dark conditions. Add tLeft and tRight together, and compare the result to
either a really high (dark) threshold value or a really low (bright) threshold
value.
Make your BOE Shield-Bot function as a light compass by remaining stationary
and rotating toward bright light sources.
Incorporate whiskers into the roaming toward light activity so that the BOE
Shield-Bot can detect and navigate around objects in its way.
Chapter 6 Summary
This chapter focused on using a pair of light sensors to detect bright light and shade for
robot navigation. Lots of interesting electronics concepts and programming techniques
come into play.
Electronics
What a phototransistor is, and how to identify its base, emitter and collector
What wavelengths are in the ultraviolet, visible, and infrared spectrums
What is meant by ambient light
What the difference is between a binary sensor and an analog sensor
What Ohms Law is, and how to use it to select a resistor to adjust the
phototransistor circuits voltage response
Using a phototransistor as a simple binary sensor in a voltage output circuit
What a capacitor is, and how small capacitors for breadboard circuits are labeled
in units of picofarads
Using a phototransistor as an analog sensor in a resistor-capacitor chargetransfer circuit, also called a QT circuit
What it means when components are connected in series vs. connected in parallel
What voltage decay is, and how its used in a resistor-capacitor circuit
Programming
How to use the Arduinos constrain function to set upper and lower limits for a
variable value
Robotics Skills
Engineering Skills
Chapter 6 Challenges
Questions
1.
2.
3.
4.
5.
6.
7.
8.
9.
What happens when the voltage applied to an I/O pin that has been set to input is
above or below the threshold voltage?
If the amount of charge a capacitor stores decreases, what happens to the voltage
at its terminals?
Exercises
1.
2.
3.
4.
5.
6.
Projects
1.
2.
3.
In Activity 1 the circuit, along with the HaltUnderBrightLight sketch, made the
BOE Shield-Bot stop under a light at the end of the course. What if you will only
have a limited time at a course before a competition, and you dont know the
lighting conditions in advance? You might need to calibrate your BOE Shield-Bot
on site. A sketch that makes the piezospeaker beep repeatedly when the BOE
Shield-Bot detects bright light and stay quiet when it detects ambient light could
be useful for this task. Write and test a sketch to do this with the circuit in
Activity 1.
Develop an application that makes the BOE Shield-Bot roam and search for
darkness instead of light. This application should utilize the charge transfer
circuits from Building the Photosensitive Eyes on page 189.
Develop an application that makes the BOE Shield-Bot roam toward a bright
incandescent desk lamp in a room where the only other light sources are
fluorescent ceiling lights. The BOE Shield-Bot should be able to roam toward the
desk lamp and play a tone when its under it. This application should use the
charge transfer circuits from Building the Photosensitive Eyes on page 189.
Question Solutions
1.
2.
3.
4.
5.
The amount of current it allows to pass into its collector and out through its base.
The phototransistors collector and emitter terminals are connected to leads.
The lead thats closer to the flat spot is the emitter. The lead thats further away
from the flat spot is the collector.
The wavelength of red is closer to the wavelength of infrared, so it should be
more sensitive to red.
VA3 increases with more light.
6.
7.
8.
9.
Exercise Solutions
1.
2.
3.
4.
5.
6.
V = I R = 0.001 A 2000 = 2 V.
V = I R I = V R = 4.5 2000 = 0.00225 A = 2.25 mA.
105 10 with 5 zeros appended and multiplied by 1 pF. 1,000,000 1 pF = (1
106) (1 1012) F = 1 106 F = 1 F.
It would be long tDecay = rcTime(7);
ndShade = tRight / (tLeft+tRight) - 0.5 = 1001 (1001 + 1001) 0.5 =
0.5 0.5 = 0.
Solution:
// Repeat 50 times
Project Solutions
1.
/*
* Robotics with the BOE Shield - Chapter 6, Project 1
* Chirp when light is above threshold. Will require updating value of
* threshold & retesting under bright light to get to the right value.
*/
void setup()
{
tone(4, 3000, 1000);
delay(1000);
}
void loop()
{
if(volts(A3) > 3.5)
{
tone(4, 4000, 50);
delay(100);
}
}
2.
The solution for this one is to make a copy of LightSeekingShieldBot, and add one
command to the loop function: ndShade = -ndShade. Add it right before the
ifelse statement. Then, instead of indicating shade to turn away from, it
indicates bright light to turn away from. Another approach would be to use an
ndLight calculation that equals tLeft / (tLeft + tRight). You would have
to search/replace ndShade with ndLight in the sketch.
and 400 for bright light, use the value 400 + (4000 400) = 400 + 900 =
1300. Dont just use 1300, its just an example; figure out the value for your
conditions.
3.
/*
* Robotics with the BOE Shield - Chapter 6, Project 3
* Roams toward light and away from shade.
*/
Servo servoLeft;
Servo servoRight;
void setup()
{
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
float tLeft = float(rcTime(8));
float tRight = float(rcTime(6));
}
long rcTime(int pin)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(5);
pinMode(pin, INPUT);
digitalWrite(pin, LOW);
214 Robotics with the BOE Shield-Bot
Charge capacitor
..by setting pin ouput-high
..for 5 ms
Set pin to input
..with no pullup
//
//
//
//
}
// maneuver function
void maneuver(int speedLeft, int speedRight, int msTime)
{
servoLeft.writeMicroseconds(1500 + speedLeft);
// Left servo speed
servoRight.writeMicroseconds(1500 - speedRight); // Right servo speed
if(msTime==-1)
// if msTime = -1
{
servoLeft.detach();
// Stop servo signals
servoRight.detach();
}
delay(msTime);
// Delay for msTime
}
Infrared
LED
Infrared Receiver
Infrared
LED
The infrared receivers in this chapter detect infrared light, similar to the phototransistors in
the last chapter. But, theres a differencethese infrared receivers are not just detecting
ambient light, but they are designed to detect infrared light flashing on and off very quickly.
The infrared LED that the BOE Shield-Bot will use as a tiny headlight is actually the same
kind you can find in just about any TV remote. The TV remote flashes the IR LED to send
messages to your TV. The microcontroller in your TV picks up those messages with an
infrared receiver like the one your BOE Shield-Bot will use.
IR Receiver
IR LED
The TV remote sends messages by flashing the IR LED very fast, at a rate of about 38 kHz
(about 38,000 times per second). The IR receiver only responds to infrared if its flashing at
this rate. This prevents infrared from sources like the sun and incandescent lights from
being misinterpreted as messages from the remote. So, to send signals that the IR receiver
can detect, your Arduino will have to flash the IR LED on/off at 38 kHz.
IR Interference: Some fluorescent lights do generate signals that can be detected by the IR
receivers. These lights can cause problems for your BOE Shield-Bots infrared headlights. One of
the things you will do in this chapter is develop an infrared interference sniffer that you can use
to test the fluorescent lights near your BOE Shield-Bot courses.
The light sensors inside many digital cameras, including some cell phones and webcams, can
all detect infrared light. By looking through a digital camera, we can see if an infrared LED
is on or off. These photos show an example with a digital camera and a TV remote. When
you press and hold a button on the remote and point the IR LED into the digital cameras
lens, it displays the infrared LED as a flashing, bright white light.
The pixel sensors inside the digital camera detect red, green, and blue light levels, and the
processor adds up those levels to determine each pixels color and brightness. Regardless of
whether a pixel sensor detects red, green, or blue, it detects infrared. Since all three pixel
color sensors also detect infrared, the digital camera display mixes all the colors together,
which results in white.
Infra means below, so infrared means below red.
The name refers to the fact that the frequency of infrared light waves is less than the frequency of
red light waves. The wavelength our IR LED transmits is 980 nanometers (abbreviated nm), and
thats the same wavelength our IR receiver detects. This wavelength is in the near-infrared range.
The far-infrared range is 2000 to 10,000 nm, and certain wavelengths in this range are used for
night-vision goggles and IR temperature sensing.
Parts List
(2) IR receivers
(2) IR LEDs (clear case)
(2) IR LED shield assemblies
(2) Resistors, 220 (red-red-brown)
(2) Resistors, 2 k (red-black-red)
(misc) Jumper wires
Gather the parts in the Parts List, using the drawings below to help identify the
infrared receivers, LEDs, and shield assembly parts.
5V
GND
Signal
Infrared LED
5V
GND
Signal
Infrared Receiver
Check the figure below to make sure you have selected infrared LEDs and not
phototransistors. The infrared LED has a taller and more rounded plastic dome,
and is shown on the right side of this drawing.
Phototransistor
Flatter on top
USE THIS
ONE!
Infrared LED
More rounded dome
Insert the infrared LED into the LED standoff base (the larger of the two pieces)
as shown below. The standoff is shaped to fit the flat side of the LED.
Make sure the IR LED snaps into the LED standoff.
Slip the short tube over the IR LEDs clear plastic case. The ring on one end of the
tube should fit right into the LED standoff with a little twist.
Left
anode
(+) lead
Right
anode
(+) lead
Here is an example of the three steps applied to the left IR LED (pin 9) and IR receiver (pin
10).
tone(9, 38000, 8);
delay(1);
int ir = digitalRead(10);
The tone actually lasts about 1.1 ms. Even though we would expect tone(9, 38000, 8) to
generate a 38 kHz signal for 8 ms, the signal actually only lasts for about 1.1 ms as of Arduino
software v1.0. Given the name tone, the function may have been designed for and tested with
audible tones. If played on a speaker, a 38 kHz tone will not be audible. Its in the ultrasonic
range, meaning its pitch is so high that its above the audible range for humans, which is about 20
Hz to 20 kHz.
The tone function generates a square wave that repeats its high/low cycle 38000 times per
second. Through experimentation with Arduino software v1.0, the author discovered that a
call to tone with a duration of 8 ms resulted in a 38 kHz square wave that lasted about 1.1
ms.
Remember that the tone function does its processing in the background. Since the IR
receiver needs a few tenths of a millisecond to respond to the 38 kHz signal, delay(1)
prevents the ir = digitalRead(10) call from copying the IR receivers output until its
ready.
The next sketch defines a function named irDetect with three parameters: one to specify
the IR LED pin, one to specify the IR receiver pin, and one to set the frequency for flashing
the IR LED.
int irDetect(int irLedPin, int irReceiverPin, long frequency)
This call passes 9 to the irLedPin parameter, 10 to irReceiverPin, and 38000 to the
frequency parameter. The function performs those three steps for infrared detection and
returns 1 if no object is detected, or 0 if an object is detected. That return value gets stored
in irLeft.
This sketch only tests the BOE Shield-Bots left IR detector. This helps simplify troubleshooting because you are focusing on only one of the two circuits. This is yet another
example of subsystem testing. After the subsystems check out, we can move to system
integration. But first, youve got to make sure to test and correct any wiring or code entry
errors that might have crept in.
1s no object
detected
0s object
detected
/*
* Robotics with the BOE Shield - TestLeftIR
* Display 1 if the left IR detector does not detect an object,
* or 0 if it does.
*/
void setup()
{
tone(4, 3000, 1000);
delay(1000);
pinMode(10, INPUT);
Serial.begin(9600);
}
void loop()
{
int irLeft = irDetect(9, 10, 38000);
Serial.println(irLeft);
delay(100);
}
// IR Object Detection Function
int irDetect(int irLedPin, int irReceiverPin, long frequency)
{
tone(irLedPin, frequency, 8);
// IRLED 38 kHz at least 1 ms
delay(1);
// Wait 1 ms
int ir = digitalRead(irReceiverPin); // IR receiver -> ir variable
delay(1);
// Down time before recheck
return ir;
// Return 1 no detect, 0 detect
}
Leave the BOE Shield-Bot connected to its programming cable, and open the
Serial Monitor when the sketch is done loading.
Place an object, such as your hand or a sheet of paper, about an inch (2 to 3 cm)
from the left IR object detector.
Verify that the Serial Monitor displays a 0 when you place an object in front of the
IR object detector, and a 1 when you remove the object.
If the Serial Monitor displays the expected values, go ahead and test the right IR
Object Detector (below). If not, go to the Troubleshooting section for help.
Modifying the sketch to test the right IR object detector is a matter of replacing irLeft with
irRight, and passing the correct pin numbers to the irDetect parameters to test the other
circuit. Heres a checklist of the changes:
Repeat the testing steps in this activity for the BOE Shield-Bots right IR object
detector.
If necessary, trouble-shoot any circuit or code entry errors.
In this activity, you will build and test indicator LEDs that will tell you if an object is detected
without the help of the Serial Monitor. This is handy if you are not near your computer, and
you need to troubleshoot your IR detector circuits.
You will also write a sketch to sniff for infrared interference from fluorescent lights. Some
fluorescent lights send signals that resemble the signal sent by your infrared LEDs. The
device inside a fluorescent light fixture that controls voltage for the lamp is called the ballast.
Some ballasts operate in the same frequency range of your IR detector, causing the lamp to
emit a 38.5 kHz infrared signal. When using IR object detection for navigation, ballast
interference can cause some bizarre BOE Shield-Bot behavior!
LED indicator circuits are similar to the ones you used with the whiskers. Make sure to be
careful about your cathodes and anodes when you connect them.
224 Robotics with the BOE Shield-Bot
Parts List
(2) Red LEDs
(2) Resistors, 220 (red-red-brown)
(misc) Jumper wires
anode
(+) leads
Reconnect the programming and power cables, and set the Power switch to 1.
Create, save, and run the sketch TestBothIrAndIndicators.
Verify that the speaker makes a clear, audible tone after the sketch loads.
Use the Serial Monitor to verify that the Arduino still receives a zero from each IR
detector when an object is placed in front of it.
Verify that when you place an object in front of the left IR detector, the left (pin 8)
red LED lights up.
Repeat that test for the right IR detector and pin 7 LED.
/*
* Robotics with the BOE Shield TestBothIrAndIndicators
* Test both IR detection circuits with the Serial Monitor. Displays 1 if
* the left IR detector does not detect an object, or 0 if it does.
* Also displays IR detector states with indicator LEDs.
*/
void setup()
{
tone(4, 3000, 1000);
delay(1000);
}
void loop()
{
int irLeft = irDetect(9, 10, 38000);
int irRight = irDetect(2, 3, 38000);
digitalWrite(8, !irLeft);
digitalWrite(7, !irRight);
Serial.print(irLeft);
Serial.print(" ");
Serial.println(irRight);
}
// IR Object Detection Function
int irDetect(int irLedPin, int irReceiverPin, long frequency)
{
tone(irLedPin, frequency, 8);
// IRLED 38 kHz for at least 1 ms
delay(1);
// Wait 1 ms
int ir = digitalRead(irReceiverPin); // IR receiver -> ir variable
delay(1);
// Down time before recheck
return ir;
// Return 1 no detect, 0 detect
}
Inside TestBothIrAndIndicators
After the two calls to the irDetect function, irLeft and irRight each store a 1 if an object
is not detected, or 0 if an object is detected. The sketch could have used an ifelse
statement to turn the indicator lights on/off depending on the value of irLeft and irRight.
But, there are other ways!
This approach uses the values of irLeft and irRight directly. Theres only one catch:
when (for example) irLeft stores 0, we want its red indicator LED to turn on, and if it
stores 1, we want its LED to turn off. Since 1 makes the indicator LED turn on and 0 turns it
off, using digitalWrite(8, irLeft) would give us the opposite of what we want. So, the
sketch uses the not operator (!) to invert the value that irLeft stores. Now,
digitalWrite(8, !irLeft) inverts the value of IrLeft so that when it stores a zero, the
LED turns on, and when it stores 1, the LED turns off. The same technique is applied for the
right IR detector and indicator LED.
The Not (!) Operator inverts all the binary values in a variable. Theres more going on with
digitalWrite(8, !irLeft) here than meets the eye. Youre probably used to passing the
digitalWrite functions value parameter either HIGH (constant for 1) to turn the light on, or
LOW (constant for 0) to turn the light off. When you use variables, the digitalWrite function
uses the variables rightmost binary digit: 1 to turn the light on, or 0 to turn the light off. So, when
irLeft is 0 (object detected) !irLeft changes its binary value 00000000 to 11111111. It has a 1
in the rightmost digit, so the light turns on. When irLeft is 1, its really binary 00000001. The
!irLeft statement results in binary 11111110. Rightmost digit is 0, so the light turns off.
Make sure the battery pack is connected to your Arduino and the BOE Shields
Power switch is set to 1.
Unplug your BOE Shield-Bot from the programming cable, and test again with
objects in the area where you plan to make it navigate.
Test the detection range with different colored objects. What colors, materials,
and surface textures can it detect only at closest range? What can it detect from
farther away?
You might have found that your BOE Shield-Bot said it detected something even though
nothing was in range. That may mean a nearby light is generating some IR light at a
frequency close to 38.5 kHz. It might also mean that direct sunlight streaming through a
window is causing false detections. If you try to have a BOE Shield-Bot contest or
demonstration near one of these light sources, your infrared systems could end up
performing very poorly! So, before any public demo, make sure to check the prospective
navigation area with this IR interference sniffer sketch ahead of time.
The concept behind this sketch is simple: dont transmit any IR through the IR LEDs, just
monitor to see if any IR is detected. If IR is detected, sound the alarm using the
piezospeaker.
You can use a handheld remote for just about any piece of equipment to generate IR
interference. TVs, VCRs, CD/DVD players, and projectors all use the same type of IR detectors
you have on your BOE Shield-Bot right now. So, the remotes you use to control these devices all
use the same kind of IR LED thats on your BOE Shield-Bot to transmit messages to your TV, VCR,
CD/DVD player, etc. All youll have to do to generate IR interference is point the remote at your
BOE Shield-Bot and repeatedly press/release one of the remotes buttons.
one of its buttons. If the BOE Shield-Bot responds by sounding the alarm, you
know your IR interference sniffer is working.
/*
* Robotics with the BOE Shield - IrInterferenceSniffer
* Test for external sources of IR interference. If IR interference is
* detected: Serial Monitor displays warning, piezospeaker plays alarm,
* and indicator lights flash.
*/
void setup()
{
tone(4, 3000, 1000);
delay(1000);
pinMode(10, INPUT);
pinMode(3, INPUT);
pinMode(8, OUTPUT);
pinMode(7, OUTPUT);
//
//
//
//
Left IR Receiver
Right IR Receiver
Left indicator LED
Right indicator LED
Serial.begin(9600);
}
void loop()
{
int irLeft = digitalRead(10);
int irRight = digitalRead(3);
Disconnect your BOE Shield-Bot from its programming cable, and point it at any
fluorescent light near where you plan to operate it.
Especially if you get frequent alarms, turn off that fluorescent light before trying
to use IR object detection under it.
Robotics with the BOE Shield-Bot 229
If the source turns out to be sunlight streaming in through a window, close the
blinds and retest, or move your obstacle course to a location thats well away
from the window.
Always use this IrInterferenceSniffer to make sure that any area where you are using the BOE
Shield-Bot is free of infrared interference.
Parts List:
(2)
(2)
(2)
(2)
First, lets use one of the red LEDs to see the difference that a resistor makes in how brightly
an LED glows. All we need to test the LED is a sketch that sends a high signal to the LED.
}
void loop()
{
}
Replace the 220 resistor that goes from pin 8 to the right LEDs cathode with a
470 resistor. Note now how brightly the LED glows.
Repeat for a 1 k resistor.
Repeat once more with a 4.7 k resistor.
Replace the 4.7 k resistor with the 220 resistor before moving on to the next
portion of this activity.
Explain in your own words the relationship between LED brightness and series
resistance.
We now know that less series resistance will make an LED glow more brightly. A reasonable
hypothesis would be that brighter IR LEDs can make it possible to detect objects that are
further away.
With a ruler, measure the furthest distance from the IR LED that a sheet of paper
can be detected when using 2 k resistors, and record your data in the next table.
Replace the 2 k resistors that connect pin 2 and pin 9 to the IR LED anodes with
4.7 k resistors.
Determine the furthest distance at which the same sheet of paper is detected, and
record your data.
Repeat with 1 k resistors, 470 resistors, and 220 resistors. (For the smaller
resistor values, they may end up making the detectors so sensitive that they see
the table surface in front of your robot. If that happens, set the robot on the edge
of the table with the IR detectors pointing off the end and try the distance
measurements again.)
Detection Distances vs. Resistance
IRELD Series Resistance ()
4700
2000
1000
470
220
Before moving on to the next activity, restore your IR object detectors to their
original circuit, with 2 k resistors in series with each IR LED.
Also, before moving on, make sure to test this last change with
TestBothIrAndIndicators to verify that both IR object detectors are working
properly.
An interesting thing about these IR detectors is that their outputs are just like the whiskers.
When no object is detected, the output is high; when an object is detected, the output is low.
In this activity, the example sketch RoamingWithWhiskers is modified so that it works with
the IR detectors; all it takes is few simple modifications. Here are the steps:
1.
2.
3.
4.
5.
Replace all instances of wLeft with irLeft and wRight with irRight.
Open RoamingWithWhiskers.
Save it as RoamingWithIr.
Modify it so that it matches the sketch below.
Save the sketch and run it on the Arduino.
Disconnect the BOE Shield-Bot from its programming cable.
Reconnect the battery pack and move the 3-position switch to position 2.
Place your BOE Shield-Bot somewhere where it can roam and avoid obstacles.
Verify that it behaves like RoamingWithWhiskers (aside from the fact that theres
no contact required).
6.
/*
* Robotics with the BOE Shield - RoamingWithIr
* Adaptation of RoamingWithWhiskers with IR object detection instead of
* contact switches.
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
// Built-in initialization block
{
pinMode(10, INPUT); pinMode(9, OUTPUT);
// Left IR LED & Receiver
pinMode(3, INPUT); pinMode(2, OUTPUT);
// Right IR LED & Receiver
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
int irLeft = irDetect(9, 10, 38000);
int irRight = irDetect(2, 3, 38000);
if((irLeft == 0) && (irRight == 0))
{
backward(1000);
turnLeft(800);
}
else if(irLeft == 0)
{
backward(1000);
turnRight(400);
}
else if(irRight == 0)
{
backward(1000);
turnLeft(400);
}
else
{
forward(20);
}
}
int irDetect(int irLedPin, int irReceiverPin, long frequency)
{
tone(irLedPin, frequency, 8);
// IRLED 38 kHz for at least 1 ms
delay(1);
// Wait 1 ms
int ir = digitalRead(irReceiverPin); // IR receiver -> ir variable
delay(1);
// Down time before recheck
return ir;
// Return 1 no detect, 0 detect
}
void forward(int time)
{
servoLeft.writeMicroseconds(1700);
servoRight.writeMicroseconds(1300);
delay(time);
}
// Forward function
// Backward function
You can reduce all your maneuver durations to 20 ms, which means your sketch will check
and recheck for objects almost 50 times per second. (Its actually a little slower, more like 40
times per second, because the code execution and IR detection all takes time too.) As your
BOE Shield-Bot navigates, it will execute a series of small turns to avoid an obstacle before it
ever runs into it. With that approach, it never turns further than it has to, and it can neatly
find its way around obstacles and successfully navigate more complex courses. After
experimenting with this next sketch, youll likely agree that its a much better way for the
BOE Shield-Bot to roam.
Create, save, and run the sketch FastIrRoaming, then test it with the same
obstacles you used from the previous activity.
/*
* Robotics with the BOE Shield - FastIrRoaming
* Adaptation of RoamingWithWhiskers with IR object detection instead of
* contact switches
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
void setup()
// Built-in initialization block
{
pinMode(10, INPUT); pinMode(9, OUTPUT);
// Left IR LED & Receiver
pinMode(3, INPUT); pinMode(2, OUTPUT);
// Right IR LED & Receiver
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
}
int irDetect(int irLedPin, int irReceiverPin, long frequency)
{
tone(irLedPin, frequency, 8);
// IRLED 38 kHz for at least 1 ms
delay(1);
// Wait 1 ms
int ir = digitalRead(irReceiverPin); // IR receiver -> ir variable
delay(1);
// Down time before recheck
return ir;
// Return 1 no detect, 0 detect
}
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);
// Left servo speed
236 Robotics with the BOE Shield-Bot
Your Turn
Up until now, your robot has mainly been programmed to take evasive maneuvers when an
object is detected. There are also applications where the BOE Shield-Bot must take evasive
action when an object is not detected. For example, if the BOE Shield-Bot is roaming on a
table, its IR detectors might be looking down at the table surface. The sketch should make it
continue forward so long as both IR detectors can see the surface of the table.
Recommended Materials:
(1) Roll of black vinyl electrical tape, (19 mm) wide, or black tempera paint and brush.
(1) Sheet of white poster board, 22 x 28 in (56 x 71 cm).
A sheet of white poster board with a border made of electrical tape or black tempera paint
makes for a handy way to simulate the drop-off presented by a table edge, with much less
risk to your BOE Shield-Bot.
238 Robotics with the BOE Shield-Bot
Build a course like one shown below. For electrical tape, make a small test patch
on a piece of paper first to make sure the infrared detectors cannot see a
reflection from it. If it does not reflect infrared, use three strips edge to edge on
the poster board with no paper visible between the strips.
22 (56 cm)
22 (56 cm)
If you are using older IR LEDs, the BOE Shield-Bot might actually be having problems with
being too nearsighted. Here are some remedies that will increase the BOE Shield-Bots
sensitivity to objects and make it more far sighted:
For the most part, programming your BOE Shield-Bot to navigate around a tabletop without
going over the edge is a matter of adjusting the if...else if...else statements from
FastIrRoaming. First of all, instead of backing up, it will need to go forward 20 ms at a time
when it sees objects with both detectors. It will also need to turn toward objects instead of
away from them, and it will need to turn for more than 20 ms when it sees the drop-off. 375
ms turns seem to work well, but it will be up to you to adjust that value for best
performance.
/*
* Robotics with the BOE Shield - AvoidTableEdge
* Adaptation of FastIrRoaming for table edge avoidance
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
}
int irDetect(int irLedPin, int irReceiverPin, long frequency)
{
tone(irLedPin, frequency, 8);
// IRLED 38 kHz for at least 1 ms
delay(1);
// Wait 1 ms
int ir = digitalRead(irReceiverPin); // IR receiver -> ir variable
delay(1);
// Down time before recheck
return ir;
// Return 1 no detect, 0 detect
}
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);
// Left servo speed
servoRight.writeMicroseconds(1500 - speedRight); // Right servo speed
if(msTime==-1)
// If msTime = -1
{
servoLeft.detach();
// Stop servo signals
Robotics with the BOE Shield-Bot 241
//From AvoidTableEdge
{
maneuver(-200, -200, 20);
else if(irLeft == 0)
else if(irLeft == 0)
{
maneuver(200, -200, 20);
else if(irRight == 0)
else if(irRight == 0)
{
maneuver(-200, 200, 20);
else
else
{
maneuver(200, 200, 20);
}
}
In response to else if(irLeft == 0), FastIrRoaming turns right for 20 ms, taking a step
toward avoiding an obstacle on the left with maneuver(200, -200, 20). AvoidTableEdge
instead turns away from a drop-off that must be on its right. Thats because the first if
statement where both IR detectors could see the table was not true. If it was, the ifelse
ifelse statement would not have made it to this else if condition. It means the left IR
242 Robotics with the BOE Shield-Bot
detector does see the table, but the right one does not. So the code makes the robot turn left
for 0.375 seconds with maneuver(-200, 200, 375). This should make it avoid a drop-off
that must have been detected on the right.
In response to else if (irRight == 0), FastIrRoaming turns left for 20 ms, taking an
incremental step toward avoiding an obstacle on the right. At this point in the ifelse
ifelse statement, we know that the right IR detector does see an object, but the left one
does not. Again, thats because, it would have handled the condition where both IR detectors
see the table with the first if statement. So, the left IR detector does not see the object and
turns right for 0.375 seconds with maneuver(200, -200, 375).
Your Turn
The 375 ms turns to avoid the table edge can be adjusted for different applications. For
example, if the BOE Shield-Bot is supposed to hug the edge of the table, smaller turns might
be useful. In a contest where the BOE Shield-Bot is supposed to push objects out of an area, a
larger turn (but not too large) would be better so that it zigzags back and forth across the
table.
You can modify the code to make shallower turns by using a smaller msTime parameter
value in the maneuver function calls. For example, if you change the 375 in
maneuver(-200, 200, 375) to 300, it will make shallower left turns. If you change it to
450, it will make sharper left turns.
Modify AvoidTableEdge so that it closely follows the edge of the simulated dropoff course. Adjust the msTime parameter values in calls to maneuver to make the
BOE Shield-Bot execute smaller turns when it sees the drop-off. How small can
you make the turn before it tries to fall off?
Experiment with pivoting away from the table edge to make the BOE Shield-Bot
roam inside the perimeter instead of following the edge.
Chapter 7 Summary
This chapter focused on using a pair of infrared LED emitters and receivers to broadcast and
detect reflections of 38.5 kHz modulated infrared light. Using this sensor system for robot
navigation used these concepts and skills:
Electronics
What an infrared LED is, and how to identify its anode and cathode
What a modulated infrared receiver is, and how to use it in a microcontroller
circuit
What effect resistor values have on LEDs when connected in series
Robotics with the BOE Shield-Bot 243
Programming
How to use the familiar Arduino tone function for a new purpose: to modulate an
infrared light signal, instead of control the pitch of a sound
How to repurpose an existing sketch from one sensor system for use with a
different sensor system, so long as both sensors have a similar output signal
How to repurpose an existing sketch designed for a specific behavior (avoiding
obstacles) to a new behavior (detecting a drop-off)
Robotics Skills
Using a pair of infrared emitters and receivers to detect objects for non-contact
autonomous sensor navigation
Engineering Skills
Chapter 7 Challenges
Questions
1.
2.
3.
4.
What is the frequency of the signal sent by tone(9, 38000, 8)? How long does
this call send the signal for? What I/O pin does the IR LED circuit have to be
connected to in order to broadcast this signal?
What has to happen after the tone command to find out if the IR receiver detects
reflected infrared?
What does it mean if the IR detector sends a low signal? What does it mean when
the detector sends a high signal?
What happens if you change the value of a resistor in series with a red LED?
What happens if you change the value of a resistor in series with an infrared
LED??
Exercises
1.
2.
Projects
1.
2.
3.
Design a BOE Shield-Bot application that sits still until you wave your hand in
front of it, then it starts roaming.
Design a BOE Shield-Bot application that slowly rotates in place until it detects an
object. As soon as it detects an object, it locks onto and chases the object. This is
a classic SumoBot behavior.
Design a BOE Shield-Bot application that roams, but if it detects infrared
interference, it sounds the alarm briefly, and then continues roaming. This alarm
should be different from the low battery alarm.
Question Solutions
1.
2.
3.
4.
38 kHz is the frequency of the signal. Although the 8 in tone calls ms parameter
calls for a signal that lasts for 8 ms, the book said that testing indicated it lasted
for 1.1 ms. The IR LED must be connected to the digital pin 8 socket for the tone
signal to make it flash on/off at 38 kHz.
The code has to pause for 1 ms with delay(1) and then store the IR value by
copying whatever digitalRead returns to a variable; for example, delay(1)
followed by irLeft = digitalRead(10).
A low signal means that 38 kHz IR was detected, which means that the 38 kHz
flashing light the IR LED transmitted must have reflected off an object in its path.
In other words: object detected. A high signal means no 38 kHz IR was detected,
so, there wasnt any object to reflect the IR LEDs light. In other words: object not
detected.
For both red and infrared LEDs, a smaller resistor will cause the LED to glow
more brightly. A larger resistor results in dimmer LEDs. In terms of results,
brighter IR LEDs make it possible to detect objects that are farther away. But it
can also have annoying side effects, like seeing the reflection of the surface it is
rolling on and misinterpreting it as an obstacle.
Exercise Solutions
1.
2.
Lets remove the left IR detector from the code. Comment out int irLeft =
digitalRead(10) by placing // to its left. Then, change if((irLeft == 0) ||
(irRight == 0)) to if(irRight == 0).
Remember that linear wheel speed control is in the -100 to 100 range. So -75
should be a little slower than full speed backward. With that in mind, try
changing maneuver(-200, -200, 250) to maneuver(-75, -200, 250).
Project Solutions
1.
One approach would be to add this code at the end of the setup function in
FastIrRoaming. It will wait until an object is detected before allowing the code to
move on to the roaming code in the loop function.
int irLeft = 1;
int irRight = 1;
2.
Rotating in place and waiting to see an object is almost the same as project 1. The
trick is to call the maneuver function to make it rotate slowly. Remember that
the maneuver functions msTime parameter just dictates low long the maneuver
routine prevents another maneuver from making the BOE Shield-Bot do
something different. You can instead use it to start a maneuver in a set-it-and
forget-it fashion. Then, let the code continue to the loop that monitors the IR
detectors until it detects an object.
int irLeft = 1;
int irRight = 1;
void loop()
{
3.
The example above goes forward for 20 ms if it does not see the object. Optionally,
it could spin in place until it sees an object and then chase after it again. For this,
you could place the code that spins and waits for a detection in a function. Then,
your code could call that function from the end of the setup function to wait until it
sees something, and also from the else statement in the loop function for when it
cant see the object any more.
The key to solving this problem is to combine FastIrRoaming and the tone portion
of IrInterferenceSniffer in a single sketch. This example also turns on the lights
while it sounds the alarm. One note, the compiler in the Arduino software wont let
you declare a variable twice in the same code block. So, in the loop function, the
first time the irLeft and irRight variables are used, they are declared with int.
The second time within the loop, they are just reused for detection (without using
int to declare them) because they have already been declared. Modified setup and
loop functions are shown here:
void setup()
// Built-in initialization block
{
pinMode(10, INPUT); pinMode(9, OUTPUT);
// Left IR LED & Receiver
pinMode(3, INPUT); pinMode(2, OUTPUT);
// Right IR LED & Receiver
pinMode(8, OUTPUT); pinMode(7, OUTPUT);
// LED indicators -> output
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
// Sniff
int irLeft = digitalRead(10);
int irRight = digitalRead(3);
// Repeat 5 times
// Turn indicator LEDs on
// Sound alarm tone
// 10 ms tone, 10 between tones
// Turn indicator LEDs off
// Roam
irLeft = irDetect(9, 10, 38000);
irRight = irDetect(2, 3, 38000);
// Backward 20 milliseconds
// If only left side detects
// Right for 20 ms
// If only right side detects
// Left for 20 ms
// Otherwise, no IR detects
// Forward 20 ms
If the circuit from the last chapter is still built on your BOE Shield-Bot, make sure
your IR LEDs are using the 2 k resistors.
If you already disassembled the circuit from the previous chapter, repeat the
steps in Chapter 7, Activity 1 on page 218.
Also, make sure to use TestBothIrAndIndicators to verify that the system is
working properly.
If you are working in different lightging condistions than you were before, use
the IR Interference Sniffer to check for problems.
The graph shows that the IR detector is most sensitive at 38 kHzits peak sensitivity at
the top of the curve. Notice how quickly the curve drops on both sides of the peak. This IR
detector is much less sensitive to IR signals that flash on/off at frequencies other than 38
kHz. Its only half as sensitive to an IR LED flashing at 40 kHz as it would be to 38 kHz
signals. For an IR LED flashing at 42 kHz, the detector is only 20% as sensitive. The further
from 38 kHz an IR LEDs signal rate is, the closer the IR receiver has to be to an object to see
that IR signals reflection.
The most sensitive frequency (38 kHz) will detect the objects that are the farthest away,
while less-sensitive frequencies can only detect closer objects. This makes rough distance
detection rather simple: pick some frequencies, then test them from most sensitive to least
sensitive. Try the most sensitive frequency first. If an object is detected, check and see if the
next-most sensitive frequency detects it. Depending on which frequency makes the reflected
infrared no longer visible to the IR detector, your sketch can infer a rough distance.
The next diagram shows an example of how the BOE Shield-Bot can test for distance using
frequency. Note this diagram is not to scale: the detection range only covers a short
distance, and begins a few centimeters away from the robot.
In this example, an object is in Zone 3. That means that the object can be detected when
38000 and 39000 Hz is transmitted, but it cannot be detected with 40000, 41000, or 42000
Hz. If you were to move the object into Zone 2, then the object would be detected when
38000, 39000, and 40000 Hz are transmitted, but not with 41000 or 42000 Hz.Z
The irDistance function from the next sketch performs distance measurement using the
technique shown above. Code in the main loop calls irDistance and passes it values for a
pair of IR LED and receiver pins. For example, the loop function uses int irLeft =
irDistance(9, 10) to check distance to an object in the left side of the BOE Shield-Bots
detection zone field of vision.
// IR distance measurement function
The irDetect function returns a 1 if it doesnt see an object, or a zero if it does. The
expression distance += irDetect(irLedPin, irReceivePin, f) counts the number of
times the object is not detected. After the for loop is done, that distance variable stores
the number of times the IR detector did not see an object. The more times it doesnt see an
object, the further away it must be. So, the distance value the function returns represents
the zone number in the drawing above.
Robotics with the BOE Shield-Bot 251
This screen capture shows some detection zone measurements from DisplayBothDistances
in the Serial Monitor. Though theres fluctuation in the values, commonly called noise, what
matters is that the numbers match, or are off by only 1, in each pair of measurements.
Right distances
Left distances
If it turns out that the detection range of one side is twice as far as the other (or more), check
the resistors connected to the IR LEDs. You may have a mismatch there; make sure both
resistors are 2 k (red-black-red). If there isnt a mismatch, try adjusting IR LED resistor
values until the detection ranges of both sides are in the same neighborhood.
/*
* Robotics with the BOE Shield - DisplayBothDistances
* Display left and right IR states in Serial Monitor.
* Distance range is from 0 to 5. Only a small range of several cm
* in front of each detector is measureable. Most of it will be 0 (too
* close) or 5 (too far).
*/
void setup()
{
tone(4, 3000, 1000);
delay(1000);
}
void loop()
{
int irLeft = irDistance(9, 10);
int irRight = irDistance(2, 3);
Serial.print(irLeft);
Serial.print(" ");
Serial.println(irRight);
delay(100);
}
// IR distance measurement function
int irDistance(int irLedPin, int irReceivePin)
{
int distance = 0;
for(long f = 38000; f <= 42000; f += 1000) {
distance += irDetect(irLedPin, irReceivePin, f);
}
return distance;
}
// IR Detection function
int irDetect(int irLedPin, int irReceiverPin, long frequency)
{
tone(irLedPin, frequency, 8);
// IRLED 38 kHz for at least 1 ms
delay(1);
// Wait 1 ms
Robotics with the BOE Shield-Bot 253
Try measuring the detection range for objects with different colors and textures.
Which colors and surfaces are easiest to detect? Which are most difficult?
For a BOE Shield-Bot to follow a leader-object, it has to know the rough distance to the
leader. If the leader is too far away, the sketch has to be able to detect that and move the
BOE Shield-Bot forward. Likewise, if the leader is too close, the sketch has to detect that and
move the BOE Shield-Bot backward. The purpose of the sketch is to make the BOE ShieldBot maintain a certain distance between itself and the leader-object.
Our machine is the BOE Shield-Bot. The measured value we want it to maintain is the
distance to the leader-object, with a set point of 2 (for zone 2). The machines processor is
the Arduino. The IR LED/receiver pairs are the sensors that take distance value
measurements to the leader-object. If the measured distance is different from the set-point
distance, the servos are the mechanical actuators that rotate to move the BOE Shield-Bot
forward or backward as needed.
This block diagram could apply to either the left IR distance sensor and servo output or the
right. In fact, your code will maintain two identical control loops, one for each side of the
BOE Shield-Bot. Lets walk through this example.
In the upper left, the set point = 2; we want the BOE Shield-Bot to maintain a zone-2 distance
between itself and its leader-object. Below that, the measured distance is zone 4, so the
leader-object is too far away. The arrows towards the symbols in the circle (called a
summing junction) indicate that you add (+) the set point and subtract (-) the measured
distance together to get the error, in this case 2 4 = -2.
Next, the error value feeds into the top squarean operator block. This block shows that the
error gets multiplied by -50, a proportionality constant (Kp). In this example, the operator
block gives us -2 -50 = 100, so 100 is the output. In a sketch, this output value gets passed
to the maneuver function. It turns the servo full speed forward to move the BOE Shield bot
closer to the leader-object.
The next block diagram shows another example. This time, the measured distance is 1,
meaning the leader-object is too close. So, the error is 1, and 150 = -50. Passing -50 to the
maneuver function turns the servo half-speed in reverse, backing the BOE Shield-Bot away
from the leader-object.
The next time through the loop, the measured distance might change, but thats okay.
Regardless of the measured distance, this control loop will calculate a value that will cause
the servo to move to correct any error. The correction is always proportional to the error.
The two calculations involved:
set point measured distance = error; error x Kp = output for maneuver
can be easily combined and re-ordered into one expression for your sketch:
If you want to take a look at the sketch in detail, see How FollowingShieldBot Works on page
258.
To prove that the proportional control loop responds correctly to all six measured distances,
fill in the table below. The block diagrams have been solved for two of the six conditions, so
youve only got four to try.
Measured
Distance
Set
Point
Too close
Just right
Condition
Error
(set point
measured)
Output
(Kp error)
Maneuver
Result
21=1
2 50 = -50
Slow reverse
2 4 = -2
-2 -50= -100
Fast forward
/*
* Robotics with the BOE Shield - FollowingShieldBot
* Use proportional control to maintain a fixed distance between
* BOE Shield-Bot and object in front of it.
*/
#include <Servo.h>
Servo servoLeft;
Servo servoRight;
// Target distances
// Proportional control constants
void setup()
// Built-in initialization block
{
pinMode(10, INPUT); pinMode(9, OUTPUT);
// Left IR LED & Receiver
pinMode(3, INPUT); pinMode(2, OUTPUT);
// Right IR LED & Receiver
tone(4, 3000, 1000);
delay(1000);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
int irLeft = irDistance(9, 10);
int irRight = irDistance(2, 3);
}
// IR distance measurement function
int irDistance(int irLedPin, int irReceivePin)
{
int distance = 0;
for(long f = 38000; f <= 42000; f += 1000) {
distance += irDetect(irLedPin, irReceivePin, f);
}
return distance;
}
// IR Detection function
int irDetect(int irLedPin, int irReceiverPin, long frequency)
{
tone(irLedPin, frequency, 8);
// IRLED 38 kHz for at least 1 ms
delay(1);
// Wait 1 ms
int ir = digitalRead(irReceiverPin); // IR receiver -> ir variable
delay(1);
// Down time before recheck
return ir;
// Return 1 no detect, 0 detect
}
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);
// Left servo speed
servoRight.writeMicroseconds(1500 - speedRight); // Right servo speed
if(msTime==-1)
// If msTime = -1
{
servoLeft.detach();
// Stop servo signals
servoRight.detach();
}
delay(msTime);
// Delay for msTime
}
// Target distances
// Proportional control constants
The convenient thing about declaring constants for these values is that you can change them
in one place, at the beginning of the sketch. The changes you make at the beginning of the
sketch will be reflected everywhere these constants are used. For example, by changing the
declaration for kpl from -50 to -45, every instance of kpl in the entire sketch changes
from -50 to -45. This is exceedingly useful for experimenting with and tuning the right and
left proportional control loops.
The first thing the loop function does is call the irDistance function for current distance
measurements and copies the results to the irLeft and irRight variables.
void loop()
{
int irLeft = irDistance(9, 10);
int irRight = irDistance(2, 3);
The next two lines of code perform those calculations for the right and left control loops, and
store the output-for-maneuver results to variables named driveLeft and driveRight.
// Left and right proportional control calculations
int driveLeft = (setpoint - irLeft) * kpl;
int driveRight = (setpoint - irRight) * kpr;
Now, driveLeft and driveRight are ready to be passed to the maneuver function to set
the servo speeds.
maneuver(driveLeft, driveRight, 20);
Since each call to maneuver lasts for 20 ms, it delays the loop function from repeating for 20
ms. The IR distance detection takes another 20 ms, so the loop repetition time is about 40
ms. In terms of sampling rate, that translates to 25 samples per second.
Sampling Rate vs. Sample Interval: The sample interval is the time between one sample and the
next. The sampling rate is the frequency at which the samples are taken. If you know one term,
you can always figure out the other: sampling rate = 1 sample interval.
If you are working on your own with one BOE Shield-Bot, you will be the leader!
The leader-object can be a book, bottle or even just your hand.
If you are part of a class with two or more BOE Shield-Bots, mount a paper panel
around the tail and both sides of a lead robot to make it more visible to the
shadow robots, like in the picture. If you are making a chain of shadow robots,
put a paper panel on each of them too.
Program the lead BOE Shield-Bot with SlowerIrRoamingForLeaderBot.
Program each shadow BOE Shield-Bot with FollowingShieldBot. Each shadow
robots IR LEDs should be pointing slightly to the left and right, and level with
horizontal (not up or down).
Place a shadow BOE Shield-Bot behind the lead BOE Shield-Bot or other leaderobject. The shadow BOE Shield-Bot should follow the leader at a fixed distance,
so long as it is not distracted by another object such as a hand or a nearby wall.
Servo servoLeft;
Servo servoRight;
void setup()
{
pinMode(10, INPUT);
pinMode(9, OUTPUT);
pinMode(3, INPUT);
pinMode(2, OUTPUT);
servoLeft.attach(13);
servoRight.attach(12);
}
void loop()
{
else if(irLeft == 0)
{
maneuver(40, -40, 20);
}
else if(irRight == 0)
{
maneuver(-40, 40, 20);
}
else
{
maneuver(40, 40, 20);
}
// Backward 20 milliseconds
// Right for 20 ms
// If only right side detects
// Left for 20 ms
// Otherwise, no IR detects
// Forward 20 ms
}
int irDetect(int irLedPin, int irReceiverPin, long frequency)
{
tone(irLedPin, frequency, 8);
// IRLED 38 kHz for at least 1 ms
delay(1);
// Wait 1 ms
Robotics with the BOE Shield-Bot 261
}
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);
// Left servo speed
servoRight.writeMicroseconds(1500 - speedRight); // Right servo speed
if(msTime==-1)
// If msTime = -1
{
servoLeft.detach();
// Stop servo signals
servoRight.detach();
}
delay(msTime);
// Delay for msTime
}
Repeat the control loop exercises from Activity #1 with the setpoint at zero. Can
any measured distance cause it to back up with a set point of zero?
Congratulations! You've made it to the end of the book. Now that you've mastered the
basics, you're ready to start exploring and adding capabilities with your BOE Shield-Bot!
Links to these ideas and more are available from the Arduino-Friendly Products page at
www.parallax.com/product/arduino.
A few BOE Shield-Bot projects are shown on the next two pages.
262 Robotics with the BOE Shield-Bot
Hardware Add-Ons
The Crawler Legs (left, #30055) and Tank Treads (right, #28106) give alternate modes of
locomotion to your Shield-Bot robot. You may run the same navigation code, but the ground
speed will be different so you may need to adjust the duration of different maneuvers.
Chapter 8 Summary
This chapter used the infrared IR LED/receiver pairs for simple distance detection, to make a
BOE Shield-Bot shadow vehicle. Now-familiar skills combined with some new concepts got
the job done:
Electronics
Programming
Robotics Skills
Engineering Skills
Reading a block diagram for a simple closed loop proportional control system,
with a summing junction and operator blocks
Understanding and using a closed loop control systems set point, error,
proportionality constant, and output value
More subsystem testing and troubleshooting
Chapter 8 Challenges
Questions
1.
2.
What would the relative sensitivity of the IR detector be if you use tone to send a
35 kHz signal? What is the relative sensitivity with a 36 kHz signal?
What keyword is used to declare a constant?
3.
4.
5.
6.
What statement is used to sweep through the list of frequencies used for IR
distance measurements?
Whats a summing junction?
In our closed loop proportional control block diagrams, what two values are used
to calculate the error term?
How do delays in the loop function contribute to setting the sampling rate?
Exercise
1.
Write a segment of code that does the frequency sweep for just four frequencies
instead of five.
1.
Write a sketch that allows you to hold your hand in front of the BOE Shield-Bot
and push it backwards and forwards with no turning.
1.
2.
3.
Project
Question Solutions
4.
5.
6.
Exercise Solution
1.
Just reduce the for statements condition from f <=42000 to f <= 41000.
Project Solution
1.
One quick and simple solution would be to average the driveLeft and
driveRight values in the FollowingShieldBot sketch. The resulting single value
can be applied both left and right speed parameters in the maneuver call.
void loop()
{
int irLeft = irDistance(9, 10);
int irRight = irDistance(2, 3);
delay(10);
Index
Index
For your convenience, this book is available as a free, searchable PDF download from the
#122-32335 product page at www.parallax.com.
-- decrement operator .............................. 34
! = not equal operator............................. 165
! NOT operator .........................................27
#include .................................................... 57
% modulus operator ................................. 27
&& boolean AND operator ....................... 31
* multiplication ......................................... 27
/ division ................................................... 27
/* and */ ................................................... 36
// line comment ........................................ 36
|| boolean OR operator............................ 31
+ addition .................................................. 27
++ increment operator........................ 34, 35
++ post increment ..................................... 34
< less than operator .................................. 31
== compare-equals operator .................... 31
> greater than operator ............................ 30
38 kHz ..................................................... 250
3-position switch....................................... 61
active-low output.................................... 152
actuators ................................................. 254
adding a library ......................................... 57
addition ..................................................... 27
ambient light........................................... 179
amperes .................................................. 186
amps ......................................................... 52
ANALOG IN sockets ................................... 50
analog sensor .......................................... 184
analog to digital conversion .................... 183
analogRead function ............................... 183
anode .......................................... 48, 51, 220
Arduino IDE software ................................ 16
Arduino language
analogRead function .................................. 183
byte .............................................................. 26
chaining else if .............................................. 32
set up ........................................................... 42
BOE Shield-Bot
assembly ...................................................... 76
orientation ............................................ 85, 103
Boe-Bot ....................................................... 7
Boe-Bot, retrofit to Arduino ....................... 9
boolean operators .................................... 31
brain ............................................................ 9
breadboard ............................................... 49
brownout .................................................. 89
buffer, serial.............................................. 96
building circuits ......................................... 51
bumper switches ...................... See whiskers
byte ........................................................... 28
byte variable type ..................................... 26
calling functions ...................................... 119
capacitance measurements .................... 189
capacitor ................................................. 189
Capek, Karel ................................................ 6
capitalization....................................... 18, 19
case sensitivity .................................... 18, 19
cathode ....................................... 48, 51, 220
centering servos........................................ 63
chain else if statements ............................ 32
char variable type ..................................... 24
character arrays ...................................... 136
charge transfer circuit............................. 191
chassis parts list ........................................ 12
Chrome and Codebender.......................... 20
circle.......................................................... 29
circuit diagrams
LEDs ............................................................. 51
LEDs with IR object detection .................... 225
photoresistor charge transfer .................... 190
phototransistor voltage .............................. See
piezospeaker ................................................ 90
prototyping area .......................................... 50
servos........................................................... 62
whisker LED test ........................................ 154
whisker switches ........................................ 149
Index
comparison of equals................................ 31
compiler .............................................. 21, 25
compound arithmetic operators .............. 34
computer buffer overruns ...................... 154
connected in parallel .............................. 192
connected in series ................................. 186
const declarations..................................... 37
constants ............................................ 29, 37
constrain function ................................... 204
continuous rotation servo
and writeMicroseconds ................................ 69
center calibration ......................................... 63
chassis mounting options ............................. 80
connecting to BOE Shield ............................. 61
parts of ......................................................... 60
PWM control ................................................ 66
run time ........................................................ 70
Servo library ................................................. 57
speed transfer curve .................................... 93
testing after assembly .................................. 86
troubleshooting ............................................ 88
definition ...................................................... 22
meaning ........................................................ 21
nesting calls ................................................ 154
long ........................................................... 28
long variable type ..................................... 27
LOW signal .............................................. 151
maneuver function ................................. 125
maneuvers in functions .......................... 123
map function........................................... 185
measurements, noise in.......................... 252
measurements, normalized differential . 197
measuring distance vs speed .................. 113
Mega ................................................... 10, 44
microfarad .............................................. 189
microsecond ............................................. 58
millisecond .......................................... 23, 58
modulation ............................................... 69
modulus .............................................. 27, 28
multiple conditions ................................... 31
multiplication ...................................... 27, 28
music note frequencies........................... 129
nanometers............................................. 178
Robotics with the BOE Shield-Bot 271
Index
parameter ................................................. 21
parentheses ............................ 18, 19, 21, 33
parentheses, empty .................................. 22
part numbers ............................................ 10
passing parameters in function call ........ 120
PBASIC ......................................................... 7
peak sensitivity, IR receiver..................... 250
phototransistor ....................................... 177
phototransistor charge transfer circuit ... 190
phototransistor voltage circuit................ 179
PI constant ................................................ 29
piano key frequencies ............................. 129
piezoelectric element................................ 89
piezospeaker ............................................. 89
piezospeaker circuit .................................. 90
PING Ultrasonic Distance Sensor ............ 263
pinMode function ............................. 52, 151
pitch function .......................................... 121
pivoting ................................................... 108
post increment operator ......................... 138
poster board............................................ 239
potentiometer........................................... 64
power supply............................................. 62
power switch ....................................... 51, 61
power terminals ........................................ 50
println........................................................ 23
processor, in control system ................... 254
programs ...................... See sketch, sketches
proportional control................................ 255
proportionality constant ......................... 255
prototyping area ....................................... 50
pulse train ................................................. 66
pulse width modulation ............................ 69
PWM ......................................................... 69
PWR_SEL ................................................... 10
QT Circuit ................................................ 192
ramping ................................................... 116
rcTime function ....................................... 194
rcTime voltage decay graph .................... 196
remote control ........................................ 264
remote controller .................................... 228
servo circuits............................................. 62
Servo library ............................................. 57
servo turret............................................. 263
set point, in control system .................... 254
setup ......................................................... 22
shadow vehicle ....................................... 254
signed numbers ........................................ 28
SimplyTronics .............................................. 7
single-pole, single throw switch ............. 151
sizeof function ........................................ 131
sketch examples
Index
variables .................................................... 24
array ........................................................... 128
declarations .................................................. 24
global vs local ............................................... 26
types ............................................................. 25
Vin ............................................................. 50
Vin sockets ................................................ 50
void setup.................................................. 22
voltage....................................................... 52
voltage decay graph ................................ 196
voltage regulator ....................................... 51
voltage, in Ohms Law ............................. 186
volts ........................................................... 52
volts function .......................................... 183
wheel rotation and direction .................. 104
wheels, attaching ...................................... 83
while loop...................................... 32, 34, 35
whisker LED test circuits ......................... 154
whisker sensor circuits ............................ 149
whiskers .................................................. 147
writeMicroseconds ................................... 57
us parameter range .................................... 111