Make An Arduino-Controlled Robot PDF
Make An Arduino-Controlled Robot PDF
Michael Margolis
Published by Make
Here is the terse version that returns the same thing (note the negation operator before the function call):
return !irSensorDetect(sensor);
Feel free to substitute your preferred style. Beginners should be reassured that there is no benefit in performance or code size in
using the terse form.
One or two more advanced programming concepts have been used where this makes the code easier to enhance. For example, long
lists of sequential constants use the
enum
declaration.
The
enum
keyword creates an enumeration; a list of constant integer values. All the enums in this book start from 0 and increase sequentially by
one.
For example, the list of constants associated with movement directions could be expressed as:
<�hei db>const int MOV_LEFT = 0
const int MOV_RIGHT = 1;
const int MOV_FORWARD = 2;
const int MOV_BACK = 3;
const int MOV_ROTATE = 4;
const int MOV_STOP = 5;
The following declares the same constants with the identical values:
enum {MOV_LEFT, MOV_RIGHT, MOV_FORWARD,
MOV_BACK, MOV_ROTATE, MOV_STOP};
Note
This icon signifies a tip, suggestion, or general note.
How to Contact Us
We have tested and verified the information in this book to the best of our ability, but you may find that features have changed (or even
that we have made a few mistakes!). Please let us know about any errors you find, as well as your suggestions for future editions, by
writing to:
We have a web page for this book, where we list errata, example code, and any additional information. You can access this page at:
http://shop.oreilly.com/product/0636920028024.do
To comment or ask technical questions about this book, send email to:
bookquestions@oreilly.com
For more information about our books, courses, conferences, and news, see our website at
http://www.oreilly.com
.
Find us on Facebook:
http://facebook.com/oreilly
Follow us on Twitter:
http://twitter.com/oreillymedia
Watch us on YouTube:
http://www.youtube.com/oreillymedia
Acknowledgments
Rob DeMartin, the business manager at Maker Media, was the driving force behind the botkits, which inspired the writing of this book.
Isaac Alexander and Eric Weinhoffer at Maker Media ran with the concept to make it a product. I thank them for testing the content of
the book to ensure that the projects and the hardware worked well together.
I am grateful to the Arduino community for contributing a wealth of free software, in particular, the IrRemote library from Ken Sherriff
that is used in the remote control chapter. I would also like to express my appreciation to Limor Fried (Ladyada) for creating the
hardware, software and online build notes for the motor shield used in this book.
Thanks also to DFRobot, the innovative company that designed the robot platforms and provided the exploded view drawings used in
the build chapters.
�war D
Mat Fordy at Cool Components (coolcomponents.co.uk) organized the robotics workshop that provided a testing ground for the
book’s projects. It was helpful and rewarding to work with the participants, each with a different level of experience, to build the robots
and see their pleasure in bringing their creations to life. Their feedback helped make the book content clear, practical and fun.
If I have achieved my goal of making the rich variety of technical topics in this book accessible to readers with limited electronics or
programming experience, then much of the credit goes to Brian Jepson. Brian, who was also my editor for the Arduino Cookbook,
was with me every step of the way. I thank him for his guidance: from his support and passion in beginning the project, to his editorial
expertise and application of his masterful communications skills right through to using his technical knowledge to test all the projects in
the book.
I would like to thank my entire family for listening to me explain the finer points of robotics during a week- long vacation in the early
stages of preparing this book. Four generations of my family were patient and constructive at times when they would have preferred
to be boating on the lake or walking in the woods.
Finally, this book would not be what it is without the contributions made by my wife, Barbara Faden. Her feedback on early drafts of
the manuscript helped shape the content. I am especially grateful for her support and patience in the wake of disruption created as I
wrangled with these two little robots to meet the book’s deadline.
Figure 1-8. Left and Right wheels turn backward, Robot moves Backward
Figure 1-8
shows that reversing the wheel rotation drives the robot backward.
sidebyside:
Figure 1-9. Left wheels turn forward, Right wheels reverse, Robot rotates Clockwise
Unlike a car (but a little like a tank), these robots can also rotate in place by driving the wheels on each side in
different directions. If the wheels on each side are spinning in opposite directions, the robot will rotate.
Figure 1-9
shows clockwise rotation.
Tools
These are the tools you need to assemble the robot chassis.
Phillips Screwdriver
A small Phillips screwdriver from your local hardware store.
Small long-nose or needle-nose pliers
For example, Radio Shack 4.5-inch mini long-nose pliers, part number 64-062 (see
Figure 1-10
) or Xcelite 4-inch mini long-nose pliers, model L4G.
Small wire cutters
For example, Radio Shack 5" cutters, part number 64-064 (
Figure 1-11
) or Jameco 161411
Soldering iron
For example, Radio Shack 640-2070 (
Figure 1-12
) or Jameco 2094143 are low cost irons suitable for beginners. But if you are serious about electronics, a good
temperature controlled iron is worth the investment, such as Radio Shack 55027897 or Jameco 146595.
Solder 22 AWG (.6mm) or thinner
For example, Radio Shack 640-0013 or Jameco 73605.
sidebyside:
Hardware Required
See
http://shop.oreilly.com/product/0636920028024.do
for a detailed parts list.
Tools listed in
Tools
AFMotor shield kit
Three 6 way 0.1" female headers
Three QTR-1A reflectance sensors
Stripboard, three 3 way 0.1" headers for line sensor mount
Ribbon Cable, 11-way or wider, cut with a sharp knife as follows:
One 10 inch length of 5 conductor ribbon cable for line sensors
Two 10 inch lengths of 3 conductor ribbon cable for edge sensors
Optional: 3 way 0.1" female header for optional charging circuit
Optional: 3 way 0.1" female header for optional wireless connection
Construction Techniques
This section provides an overview of the motor controller shield construction.
Soldering
Soldering is easy to do if you understand the basic principles and have a little practice. The trick for making a good solder joint is to
provide the right amount of heat to the parts to be soldered and use the right solder. 22 AWG solder (0.6mm or .025 inch) or thinner is
a good choice for soldering printed circuit boards. A 25-watt to 40-watt iron, ideally with temperature control, is best. The components
to be joined should be mechanically secure so they don't move while the solder is cooling—wires should be crimped around terminals
(see
Figure 4-11
and
Figure 4-12
). To make the joint, the tip of the iron should have good contact with all the components to be soldered. Feed a small amount of solder
where the iron is touching the parts to be joined. When the solder flows around the joint, remove the solder first and then the iron. The
connection should be mechanically secure and the joint shiny.
Note
The parts to the right of (as well as below) the board are packed with the shield, but the three 6-pin headers on the left are not
supplied with the standard shield. These headers are used to connect the sensors. These headers
are
included with the Maker Shed companion kits that go along with this book. You can also purchase female headers from Adafruit
and other suppliers.
The two Maker Shed kits can be found at
http://www.makershed.com/Bots_and_Bits_for_Bots_s/46.htm
. Look for either the Rovera 2W (Arduino-Controlled 2 Wheel Robotics Platform) or Rovera 4W (Arduino Controlled 4 Wheel
Robotics Platform).
Solder the smallest components first (
Figure 2-2
). The three small capacitors and two resistors are not polarized so you can insert them either way around.
Figure 2-2. Solder the Small Components
The resistor network (the long thin component with ten pins)
is
polarized—the end with the white dot goes to the left of the PCB (nearest to C1) as shown in
Figure 2-3
.
Figure 2-3. Solder the resistor network - the marker (circled) indicates correct orientation
The large capacitors, ICs, and LED are all polarized. The color of the components shown in the step-by-step assembly pictures on the
Adafruit site (you can find the link at the beginning of these build notes) may not match the components or layout for the parts you
received (particularly the capacitors) so carefully check that you have placed the correct value component in the correct orientation.
Figure 2-4
shows the layout for version 1.2 of the shield PCB. The kit includes two IC sockets for the L293D chips. As mentioned in the assembly
instructions on the Adafruit site, these are optional but if you like to play safe and want to use the sockets, solder them so the indent
indicating pin 1 matches the outline printed on the PCB.
Figure 2-4. Solder the rest of the polarized components
Figure 2-5
shows the board with all of the standard shield components (pushbutton, headers, screw terminals) soldered. The final assembly step
is to solder the three 6-pin female headers near the analog input pins. These headers are not included in the shield package or
mentioned in Adafruit's step-by-step build instructions, but are included with the Maker Shed kits.
Figure 2-5. Everything soldered except the sensor headers
Figure 2-6
shows all components including the sensor headers soldered. Trim the component pins (except the header pins that connect the
shield to the Arduino) on the underside of the board so they are clear of the Arduino when the shield is plugged onto the board. Locate
one of the jumpers supplied with the shield and plug this onto the pins marked
power jumper
—this connects the motor power input and the Arduino VIN (power input) together so both are fed from the batteries that you will be
wiring after you have built the robot chassis.
Figure 2-6. Shield with sensor headers
Figure 2-7
shows where all of the sensors and other external devices will be connected. The three pin female headers are not needed for some
of the projects but you will find it convenient to solder these to the shield at this time.
Figure 2-8
shows two styles of connections. On the left, you'll find the stripboard-based wiring scheme as described in
Making a Line Sensor Mount
. As you'll see in later chapters, you can experiment with a variety of m�igh� рounting methods, including the stripboard-based
one. The right side of
Figure 2-8
shows the wiring for separately connected sensors. As you read through the later chapters and experiment with various mounting
techniques, you'll use one or the other wiring schemes. Because you'll be using sockets and ribbon connectors to hook up the
sensors, you won't be locked into any particular connection scheme; you can mix and match.
Note
The left and right designation in the diagram refers to left and right from the robot's perspective, and the later chapters will
explain where to connect these.
Figure 2-7. Connections for devices covered in the chapters to come
Figure 2-8. Connection detail - stripboard wiring is shown on the left, individual jumpers shown on the right
Figure 2-10. Stripboard layout for mounting QTR-1A reflectance sensors for line following
To ensure that the mounting bolts don't short the tracks, you can either cut the tracks as shown in
Figure 2-10
(you will be cutting along the third column from the left, or the "C" column) or use insulated washers between the bolts heads and the
tracks.
Figure 2-11
shows how the header sockets are connected, and
Figure 2-12
shows the completed stripboard, with the ribbon cable connected. A ten inch length of cable is more than ample.
Figure 2-13
shows the other end of the ribbon connected to shield pins.
Next Steps
The next stage in building the robot is to assemble the chassis.
Chapter 3
covers the two-wheeled robot and
Chapter 4
is for the four-wheeled version.
Hardware Required
See
http://shop.oreilly.com/product/0636920028024.do
for a detailed parts list.
Tools listed in
Tools
The assembled electronics (see
Chapter 2, Building the Electronics
2WD Mobile Platform (two wheeled robot kit made by DFRobot)
Two 0.1uF ceramic capacitors
Two lengths of 3 conductor ribbon cable, two 3 way 0.1" headers for edge sensors
Optional: charging circuit resistors and diode, see detailed parts list
Mechanical Assembly
Lay Out the Chassis Parts
Figure 3-2
shows all of the parts contained in the 2WD chassis package. The three black brackets to the left of the figure are not needed for any
of the projects in this book.
Motor Assembly
Use two long bolts with lock washers and nuts, as shown in
Figure 3-5
, to attach each motor to the chassis lower plate. Tighten the nuts snugly but take care not to stress the plastic motor housing.
Note
Lock washers are used to prevent a nut from accidentally coming lose due to vibration. This is particularly important for attaching
the motor and switch. These washers have a split ring or serrations that apply extra friction when tightened.
If you find that things still come lose, don't overtighten the nuts; an effective solution is retighten the nut and apply a dab of nail
polish to the point where the threads emerge from the nut.
Figure 3-4
shows the motors in place with the nut seen on the upper right ready to be tightened.
Figure 3-4. Motors mounted on the chassis lower plate
Figure 3-5. Motor Assembly
Note
This robot is sometimes built with the sensor plate mounted at the opposite end of the chassis (furthest from the caster). You can
build yours however you like, but the orientation shown here enables the servo mounted distance scanner to be attached in the
front of the robot. Also, the sensor bracket in this location maximizes the distance between the wheels and the line sensors and
this improves line following sensitivity.
Figure 3-9
shows the underside of the chassis after mounting the sensor bracket. Note that the sensor bracket is attached to the bottom of the
chassis plate.
sidebyside:
Figure 3-8. Sensor bracket viewed with the robot right side up
F� ��eight="0">
Figure 3-9. Sensor bracket viewed with the robot upside down
The battery pack is bolted to the bottom base plate with two countersunk (flat headed) Phillips bolts as shown in
Figure 3-10
and
Figure 3-11
. You may want to delay this step until after the battery leads have been soldered to make it easier to position all the wires.
sidebyside:
Figure 3-10. Motor Assembly
Figure 3-11. Chassis with Battery Pack Attached
Cut two pieces of red/black wire, each about 7 1/2 inches long. Strip to expose about 3/16 inch of bare wire at one end of the wires
and attach to the motor terminals. Strip 1/4 inch off the other end of the pairs of wires; these will be connected to the motor shield.
Connect a 0.1uF capacitor across each of the motor terminals, as shown in
Figure 3-12
. The capacitors suppress electrical spikes generated by the motor that could interfere with signals on the Arduino board.
Figure 3-20. Velcro pad in position on the 2WD chassis. Inset shows Velcro attached to the Arduino board.
Figure 3-21
shows the mounted boards. The Velcro will hold the boards in position when the robot is moving about, but use one hand to steady the
Arduino when you unplug the shield and take care not to use too much downward pressure that could push the Arduino pins through
the tape when plugging in the shield.
Note
The spacer is required for a Leonardo board because there is insufficient space for an M3 bolt in the munting hole near the
switch. The Uno board has more room so you can use a another of the 10mm spacers and M3 hardware for mounting that
board.
Figure 3-23
shows the location of the mounting points viewed from the underside of the panel.
sidebyside:
Figure 3-22. Mounting the Arduino board as viewed from the top of the chassis
Figure 3-23. Underside showing location of the three Arduino mounting points
Attach the top plate with four M3 bolts as shown in
Figure 3-24
.
Figure 3-24. Top Plate Assembly
Next Steps
Chapter 5, Tutorial: Getting Started with Arduino
explains how to set up and use the development environment that will be used to upload code to the robot. If you are already an
Arduino expert, you can skip to
Chapter 6, Testing the Robot's Basic Functions
, but first, see
Installing Third-Party Libraries
for advice on the libraries used with the code for this book.
If you have the libraries installed and want run a simple test to verify that the motors are working correctly, you can run the sketch
shown in
Example 3-1
.
Example 3-1. Initial motor test for 2WD
/*******************************************
* MotorTest2wd.ino
* Initial motor test for 2WD - robot rotates clockwise
* Left motor driven forward, right backward
* then counter-clockwise
* Michael Margolis 24 July 2012
********************************************/
const int LED_PIN = 13;
const int speed = 60; // percent of maximum speed
#include <AFMotor.h> // adafruit motor shield library (modified my mm)
AF_DCMotor Motor_Left(1, MOTOR12_1KHZ); // Motor 1
AF_DCMotor Motor_Right(2, MOTOR12_1KHZ); // Motor 2
int pwm;
void setup()
{
Serial.begin(9600);
blinkNumber(8); // open port while flashing. Needed for Leonardo only
// scale percent into pwm range (0-255)
pwm= map(speed, 0,100, 0,255);
Motor_Left.setSpeed(pwm);
Motor_Right.setSpeed(pwm);
}
// run over and over
void loop()
{
Serial.println("rotate cw");
Motor_Left.run(FORWARD);
Motor_Right.run(BACKWARD);
delay(5000); // run for 5 seconds
Serial.println("rotate ccw");
Motor_Left.run(RELEASE); // stop the motors
Motor_Right.run(RELEASE);
delay(5000); // stop for 5 seconds
}
// function to indicate numbers by flashing the built-in LED
void blinkNumber( byte number) {
pinMode(LED_PIN, OUTPUT); // enable the LED pin for output
while(number--) {
digitalWrite(LED_PIN, HIGH); delay(100);
digitalWrite(LED_PIN, LOW); &nbs�/>#do or/>*KHZp;delay(400);
}
}
This sketch runs the motors in opposite directions to cause the robot to rotate clockwise for 5 seconds, then reverses direction to
rotate counter-clockwise. This will repeat until the power is switched off.
Hardware Required
See
http://shop.oreilly.com/product/0636920028024.do
for a detailed parts list.
Tools listed in
Tools
The assembled electronics (see
Chapter 2, Building the Electronics
4WD Mobile Platform (four wheeled robot kit made by DFRobot)
Four 0.1uF ceramic capacitors<�0">� � din/p>
Two lengths of 3 conductor ribbon cable, two 3 way 0.1" headers for edge sensors
Optional: charging circuit resistors and diode, see detailed parts list
Mechanical Assembly
Mechanical assembly of the 4WD chassis is straightforward and the only tools needed are a Phillips screwdriver and pliers. Following
the steps in order will ensure that you use the correct hardware in each assembly. You will need a soldering iron, wire cutters, and wire
strippers to wire up the motor and power leads.
Motor Assembly
Use four long bolts to attach two motors to each of the side plates. The motor shaft goes through the large hole and there is a small
locating stud on the motor that fits into the smaller hole. The lock washer (the one with a raised edge) goes between the nut and flat
washer. Ensure the motor is flat against the plate and tighten the nuts firmly but take care not to use too much force or you will stress
the plastic mo�div heig r="#8tor housing.
Figure 4-4
and
Figure 4-5
shows the assembly.
Figure 4-4. Motor assembly
sidebyside:
Note
Lock washers are used to prevent a nut from accidentally coming loose due to vibration. This is critical for attaching
the motor and switch. These washers have a split ring or serrations that apply extra friction when tightened. If you
find that things still come loose, don't overtighten the nuts. Instead, retighten the nut and apply a dab of nail polish to
the point where the threads emerge from the nut.
Figure 4-9. Rear panel switch and power jack assembly viewed from the front
Figure 4-10. The other side of the panel showing the switch orientation and power jack
Figure 4-16. Wiring for trickle charging with Arduino voltage monitoring
Figure 4-17. Wiring for trickle charging with Arduino voltage monitoring
Next Steps
Chapter 5, Tutorial: Getting Started with Arduino
explains how to set up and use the development environment that will be used to upload code to the robot. If you are already an
Arduino expert, you can skip to
Chapter 6, Testing the Robot's Basic Functions
, but first, see
Installing Third-Party Libraries
for advice on the libraries used with the code for this book and the steps needed to configure the RobotMotor library for the 4WD
robot.
If you have the libraries installed and want run a simple test to verify that the motors are working correctly, you can run the following
sketch:
Example 4-1. Initial motor test for 4WD
/*******************************************
* MotorTest4wd.ino
* Initial motor test for 4WD
* robot rotates clockwise
* (Left motors driven forward, right backward)
* Michael Margolis 24 July 2012
********************************************/
const int LED_PIN = 13;
const int speed = 60; // percent of maximum speed
#include <AFMotor.h> // adafruit motor shield library (modified my mm)
AF_DCMotor Motor_Left_Front(4, MOTOR34_1KHZ); // Motor 4
AF_DCMotor Motor_Right_Front(3, MOTOR34_1KHZ); // Motor 3
AF_DCMotor Motor_Left_Rear(1, MOTOR12_1KHZ); // Motor 1
AF_DCMotor Motor_Right_Rear(2, MOTOR12_1KHZ); // Motor 2
int pwm;
void setup()
{
Serial.begin(9600);
blinkNumber(8); // open port while flashing. Needed for Leonardo only
// scale percent into pwm range (0-255)
pwm= map(speed, 0,100, 0,255);
Motor_Left_Front.setSpeed(pwm);
Motor_Right_Front.setSpeed(pwm);
Motor_Left_Rear.setSpeed(pwm);
Motor_Right_Rear.setSpeed(pwm);
}
// run over and over
void loop()
{
Serial.println("rotate cw");
Motor_Left_Front.run(FORWARD);
Motor_Left_Rear.run(FORWARD);
Motor_Right_Front.run(BACKWARD);
Motor_Right_Rear.run(BACKWARD);
delay(5000); // run for 5 seconds
Serial.println("stopped");
Motor_Left_Front.run(RELEASE); // stop the motors
Motor_Right_Front.run(RELEASE);
Motor_Left_Rear.run(RELEASE); // stop the motors
Motor_Right_Rear.run(RELEASE);
delay(5000); // stop for 5 seconds
}
// function to indicate numbers by flashing the built-in LED
void blinkNumber( byte number) {
pinMode(LED_PIN, OUTPUT); // enable the LED pin for output
while(number--) {
digitalWrite(LED_PIN, HIGH); delay(100);
digitalWrite(LED_PIN, LOW); delay(400);
}
}
This sketch runs the motors in opposite directions to cause the robot to rotate clockwise for 5 seconds, then stops for 5 seconds. This
will repeat until the power is switched off.
Note
This test sketch does not use the RobotMotor library—if this test functions correctly but the test in
Chapter 6, Testing the Robot's Basic Functions
does not work, the most likely cause is the configuration of the motor library—make sure you copy the 4wd version of the library
code as described in
Installing Third-Party Libraries
.
Note
If you’re already familiar with Arduino, please feel free to skip the introductory material in this chapter. However, you will need to
install the libraries that are included in the download the code available from:
http://shop.oreilly.com/product/0636920028024.do
. The section
Installing Third-Party Libraries
has details on installing the required libraries.
Arduino is best known for its hardware, but you also need software to program that hardware. Both the hardware and the software are
called “Arduino.” The combination enables you to create projects that sense and control the physical world. The software is free, open
source, and cross-platform. The boards are inexpensive to buy or you can build your own (the hardware designs are also open
source). In addition, there is an active and supportive Arduino community that is accessible worldwide through the Arduino forums and
the wiki (known as the Arduino Playground). The forums and the wiki offer project development examples and solutions to problems
that can provide inspiration and assistance as you pursue your own projects.
The information in this chapter will get you started by explaining how to set up the development environment and how to compile and
run an example
sketch
.
Note
Source code containing computer instructions for controlling Arduino functionality is usually referred to as a
sketch
in the Arduino community. The word
in the Arduino community. The word
sketch
will be u�all�� sed throughout this book to refer to Arduino program code.
The Blink sketch, which comes with Arduino, is used as an example sketch in this chapter. If you have already assembled the robot
and downloaded the source code for this book, feel free to use the HelloRobot sketch described in
Chapter 6, Testing the Robot's Basic Functions
.
Warning
If you don't have the Arduino software and driver installed on your machine, wait until
Connecting the Arduino Board
to plug the Arduino into your computer.
Hardware Required
Computer with Arduino 1.0.1 or later installed
Leonardo (or Uno) Arduino board
Motor Shield (see
Chapter 2, Building the Electronics
)
USB cable
Arduino Software
Software programs, called sketches, are created on a computer using the Arduino
integrated development environment
(IDE). The IDE enables you to write and edit code and convert this code into instructions that Arduino hardware understands. The IDE
also transfers those instructions to the Arduino board (a process called
uploading
).
Arduino Hardware
The Arduino board is where the code you write is executed. The board can only control and respond to electricity, so specific
components are attached to it to enable it to interact with the real world. These components can be
sensors
, which convert some aspect of the physical world to electricity so that the board can sense it, or
actuators
, which get electricity from the board and convert it into something that changes the world. Examples of sensors include switches,
accelerometers, and ultrasound distance sensors. Actuators are things like lights and LEDs, speakers, motors, and displays.
There are a variety of official boards that you can run your Arduino sketches on and a wide range of Arduino-compatible boards
produced by members of the community.
The most popular boards contain a USB connector that is used to provide power and connectivity for uploading your software onto the
board.
Figure 5-1
shows the board used for the robots in this book, the Arduino Leonardo.
Figure 5-1. Basic board: the Arduino Leonardo
You can get boards that are smaller and boards with more connections. The Leonardo is used with these robotics projects because it
is inexpensive but you can use other boards such as the Uno if you prefer.
Note
If you want to use an Uno board (or earlier Arduino boards), you may need to use a s slightly higher voltage (an additional
battery) to power the robot, see
Appendix D
).
Add-on boards that plug into Arduino to extend hardware resources are called
shields
. The robots covered in this book use a shield that controls the direction and speed of the motors, see
Figure 5-2
:
Figure 5-2. Motor Shield plugged into the Arduino Leonardo
Online guides for getting started with Arduino are available at
http://arduino.cc/en/Guide/Windows
for Windows,
http://arduino.cc/en/Guide/MacOSX
for Mac OS X, and
http://www.arduino.cc/playground/Learning/Linux
for Linux.
Installing Arduino on OS X
The Arduino download for the Mac is a disk image (
.dmg
); double-click the file when the download is complete. The image will mount (it will appear like a memory stick on the desktop). Inside
the disk image is the Arduino application. Copy this to somewhere convenient—the
Applications
folder i�3.� div h Ts a sensible place. Double-click the application once you have copied it over (it is not a good idea to run it from
the disk image). The splash screen will appear, followed by the main program window (
Figure 5-4
).
Figure 5-4. IDE main window (Arduino 1.0 on a Mac)
Driver Installation
To enable the Arduino development environment to communicate with the board, the operating system needs to use the appropriate
drivers for your board.
On Windows, use the USB cable to connect your PC and the Arduino board and wait for the Found New Hardware Wizard to appear.
If you are using a Leonardo or Uno board let the wizard attempt to find and install drivers.
Troubleshooting the Found New Hardware Wizard
If the Found New Hardware Wizard does not appear when you first connect a Leonardo board, open Device Manager
as described in the next paragraph and if you see
Other device> Arduino Leonardo
with an exclamation point, right click on the entry and select Update Driver Software. Choose the Browse my
computer for Driver Software option, and navigate to the Drivers folder inside the Arduino folder you just unzipped.
Select the
drivers
folder and windows should then proceed with the installation process. If the
Windows can't verify the publisher of the driver software
dialog pops up, select
Install this software anyway
.
If the Wizard starts but fails to find drivers (don't worry, this is the expected behavior with an Uno board). To fix it
you now need to go to Start Menu>Control Panel>System and Security. Click on System, and then ope�p><��
�n Device Manager. In the listing that is displayed find the entry in COM and LPT named
Arduino UNO (COM nn)
.
nn
will be the number Windows has assigned to the port created for the board. You will see a warning logo next to this
because the appropriate drivers have not yet been assigned. Right click on the entry and select Update Driver
Software. Choose the Browse my computer for Driver Software option, and navigate to the Drivers folder inside the
Arduino folder you just unzipped. Select the
ArduinoUNO.inf
file and windows should then proceed with the installation process. If the
Windows can't verify the publisher of the driver software
dialog pops up, select
Install this software anyway
.
If you are using an earlier board (any board that uses FTDI drivers) with Windows Vista or Windows 7 and are online, you can let the
wizard search for drivers and they will install automatically. On Windows XP (or if you don't have internet access), you should specify
the location of the drivers. Use the file selector to navigate to the
FTDI USB Drivers
directory, located in the directory where you unzipped the Arduino files. When this driver has installed, the Found New Hardware
Wizard will appear again, saying a new serial port has been found. Follow the same process as before.
On the Mac, the latest Arduino boards can be used without additional drivers. When you first plug the board in a notification will pop up
saying a new network port has been found; you can dismiss this. If you are using earlier boards (boards that need FTDI drivers), you
will need to install driver software. There is a package named
FTDIUSBSerialDriver
, with a range of numbers after it, inside the Arduino installation disk image. Double-click this and the installer will take you through the
process. You will need to know an administrator password to complete the process.
On Linux, most distributions have the driver already installed, but follow the Linux link given in
Arduino Hardware
for specific information for your distribution.
Note
If the software fails to start, check the troubleshooting section of the Arduino website,
http://arduino.cc/en/Guide/Troubleshooting
, for help solving installation problems.
Note
If the power LED does not illuminate when the board is connected to your computer, the board is probably not receiving power.
The exact message may differ depending on your board and Arduino version; it is telling you the size of the sketch and the maximum
size that your board can accept.
The final message telling you the size of the sketch indicates how much program space is needed to store the controller instructions
on the board. If the size of the compiled sketch is greater than the available memory on the board, the following error message is
displayed:
Sketch too big;
see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips
on reducing it.
If this happens, you need to make your sketch smaller to be able to put it on the board, or get a board with higher capacity. You will not
have this problem with the Blink example sketch.
If there are errors in the code, the compiler will print one or more error messages in the console window. These messages can help
identify the error.
As you develop and modify a sketch, you should also consider using the File→
Save As menu option and using a different name or version number regularly so that as you implement each bit, you can go back to an
older version if you need to.
Using Tabs
Tabs provide a convenient way to organize code when your sketch starts to grow. It enables you to keep functionally related code
together and simplifies sharing this code across more than one sketch.
The arrow in the upper right of
Figure 5-8
points to the button which invokes a drop-down window of tab related functions. This window displays the names of the tabs and offers
a list of commands:
New Tab
creates a new tab that (you will be prompted to name the tab)
Rename
enables you to change the name of the currently selected tab
Delete
deletes the current tab (you are asked if you are sure you want to do that)
Figure 5-8. IDE tabs
Each tab is a separate file and when you copy these files to other sketches you add the tab to that sketch.
Because there are many functional modules used in this book and these are shared across most of the sketches, tabs are used
extensively.
Figure 5-8
shows the myRobot sketch, discussed in the next chapter, that uses tabs for infrared sensor code (irSensors) and for program
constants and definitions (
robotDefines.h
).
Hardware Required
The assembled robot chassis.
Motors connected to shield (see
Figure 3-25
for 2WD or
Figure 4-26
for 4WD).
Example code and libraries installed, see
Installing Third-Party Libraries
.
5 AA cells inserted into the battery holder (USB does not provide sufficient power to drive t�0">� � � he motors).
Reflectance sensors mounted and connected (left sensor to analog input 0, right to analog 1). You can use the stripboard wiring
described in
Making a Line Sensor Mount
. But to run the edge detecting project described in
Chapter 9
, you need more space between the sensors.
Figure 6-1
shows the assembled two wheel robot;
Figure 6-2
shows the assembled four wheel robot.
Figure 6-3
shows the sensor and motor connections.
sidebyside:
Figure 6-1. Two wheeled robot with reflectance sensors
Figure 6-2. Four wheeled robot with reflectance sensors
Software Prerequisites
Although the sketch code used in this chapter is printed in the pages that follow, you will need some libraries that are included in
example code (see
How to Contact Us
for the URL). The sketch folders can be copied to your Arduino sketchbook folder (if you are not familiar with the Arduino environment,
� Cenv� read through
Chapter 5
). The download files in the library folder must be copied to your Arduino libraries folder (see
Installing Third-Party Libraries
).
Install the AFMotor library contained in the download zip file. This library is modified from the one on the Adafruit site to work with the
Leonardo board; the standard Adafruit library can be used with the Uno board.
Install the RobotMotor library contained in the example code download. This library comes configured for the two wheeled robot; if you
have the four wheeled robot will need to update the library for this robot as described in the
Note
below.
Note
If your robot uses four wheel drive, you must configure the
RobotMotor
library code by modifying the
RobotMotor.h
file to tell the compiler that the library should be built for the 4WD chassis. See
Installing Third-Party Libraries
for details on how to do this.
A third library, named IrRemote, is also included in the download. This library won't be needed until
Chapter 11
, but copying it into your libraries folder now will save you having to do this later.
The sketch tests the calibration of the robot's speed of movement. The front sensors are used to initiate a motor test—the motors
rotate the robot 360 degrees in the direction of the sensor that was triggered. If the robot is functioning correctly, it will execute a
complete revolution at seven speeds ranging from slowest to fastest.
To run the test, place the robot on a reflective white surface such as a large sheet of paper. When the robot's up and running, Arduino's
pin 13 LED will flash once.
Note
Another way to test is to put the robot on something that will raise the wheels off the ground by an inch or so. This will enable
the motors to turn without the robot skittering around.
This sketch displays debugging information to the serial console. If you'd like to view it, you'll need to keep the USB cable plugged into
your computer and your robot; be careful, since the robot will be moving. If you're using an Arduino Leonardo, wait until the robot's LED
flashes to indicate it's ready before opening the Arduino Serial Monitor (the Serial Monitor is the rightmost icon on the Arduino
toolbar). When the sketch starts, you should see the following in the Arduino Serial Monitor:
Waiting for a sensor to detect blocked reflection
Swipe something dark (a small piece of matte black paper the size of a business card works well) near one of the sensors (panel 2
seen in
Figure 6-7
). The Serial monitor should now display the output similar to that shown in
Example 6-2
. The number of lines and the values displayed will vary with different robots but you should see multiple lines showing the direction of
rotation, speed and time in milliseconds.
Example 6-2. Serial output from HelloRobot.ino
Left calibration
Left: rotate 360 degrees at speed 40 for 5500ms
Left: rotate 360 degrees at speed 50 for 3300ms
Left: rotate 360 degrees at speed 60 for 2400ms
Left: rotate 360 degrees at speed 70 for 2000ms
Left: rotate 360 degrees at speed 80 for 1750ms
Left: rotate 360 degrees at speed 90 for 1550ms
Left: rotate 360 degrees at speed 100 for 1150ms<� Bnbs/blockquote>
Motors on the left side should spin in reverse, motors on the right should spin forward for the indicated time in milliseconds (if the robot
was on the ground, it would rotate to the left (counter-clockwise). If you don't see the expected results, see
Troubleshooting
for help.
Completing this test will verify that everything (the robot motors, power source, Arduino and motor shield) is wired up and functioning
correctly. Double check that you have completed all the building steps. Take particular care that the battery wires to the motor shield
are attached to the correct polarity.
sidebyside:
Figure 6-6. Robot sitting on a reflective surface
Figure 6-6
shows the robot stationary on a reflective surface. If the robot moves when placed on the surface, switch the power
off and then on so the robot can measure and calibrate for the ambient light level. It should remain motionless until a
sensor detects a reduction in the light reflected off the surface.
sidebyside:
Figure 6-7. Non-reflective card under right sensor
The robot should rotate in the direction of the sensor that detects the reduced reflection. In
Figure 6-7
, a non-reflective card is swiped under the right sensor which will trigger the robot to turn clockwise.
sidebyside:
Figure 6-8. Robot rotate� B Bos in direction of swiped sensor
Figure 6-8
shows the right motor running backwards and the left forwards which will rotate the robot clockwise.
This library provides a consistent interface for motor functions in order to isolate the higher level logic from hardware specifics. This
means that you can use the same sketch code with (almost) any motor hardware simply by changing the
RobotMotor
library code to suit the hardware. The motor code is explained in the next chapter and you can find example code to support � Ce
t� a different motor controller in
Appendix B
.
The block that begins with:
/***** Global Defines ****/
contains declarations for constants that identify: sensors, directions and obstacles. These constants enable you to refer to elements in
your sketch using meaningful names instead of numbers, for example this:
calibrateRotationRate(DIR_LEFT,360);
instead of this:
motorForward(0, 360);
The
setup
section calls functions to initialize the motor and sensor modules (more on these later). The
loop
function uses the
lookForObstacle
function to determine if a reflection is detected. It waits until no reflection is detected on either sensor; the robot is not on the ground (or
on a non-reflective surface). The
lookForObstacle
function is checked to determine if the left or right sensor detects a reflection, and if so, calls the
calibrateRotationRate
function to rotate the robot for a short period.
The
lookForObstacle
function is told which obstacle to check for (the obstacles are identified using the defines described above). The
case
statement (see
http://arduino.cc/en/Reference/SwitchCase
) is used to call
irEdgeDetect
function that returns true if an object is detected on that sensor. If no object is detected, the function returns
OBST_NONE
, shown in
Example 6-5
. See
Infrared Reflectance Sensors
for a detailed explanation of
irEdgeDetect
and related functions.
Example 6-5. The lookForObstacle function
// returns true if the given obstacle is detected
boolean lookForObstacle(int obstacle)
{
&nb� Bighsp; switch(obstacle) {
case OBST_FRONT_EDGE: return irEdgeDetect(DIR_LEFT) &&
irEdgeDetect(DIR_RIGHT);
case OBST_LEFT_EDGE: return irEdgeDetect(DIR_LEFT);
case OBST_RIGHT_EDGE: return irEdgeDetect(DIR_RIGHT);
}
return false;
}
The motor code commands the motor controller board to drive the motor forwards, backwards or stop.
For example, the following will spin the right motor forward at a speed given by the
speed
parameter (the parameter is the percentage of the maximum speed):
motorForward(MOTOR_RIGHT, speed);
Troubleshooting
If you are having trouble getting HelloRobot working then the first thing to do is to put the robot down, walk away from your computer
screen and have a refreshing drink. Come back and look at things with fresh eyes and check to see if you have things wired up and
connected correctly. If it looks like the connections are okay, then the next step is to make a list of the major symptoms:
Compile errors
'AF_DCMotor' does not name a type
error message—this message indicates the AFMotor library has not been found. This library is included with the download code
for this book (see
How to Contact Us
for the URL). See
Installing Third-Party Libraries
in
Chapter 5, Tutorial: Getting Started with Arduino
� Cv h�
for help with this.
"This chip is not supported!"
error message—This message is displayed if the chip selected in the IDE is not recognized by the library. This will occur if you
select the Leonardo board and use a version of the AFmotor library that does not support this chip. Replacing your AFMotor
library with the one in the book's example code will fix this problem.
"expected definition: CHASSIS_2WD or CHASSIS_4WD not found"
will be displayed if you changed the defines in the RobotMotor library to an invalid value. This library expects to find either
CHASSIS_2WD or CHASSIS_4WD following the
#define
in RobotMotor.h.
Software Errors
The Serial Monitor is not displaying the text shown at the end of
Load and Run helloRobot.ino
—read through
Chapter 5, Tutorial: Getting Started with Arduino
and check that you have the drivers for your board correctly installed.
The Serial Monitor displays the initial text but then displays errors or other unexpected text—see
Appendix C
.
Hardware symptoms
No LEDs on the Arduino board are lit (you may need to remove the motor shield to check this). - This usually means that either
no power is being supplied to the board. If the power switch is on, check that the batteries have sufficient voltage and are
located correctly. Check the wiring from the battery and switch to the shield.
Motors don't turn—Check that the batteries are fitted correctly (USB does not provide enough power to drive the motors). Check
the motor wiring. You can test each motor by disconnecting the motor wires going to the motor terminals on the shield and
connecting them directly to the battery terminals. If the motors still do not turn but the shield LED is lit, then double check the
shield soldering.
Two of the four motors don't turn on the 4WD robot—Have you configured the library for 4WD?—see
Software Prerequisites� B
.
Motors run but the robot does not rotate 360 degrees—the robot rotation does not need to be exact; anything within 20 or 30
degrees is good enough. See
Chapter 7
if you do want to adjust the rotation rate.
See
Appendix C
for more on debugging.
Note
You can download the
myRobot
sketch from the book's website but you may want to go through these steps yourself to familiarize yourself with the procedure for
creating and using tabs in the IDE.
1. Load the HelloRobot sketch and use the IDE file menu to save as 'myRobot'.
2. Create a tab by clicking the tab dropdown and selecting 'New Tab' (see
Figure 5-8
). Name the tab 'IrSensors'.
3. Click the
myRobot
tab, scroll down to the end of the sketch and
cut
all code from the end up to the
ir reflectance sensor code
(
Example 6-8
) comment and paste it into the
IrSensors
tab.
Example 6-8. IR reflectance sensor code
/****************************
ir reflectance sensor code
****************************/
const byte NBR_SENSORS = 3; // this version only has left and right sensors
const byte IR_SENSOR[NBR_SENSORS] = {0, 1, 2}; // analog pins for sensors
int irSensorAmbient[NBR_SENSORS]; // sensor value with no reflection
int irSensorReflect[NBR_SENSORS]; // value considered detecting an object
int irSensorEdge[NBR_SENSORS]; // value considered detecting an edge
boolean isDetected[NBR_SENSORS] = {false,false}; // set true if object detected
const int irReflectThreshold = 10; // % level below ambient to trigger reflection
const int irEdgeThreshold = 90; // % level above ambient to trigger edge
void irSensorBegin()
{
for(int sensor = 0; sensor < NBR_SENSORS; sensor++)
irSensorCalibrate(sensor);
}
// calibrate for ambient light
void irSensorCalibrate(byte sensor)
{
int ambient = analogRead(IR_SENSOR[sensor]); // get ambient level
irSensorAmbient[sensor] = ambient;
// precalculate the levels for object and edge detection
irSensorReflect[sensor] = (ambient * (long)(100-irReflectThreshold)) / 100;
irSensorEdge[sensor] = (ambient * (long)(100+irEdgeThreshold)) / 100;
}
// returns true if an object reflection detected on the given sensor
// the sensor parameter is the index into the sensor array
boolean irSensorDetect(int sensor)
{
boolean result = false; // default value
int value = analogRead(IR_SENSOR[sensor]); // get IR light level
if( value <= irSensorReflect[sensor]) {
result = true; /� B A/ object detected (lower value means more reflection)
if( isDetected[sensor] == false) { // only print on initial detection
Serial.print(locationString[sensor]);
Serial.println(" object detected");
}
}
isDetected[sensor] = result;
return result;
}
boolean irEdgeDetect(int sensor)
{
boolean result = false; // default value
int value = analogRead(IR_SENSOR[sensor]); // get IR light level
if( value >= irSensorEdge[sensor]) {
result = true; // edge detected (higher value means less reflection)
if( isDetected[sensor] == false) { // only print on initial detection
Serial.print(locationString[sensor]);
Serial.println(" edge detected");
}
}
isDetected[sensor] = result;
return result;
}
The
myRobotOk
example sketch provided in the book download code shows the code after the code is moved into the tabs.
Global Definitions
Definitions that need to be accessed across multiple modules are called 'global' definitions. These are generally stored
in files called 'header files' (or 'headers'). These files typically have a file extension of
.h
and the file containing these global definitions is here called
robotDefines.h
. Although the Arduino build process will automatically make all of the functions in each tab accessible throughout the
sketch, constant definitions should be explicitly included at the top of the main tab as follows:
// include the global defines
#include "robotDefines.h"
The final step in restructuring the sketch is to move the constant definitions at the top of the sketch into a separate tab. These
constants are used by a number of different modules and collecting these together makes it easier to ensure that the values are
accessible by all the modules:
1. Create a tab named
robotDefines.h
(don't forget the
.h
).
2. From the top of the myRobot tab, move the defines starting from:
/**** Global Defines ****/
(
Example 6-9
) into the tab you just created.
Switch back to the myRobot tab, and add this line at the top, right after the
#include
s for
AFMotor.h
and
RobotMotor.h
:
#include "robotDefines.h"
Hardware Required
This chapter uses the AFMotor shield described in
Chapter 2
.
Types of Motors
Brushed DC Motors, such as the ones used in the two wheeled and four wheeled platforms (see
Figure 7-2
) are the most common type used with Arduino robots. These have two leads connected to brushes (contacts) that control� K
th� the magnetic field of the coils that drive the motor core (armature). Motor direction can be reversed by reversing the polarity
of the power source. These motors typically rotate too fast to directly drive the robot wheels or tracks, so gear reduction is used
to reduce speed and increase torque.
Figure 7-2. DC motor with gearbox
Other kinds of motors can be used to power robots; here are some you may come across:
Continuous rotation servo
These motors are used on smaller robots. They have the advantage that the motor controller, motor, and
gearbox are all mounted in the same housing, so they are easy to attach to a robot and can be driven directly
from Arduino pins. However they usually have less torque than typical stand-alone brushed motors.
Brushless motors
These have increased torque and efficiency compared to brushed motors but they are more expensive and
complex to control. However, prices are dropping and they are a good choice for a larger robot.
Stepper motors
These motors are used on large robots when precise control is required. These motors typically require 12 or
24 volts so they are not often used on small battery operated robots. However they may become more popular
due to the recent availability of low cost 5 volt steppers.
Motor Controllers
The two wheel and four wheel platforms use small DC motors that are controlled using an H-Bridge. The H-Bridge featured in
this book is part of the AFMotor shield from Adafruit Industries. This can drive up to four motors independently, although only two
are used with the two wheeled robot. This shield requires a library for interfacing sketch code with the hardware; this library is
included with the code download for this book (see
How to Contact Us
).
Note
The name H-bridge derives from the characteristic shape that you can see in these figures.
To enable the sketches to work with other H-Bridge hardware, a library named
RobotMotor
is provided with the example code that provides generic control functions that the library translates into the specific commands
for the AFMotor shield or another shield if you have use different hardware. see
Software Architecture for Robot Mobility
Note
This library is modified from the one on the Adafruit site to work with the Leonardo board. The standard Adafruit library
can be used with the Uno board). See
Installing Third-Party Libraries
if you need directions for installing a library. If you followed along with
Chapter 6
, you will already have the library installed.
The following diagrams explain how an H-bridge works and the
RobotMotor
functions used to control the motors:
sidebyside:
Figure 7-3. H-Bridge with Motor Idle
Figure 7-3
is a schematic drawing that shows how an H-bridge works. The motor is connected to the positive supply
voltage and ground through four switches (in the actual H-bridge, the switching is done with transistors). When
all the switches are open, no current flows and the motor is stopped. The code to stop a motor is:
motorStop(motor);
Note
The parameter in brackets
(motor)
is a constant identifying the motor (
MOTOR_
LEFT
or
MOTOR
_RIGHT
) to control. The software for the four wheeled robot treat� J qs the two motors on the same side as if they were a
single motor.
sidebyside:
Figure 7-4. H-Bridge with Motor Running Forward
Figure 7-4
shows the two switches that when closed will cause the motor to run forward. The one marked A
connects the positive motor terminal to the positive power supply. Switch D
connects the negative motor terminal to ground. The code to run a motor forward at the given speed is:
motorForward(motor, speed);
Note
The constant
speed
is a value representing speed as a percent of maximum speed. This is described in the section:
Controlling Motor Speed
.
sidebyside:
Figure 7-5. H-Bridge with Motor Running in Reverse
Figure 7-5
shows that the opposite switches result in the motor reversing. Switch C
connects the positive supply to the negative motor terminal. Switch B
connects the positive motor terminal to ground. The code to run a motor in reverse at the given speed is:
motorReverse(motor, speed);
� J q
sidebyside:
Figure 7-6. H-Bridge with Motor Brake
Figure 7-6
shows switches B
and D
closed. Both motor terminals are connected together - neither terminal is connected to the positive supply
voltage. This is a mode supported by some H-bridge hardware to stop the motor more quickly than it would in
the previous case where the motor is simply disconnected from the power. Because the motor terminals are
shorted, the motor will resist rotation. If the motor had been spinning and then set to this mode, it will stop
more quickly than if the terminals were simply disconnected. The code to brake a motor is:
motorBrake(motor);
Note
Not all H-bridges, including the Adafruit library, support this mode. The
RobotMotor
library will call
motorStop
when the
motorBrake
function is called.
Note
map
is a handy function that is used extensively throughout this book. The function scales a value from one range to another
range. For example, the following scales a value from
analogRead
(0-1023) to a percent (0-100):
int toPercent = map(val, 0,1023, 0,100);
The table holds durations in milliseconds for speeds in intervals of 10%. The values were derived from experimentation with the
two wheeled robot using a sketch named
myRobotCalibrateRotation
sketch and noting the angles for each of the speeds as shown in
Figure 7-8
.
Figure 7-8. Angle that the robot rotates for one second burst at each of the supported speeds
By calculating the angle as a fr� JJJJv haction of 360 degrees, the time to rotate the robot one complete revolution can be
determined for each speed ( the calculation for the value in milliseconds is:
1000*(360/angle)
.
Figure 7-9
shows the actual times for the 2WD robot.
Figure 7-9. Time for a full rotation at various speeds
The relationship between rotation angle and speed percentage is not linear, so interpolation is used to calculate the duration to
produce a full rotation for any speed (as long as it is as fast or faster than the minimum speed).
Example 7-3
shows the code that uses the table with times based on the data shown in
Figure 7-9
.
The RobotMotor library has the code to determine how much time the robot requires to rotate 360 degrees. This will differ
between the two and four wheeled chassis and vary as the motor speed varies.
Example 7-3
shows the values used in the RobotMotor.cpp code for the 2WD chassis.
Example 7-3. Controlling rotation rate
// tables hold time in ms to rotate robot 360 degrees at various speeds
// this enables conversion of rotation angle into timed motor movement
// The speeds are percent of max speed
// Note: low cost motors do not have enough torque at low speeds so
// the robot will not move below this value
// Interpolation is used to get a time for any speed from MIN_SPEED to 100%
const int MIN_SPEED = 40; // first table entry is 40% speed
const int SPEED_TABLE_INTERVAL = 10; // each table entry is 10% faster speed
const int NBR_SPEEDS = 1 + (100 - MIN_SPEED)/ SPEED_TABLE_INTERVAL;
int speedTable[NBR_SPEEDS] = {40, 50, 60, 70, 80, 90, 100}; // speeds
int rotationTime[NBR_SPEEDS] = {5500, 3300, 2400, 2000, 1750, 1550, 1150}; // time
Example 7-4
shows the values for the 4WD chassis.
Example 7-4. Controlling rotation rate
const int MIN_SPEED = 60; // first table entry is 60% speed
const int SPEED_TABLE_INTERVAL = 10; // each table entry is 10% faster speed
const int NBR_SPEEDS = 1 + (100 - MIN_SPEED)/ SPEED_TABLE_INTERVAL;
int speedTable[NBR_SPEEDS] = {60, 70, 80, 90, 100}; // speeds
int rotationTime[NBR_SPEEDS] = {5500, 3300, 2400, 2000, 1750}; // time
Note that there are fewer entries in the tables for the 4WD robot because this chassis requires a higher speed to get going.
Calibrating Rotation and Tracking
explains how to adjust the tables to suit your robot.
The table entries assume speed intervals of 10% so the value for MIN_SPEED should be multiple of 10. There must be one
rotation time per speed so if you increase MIN_SPEED by 10 for example, you will also need to remove the first element in both
speedTable and rotationTime.
The code in
RobotMotor.cpp
that uses the data in the rotationTime table is the same for both chassis (see
Example 7-5
).
Modifying a Library
You know how to modify an Arduino sketch—just edit it in the Arduino IDE. But modifying a library is a bit
more involved. You need to go into the sketch folder, open up the library directory, and find the file. Then you
need to open it in a text editor. Here's how to modify the
RobotMotor.h
file to use the 4WD chassis.
First, find the sketchbook location. Go to Arduino's preferences (File→
Preferences on Windows or Linux, Arduino→
Preferences on Mac). Under Sketchbook Location, you'll find the name of the directory that contains your
sketches and libraries. Next:
1. Open the sketchbook folder in the Finder (Mac) or Explorer (Windows).
2. Locate the libraries directory inside, and then open the directory named
RobotMotor
.
3. Right-click (or Control-click on the Mac) the
RobotMotor.h
file, and open it with a plain text editor. On Windows, you should use Notepad. On the Mac, you can use
TextEdit. On Linux, use your favorite plain text editor.
4. Change
#define CHASSIS_2WD
to
#define CHASSIS_4WD
<="0">
and save the file.
Although you need to quit and restart the Arduino IDE when you install a new library, you don't need to do so each time you
modify a library.
Example 7-5. Applying the rotationTime table
// return the time in milliseconds to turn the given angle at the given speed
long rotationAngleToTime( int angle, int speed)
{
int fullRotationTime; // time to rotate 360 degrees at given speed
if(speed < MIN_SPEED)
return 0; // ignore speeds slower then the first table entry
angle = abs(angle);
if(speed >= 100)
fullRotationTime = rotationTime[NBR_SPEEDS-1]; // the last entry is 100%
else
{
int index = (speed - MIN_SPEED) / SPEED_TABLE_INTERVAL ; // index into speed
// and time tables
int t0 = rotationTime[index];
int t1 = rotationTime[index+1]; // time of the next higher speed
fullRotationTime = map(speed,
speedTable[index],
speedTable[index+1], t0, t1);
// Serial.print("index= "); Serial.print(index);
// Serial.print(", t0 = "); Serial.print(t0);
// Serial.print(", t1 = "); Serial.print(t1);
}
// Serial.print(" full rotation time = "); Serial.println(fullRotationTime);
long result = map(angle, 0,360, 0, fullRotationTime);
return result;
}
To calculate the time to rotate an angle other than 360 degrees, the
map
function is used again:
long result = map(angle, 0,360, 0, fullRotationTime);
Running this sketch will rotate the robot left (CCW) for one second, stop for one second, then rotate the robot right (CW) for a
second. If you mark the angle of the robot after each CCW rotation, you can calculate how much longer or shorter it would take
the robot to turn 360 degrees for each speed. If your robot does not rotate at all at the slower speeds, note the lowest speed that
the robot does move and set
MIN_SPEED
in RobotMotor.cpp to this value.
The RobotMotor library also supports the ability to adjust the relative power to each motor in order to prevent the robot drifting off
a straight course due to differences in performance between the left and right motor(s). If your robot does not track a straight line
when moving forward or backward, you can modify the motor library (see next section) to correct this.
The RobotMotor.cpp library file contains a constant that can be adjusted to correct drift:
const int differential = 0; // % faster left motor turns compared to right
If the robot drifts the right when running this sketch, try setting
differential
to 2. If this overcorrects (the robot now drifts to the left), decrease the differential value. If you need more correction, increase the
value. If the robot was drifting to the left, use negative values of differential to compensate. You should be able to get the robot
running more or less straight after a little trial and error. Don't worry about minor deviations which are caused by small
differences in the efficiency of the motors at varying battery levels.
After you have settled in a value for
differential
you must change this in the RobotMotor.cpp file. Open this file with a text editor and (see
Modifying a Library
) and find the declaration towards the beginning of the file:
const int differential = 0; // % faster left motor turns compared to right
Replace 0 with the value determined from the calibration sketch and save the file.
The
RobotMotor.cpp
file contains code for both the two wheel and four wheel chassis. Conditional compilation is used to build the library for the
appropriate version.
#if defined CHASSIS_2WD
and
#if defined CHASSIS_4WD
are checks to see which chassis has been defined in the
RobotMotor.h
file. code between
#if defined CHASSIS_2WD
and
#endif
will only be compiled if
CHASSIS_2WD
is defined in
RobotMotor.h
. See
Installing Third-Party Libraries
for more details on changing the define for the four wheel chassis.
This library can be modified to support different hardware. For example, see
Appendix B
for the code to use the Ardumoto shield (but note that Ardumoto only supports two motors so is not suitable for the four wheeled
robot).
The code provides functions that combine the individual motor commands described in
Motor Controllers
. For example, the
moveForward
function calls the individual functions to rotate the left and right motors in the direction that moves the robot forward. The speed to
function calls the individual functions to rotate the left and right motors in the direction that moves the robot forward. The speed to
move is set by the
moveSetSpeed
function.
moveSetSpeed
commands the motors to run at the desired speed and stores the speed value so the robot can resume running at the last set
speed following an evasive action needed to avoid obstacles.
The
moveFaster
function increases the current speed by a specified increment and calls
moveSetSpeed
to make this the current speed. For example,
movefaster(10);
will result in the robot moving at 85% speed if it was previously moving at 75%.
The
moveSlower
function is similar but decreases rather than increases the speed. Both functions check to ensure that the new speed is valid. If
moveSlower(20)
was called when the robot was moving at 85% speed, the robot would slow down to run at 65% speed.
The movement functions also call the function
changeMoveState
to store the current movement state. These states are defined in the
robotDefines.h
tab (see
Example 6-9
) and are used to enable the robot to make decisions with the knowledge of what it is currently doing. For example, detecting an
obstacle in front can be handled differently depending on whether the robot is moving forwards or backwards. The robot can
check the current move state when it encounters an object and take action if the robot is moving towards it but ignore obstacles
that are not in the direction of movement. Here are all the move states:
enum {MOV_LEFT, MOV_RIGHT, MOV_FORWARD,
MOV_BACK, MOV_ROTATE, MOV_STOP};
Note
If you are unfamiliar with
enum
(enumerated lists), see
Code Style (About the Code)
in
<� Jdirit font color="#8e0012">Preface
or an online C or C++ reference.
To assist debugging, each state has an associated text label that can be printed to the serial monitor to show what the robot
should be doing.
const char* states[] = {"Left", "Right", "Forward",
"Back", "Rotate", "Stop"};
The
moveRotate
function will rotate a robot by the given angle. Negative angles turn counter clockwise, positive angles turn clockwise. Rotation is
achieved by running the motors in opposite directions (see
How Robots Move
).
The
timedMove
and
movingDelay
functions work together to provide a simple way to instruct the robot briefly move away from an obstacle. Because
movingDelay
can check for obstacles while taking evasive action, it can avoid bumping into new obstacles while moving away from another.
The
checkMovement
function is implemented in the Look module (see
The Look Code
).
Hardware Discussed
QTR-1A reflectance sensors
Two are used for edge detection, but a third is required for line following. Additional sensors are available from
many internet shops that stock robot parts, or direct from the manufacturer:
http://www.pololu.com/catalog/product/958/
SONAR Distance Sensor
One is used to measure the distance to obstacles (Maker Shed product code MKPX5).
@ X� kquote>
Maxbotix EZ1 distance sensor
This is an optional item that can be used to measure distance.
Sharp IR
This is an optional item that can be used to measure distance.
PIR (Passive Infrared) sensor
This is an optional item that can be used to activate the robot when it detects the presence of a 'warm body'
(Maker Shed product code: MKPX6).
Sound Sensor
This is an optional item that can activate the robot on a sound level, such as a hand clap. (SparkFun product
code BOB-09964).
Software
The chapter contains background information on sensors that will be added to the robot in later chapters. The reflectance sensor
code is from the sketches introduced in
Chapter 6, Testing the Robot's Basic Functions
. The Ping (Sonar distance sensor) hardware and software is covered in
Chapter 10, Autonomous Movement
.
Note
The sensor voltage reduces with increased light, so lower readings mean more reflectance. Therefore, the closer a reflecting
object is to the sensor, the lower the reading on the analog pin monitoring the sensor.
Whereas
irSensorDetect
returns true when a reflection is detected, sometime you want the opposite case—to return true if an edge (no� R A
reflection) is detected, as in
Example 8-2
. The
irEdgeDetect
provides this capability; it is used to return true when an edge is detected. In other words, when the sensor is looking
downwards, no reflection from the surface is detected because a dark object is blocking the reflection or the nearest surface—
probably the floor—is many inches away! This effect is used in the examples from
Chapter 6
to detect when you've placed a dark object under the sensor.
Example 8-2. Detecting the absence of a reflection
boolean irEdgeDetect(int sensor)
{
boolean result = false; // default value
int value = analogRead(IR_SENSOR[sensor]); // get IR light level
if( value >= irSensorEdge[sensor]) {
result = true; // edge detected (higher value means less reflection)
if( isDetected[sensor] == false) { // only print on initial detection
Serial.print(locationString[sensor]);
Serial.println(" edge detected");
}
}
isDetected[sensor] = result;
return result;
}
The sensors need to be calibrated to take ambient light into account. Reflectance sensors respond to sunlight and artificial light
so a threshold is measured with no object near the sensor. Levels above this threshold mean the light level is
above ambient
, which indicates that a nearby object is reflecting the IR light from the sensor. Ambient light calibration is done using the code
shown in
Example 8-3
.
Example 8-3. Light calibration
// calibrate thresholds for ambient light
void irSensorCalibrate(byte sensor)
{
int ambient = analogRead(IR_SENSOR[sensor]); // get ambient level
irSensorAmbient[sensor] = ambient;
// precalculate the levels for object and edge detection
irSensorReflect[sensor] = (ambient * (long)(100-irReflectThreshold)) / 100;
irSensorEdge[sensor] = (ambient * (long)(100+irEdgeThreshold)) / 100;
}
Note
(long)
is used in the calculation to prevent overflow. Values like 950� R A00 cannot fit into an Arduino integer (max value is
32,767) whereas a long can store values up to 2,147,483,647.
You may come across code that performs this calculation using floating point (
ambient * 0.95)
. However, floating point requires more code and memory than integer calculations.
This loads the ambient light level into the variable
ambient
, calculates levels for reflectance detection (stored in the
irSensorReflect
array) and levels for edge detection (stored in the
iresensorEdge
array). The constant
irReflectThreshold
is the percentage difference in light to detect a reflecting obstacle. The constant
iredgeThreshold
is the percent difference to detect an edge. The default values for these thresholds are 10% for reflection and 90% for edge
detection.
Here is an example assuming the ambient value from analogRead was 1000 with
irReflectThreshold
equal to 10 :
(1000 * 90) / 100 =
90000 / 100 =
900
In this example, if the ambient reading was 1000, the irSensorReflect's threshold reading for object detection is 900, which is
10% below the ambient reading.
The
pingGetDistance
function returns the distance in inches as measured with a ping sensor on the digital pin (
pingPin
) passed to the function. The sound pulse used to measure the distance is triggered by sending a digital pulse that is low for 2
microseconds and high for 5 microseconds. The pin mode is changed from output to input and the
pulseIn
function is used to measure the response from the sensor, which arrives as an incoming pulse width. The formula described at
the beginning of this section is used to convert this value � R Qto the distance.
Proximity Sensor
A PIR (Passive Infrared) sensor can be used to activate your robot when it detects the presence of a nearby person, or even a
dog or cat. The sensor acts like a switch that sends a
HIGH
signal to an Arduino pin when motion is detected (they work by detecting changes in the heat radiated from people or pets).
Figure 8-5
shows the sensor connected to analog pin 5, but you can use any spare pin, such as
A4
instead of
A5
.
Figure 8-5. PIR Sensor Connected to Analog Pin 5
The following
loop
code will spin the robot when movement is detected. If you want your robot to do this, replace the
loop
function in the
myRobot
sketch from
Making the Sketch Easy to Enhance
with the code shown in
Example 8-7
.
Example 8-7. Spinning the bot
void loop()
{
Serial.println("Waiting to detect movement from PIR sensor");
pinMode(A5, INPUT); // configure the pin for input
if(digitalRead(A5) == HIGH)
{
calibrateRotationRate(DIR_LEFT, 360); // spin robot CCW one rotation
}
}
Sound Sensor
You can use a sound sensor to start or stop your ro� S st� bot in response to sound, for example a hand clap or whistle. You
will need a microphone with an amplifier, for example, the BOB-09964 breakout board from SparkFun.
Figure 8-6
shows the board connected to analog pin 4.
Figure 8-6. Sound Sensor Connected to Analog Pin 4
The code that follows is the main tab from the
myRobotSound
sketch available in the download for this book. Noise level above a threshold will drive the robot forward. The robot stops when
the level drops below the threshold. If you need to change the sensitivity, experiment with higher or lower values for the threshold.
Example 8-8
shows the code for the main tab.
Example 8-8. Sound sensor code
/**********************************************************
MyRobotSound.ino
Robot moves when a sound level exceeds a threshold
Based on Recipe 6.7 from Arduino Cookbook
Copyright Michael Margolis 20 July 2012
***********************************************************/
#include <AFMotor.h> // adafruit motor shield library
#include "RobotMotor.h" // 2wd or 4wd motor library
#include "robotDefines.h" // global defines
const int analogInPin = 5; // analog pin the sensor is connected to
const int middleValue = 512; //the middle of the range of analog values
const int numberOfSamples = 128; //how many readings will be taken each time
int sample; //the value read from microphone each time
long signal; //the reading once you have removed DC offset
long averageReading; //the average of that loop of readings
long runningAverage=0; //the running average of calculated values
const int averagedOver= 16; //how quickly new values affect running average
//bigger numbers mean slower
const int threshold=400; //at what level the robot will move
int speed = 50;
// Setup runs at startup and is used configure pins and init system variables
void setup()
{
Serial.begin(9600);
blinkNumber(8); // open port while flashing. Needed for Leonardo only
motorBegin(MOTOR_LEFT);
motorBegin(MOTOR_RIGHT);
}
void loop()
{
int level = getSoundLevel();
if (level > threshold) //is level more than the threshold ?
&� RRRRbr/nbsp; {
motorForward(MOTOR_LEFT, speed);
motorForward(MOTOR_RIGHT, speed);
}else
{
motorStop(MOTOR_LEFT);
motorStop(MOTOR_RIGHT);
}
}
// function to indicate numbers by flashing the built-in LED
void blinkNumber( byte number) {
pinMode(LED_PIN, OUTPUT); // enable the LED pin for output
while(number--) {
digitalWrite(LED_PIN, HIGH); delay(100);
digitalWrite(LED_PIN, LOW); delay(400);
}
}
int getSoundLevel()
{
long sumOfSquares = 0;
for (int i=0; i<numberOfSamples; i++) { //take many readings and average them
sample = analogRead(analogInPin); //take a reading
signal = (sample - middleValue); //work out its offset from the center
signal *= signal; //square it to make all values positive
sumOfSquares += signal; //add to the total
}
averageReading = sumOfSquares/numberOfSamples; //calculate running average
runningAverage=(((averagedOver-1)*runningAverage)+averageReading)/averagedOver;
return runningAverage;
}
See the Arduino Cookbook if you want a detailed description of how this code works.
Arduino Cookbook
For descriptions of how to use lots of additional sensors with Arduino, see:
Arduino Cookbook
by Michael Margolis (O’Reilly).
Hardware Required
Two reflectance sensors are used for edge detection and a third is needed for line following. Although you can use the
stripboard mount (for the three line following sensors) discussed in
Chapter 2
to experiment with edge detection, the robot will perform the edge detection task best with the sensors further apart (the
stripboard approach is best for line following). If the sensors are close together, the robot can have difficulty determining
the best angle to turn when an edge is encountered.
Note
See
Chapter 3, Building the Two-Wheeled Mobile Platform
for details of mounting these sensors on the 2WD chassis and
Chapter 4, Building the Four-Wheeled Mobile Platform
for the 4WD chassis. The principles of reflectance sensors are covered in
Infrared Reflectance Sensors
in
Chapter 8, Tutorial: Introduction to Sensors
.
A reflective surface with non-reflective edges for the edge detection sketch (see
Figure 9-2
. You can use a large sheet of plain white paper with the edges marked using a black marker pen or black electrical tape.
The border should be around 3/4 of in inch thick or so. The optimal surface would be white, but with sufficient friction that
your robot won't slip. Lining paper, often sold as unpasted wall liner, is a great surface. It's designed to provide an even
surface or wallpapering or painting, but with enough texture that makes it great for racing robots.
A reflective surface with a non-reflective line approximately 3/4 inch wide, see
Figure 9-3
. If your surface is at least a couple of feet wide, you can use the same course for edge detection and line following. The
sketches have been tested using a three foot length of 27 inch wide lining paper.
As mentioned in
Chapter 6, Testing the Robot's Basic Functions
, the
lookForObstacle
function enables you to enquire if an obstacle is detected and will return true if so. The
case
statement (see
http://arduino.cc/en/Reference/SwitchCase
) tries to match the
obstacle
variable with one of the obstacle constants (defined in
robotDefines.h)
. If there is a match, the
irEdgeDetect
function is called with relevant sensor and this will return true if an object is detected on that sensor. If no object is detected, the
function returns
OBST_NONE
. The look functionality can be expanded by adding code to the case statement and calling appropriate sensor functions, as you
will see later in this chapter.
But first, let's use the existing functionality to give the robot the ability to follow lines and detect edges.
Edge Detection
Edge detection is one of the easier behaviors to understand and program. The robot moves until it encounters an edge; it should
then change direction to avoid moving over the edge. Edges are detected by using reflectance sensors (see:
Chapter 8, Tutorial: Introduction to Sensors
). Typically, the edge is an area that does not reflect, for example the edge of a table.
In the sketch that follows, the robot will remain within a reflective surface (for example, a large white sheet of paper) that is
bounded by a black line. Black electrical tape (3/4 inch or wider) works well but a black line of similar width drawn with magic
marker or paint can also work as the 'edge'. To avoid damaging your robot, an actual table is not recommended for early
experiments until you are sure you have everything working correctly.
Figure 9-2
shows how the robot responds to moving over an edge. In panel1, the sensors do not detect an edge so the robot moves
forward. In panel 2, the left sensor moves off the reflective surface so the robot stops and rotates 120 degrees. In panel 3, the
robot completes its rotation; panel 4, shows the robot moving forward again.
Figure 9-2. Robot stays within the reflective area
Example 9-2. Main sketch code for edge detection
/******************************************************************************
myRobotEdge.ino
Robot sketch to move within area bordered by a non-reflective line
Michael Margolis 7 July 2012
******************************************************************************/
#include <AFMotor.h> // adafruit motor shield library
#include "RobotMotor.h" // 2wd or 4wd motor library
#include "robotDefines.h" // these were the global defines from myRobot
/// Setup runs at startup and is used configure pins and init system variables
void setup()
{
Serial.begin(9600);
blinkNumber(8); // open port while flashing. Needed for Leonardo only
lookBegin(); /// added Look tab
moveBegin(); /// added Move tab
Serial.println("Ready");
}
void loop()
{
/// code for roaming around and avoiding obstacles
if( lookForObstacle(OBST_FRONT_EDGE) == true)
{
Serial.println("both sensors detected edge");
timedMove(MOV_BACK, 300);
moveRotate(120);
while(lookForObstacle(OBST_FRONT_EDGE) == true )
moveStop(); // stop motors if still over cliff
}
else if(lookForObstacle(OBST_LEFT_EDGE) == true)
{
Serial.println("left sensor detected edge");
timedMove(MOV_BACK, 100);
moveRotate(30);
}
else if(lookForObstacle(OBST_RIGHT_EDGE) == true)
{
Serial.println("right sensor detected edge");
timedMove(MOV_BACK, 100);
&� ZZZZ Qnbsp; moveRotate(-30);
}
else
{
moveSetSpeed(MIN_SPEED);
moveForward();
}
}
// function to indicate numbers by flashing the built-in LED
void blinkNumber( byte number) {
pinMode(LED_PIN, OUTPUT); // enable the LED pin for output
while(number--) {
digitalWrite(LED_PIN, HIGH); delay(100);
digitalWrite(LED_PIN, LOW); delay(400);
}
}
The code for this sketch is derived from the myRobotMove sketch discussed in
Chapter 7, Controlling Speed and Direction
. You can download the example code, locate myRobotEdge, open the sketch and upload it to the robot. Or you can derive the
sketch yourself:
1. Open the myRobotMove sketch in the example code and do a Save As and name it myRobotEdge.
2. Create the Look tab.
3. Locate and move the two functions at the end of the main tab starting from the comment "code to look for obstacles" into
the Look tab. This code is listed in the section:
The Look Code
.
4. Replace the main sketch code with the code listed here:
Example 9-2
.
5. Compile and upload the code
Place the robot within the bounded surface and switch the power on (the robot calibrates the sensors after it is switched on so all
the sensors should be over the reflective area). After a short delay the robot will move forward until it detects a non-reflective
edge.
The loop code checks if an edge is detected directly ahead with both sensors (
OBST_FRONT_EDGE
), or on the left (
OBST_LEFT_EDGE
) or right (
OBST_RIGHT_EDGE
). If the edge was ahead, the robot backs away for 0.3 seconds, rotates 120 degrees and then moves forward again. If the edge
was to the side, the robot turns 30 degrees away from that side and then moves forward. Feel free to experiment with the angles
to get a behaviour that suits the area you have defined for containing your robot.
Is Your Robot Not Moving Right?
If your robot is not rotating enough or too much when attempting to move away from an edge, you may need
to calibrate rotation rates; see
Controlling Motor Speed
. If the robot 'stutters' instead of turning, try increasing the speed by changing the loop code from
moveSetSpeed(MIN_SPEED);
to
moveSetSpeed(MIN_SPEED+10);
.
If the robot does not detect the edge, you can make it more sensitive by reducing the value of
irEdge
Threshold
in the IrSensors tab.
Line Following
Line following is a classic task for a robot. The robot uses sensors to determine its position in relation to a line and follows this
line by moving to keep its sensors centered above the line.
Figure 9-3
shows a robot moving around a track marked with a black line on a white surface.
Example 9-4. Complete listing for code in the myRobotLine main tab
/******************************************************************************
myRobotLine.ino
Robot sketch to follow lines
Michael Margolis 7 July 2012
******************************************************************************/
#include <AFMotor.h> // adafruit motor shield library
#include "RobotMotor.h" // 2wd or 4wd motor library
#include "robotDefines.h" // these were the global defines from myRobot
int speed = MIN_SPEED; // speed in percent when moving along a straight line
/// Setup runs at startup and is used configure pins and init system variables
void setup()
{
Serial.begin(9600);
blinkNumber(8); // open port while flashing. Needed for Leonardo only
lookBegin(); /// added Look tab
moveBegin(); /// added Move tab
lineSenseBegin(); // initialize sensors
Serial.println("Ready");
}
void loop()
{
int drift = lineSense();
&n� ZZZZ absp; lineFollow(drift, speed);
}
// function to indicate numbers by flashing the built-in LED
void blinkNumber( byte number) {
pinMode(LED_PIN, OUTPUT); // enable the LED pin for output
while(number--) {
digitalWrite(LED_PIN, HIGH); delay(100);
digitalWrite(LED_PIN, LOW); delay(400);
}
}
/****************************
Line Sensor code
****************************/
int damping = 5; //1 is most sensitive, range 1 to 1023)
void lineSenseBegin()
{
}
//returns drift - 0 if over line, minus value if left, plus if right
int lineSense()
{
int leftVal = analogRead(SENSE_IR_LEFT);
int centerVal = analogRead(SENSE_IR_CENTER);
int rightVal = analogRead(SENSE_IR_RIGHT);
int leftSense = centerVal - leftVal;
int rightSense = rightVal - centerVal;
int drift = rightVal - leftVal ;
return drift;
}
int lineFollow(int drift, int speed)
{
int leftSpeed = constrain(speed - (drift / damping), 0, 100);
int rightSpeed = constrain(speed + (drift / damping), 0, 100);
motorForward(MOTOR_LEFT, leftSpeed);
motorForward(MOTOR_RIGHT, rightSpeed);
}
The code for this sketch is derived from the myRobotEdge sketch discussed earlier in this chapter. You can download the
example code, locate myRobotLine, open the sketch and upload it to the robot. Or you can derive the sketch yourself:
1. Open the myRobotEdge sketch in the example code and do a Save As and name it myRobotLine.
2. Locate the defines for locations of sensors in the robotDefines tab and add the center sensor following the defines for the
left and right sensors:
const int SENSE_IR_CENTER = 2;
.
3. Replace the main sketch code with the code listed here:
Example 9-4
.
4. Compile and upload the code
Place the robot on the surface with the center sensor above the line and switch the power on. After a short delay the robot will
move forward and track the line. The robots ability to follow the line depends on many factors:
Line thickness - the optimum thickness depends on the spacing of the sensors. 3/4 inch works well with the robot built as
described but you can experiment with different line widths and different sensor spacing.
Sensor height above surface - the sensors are less sensitive when further from the surface- try using spacers to move the
sensors closer to the surface.
Speed - too slow and the robot may not have enough torque, too fast and � ZZZZ athe robot will overshoot the line. Try
running at a speed around 10% above minimum speed if the robot appears to be sluggish - in the top of the main tab,
change the code to :
int speed = MIN_SPEED+10;
.
Robot over sensitive - if the robot follows the line but zig-zags excessively , increase the damping value in the line sensor
code in the main tab. Try larger values until you find a range that works. Note that you may need a different damping value
if you change the speed.
Robot not sensitive enough - if the robot drifts off the line, decrease the damping value in the line sensor code in the main
tab. Try smaller values until you find a range that works. Note that you may need a different damping value if you change
the speed.
int maxRange[] =
int maxRange[] =
{0, 1023, 1023, 1023, 1023, 100, 100};
Hardware Required
Ping distance sensor from Parallax; see
Sonar Distance Sensors
in
Chapter 8, Tutorial: Introduction to Sensors
.
Servo required for myRobotScan; see
Sonar Distance Sensors
in
Chapter 8, Tutorial: Introduction to Sensors
.
Connect the Ping sensor and servo the right way around; the black wires (ground) go nearest the pin marked
-
, the white (or lighter color) signal wire goes nearest the pin mar� ZZZZ p� � ked
S
(
Figure 10-1
).
Figure 10-1. Ping sensor and servo plug into pins on the motor shield
Note
The Distance tab's code is not listed in this chapter, but it is included in the example code (see
How to Contact Us
for information on downloading the example code).
Figure 10-2
shows the modules used in this chapter.
Figure 10-7. Ping sensor with homemade wood mount, not fully assembled
Figure 10-8. Rear view of mount; note nuts used as spacers between PCB and mount
Figure 10-9
and
Figure 10-10
show the mount attached to the robot.
The
roam
function uses information reported by the distance sensor to detect obstacles. The distance sensor code is described in
Sonar Distance Sensors
, the sketches in this chapter contain the code in a new tab named Distance.
The
checkMovement
function introduced in the previous chapter is enhanced here to check for and return
false
if there are obstacles in front when the robot is moving forward.
checkMovement
is called when the robot is taking evasive action during a timed move. You can add additional checks into this function if needed.
For example, if you add sensors to detect an edge to the rear of the robot and added your own code that returned true when this
sensor detected an edge, the logic shown in
Example 10-3
would prevent the robot from going over an edge when backing up to avoid an obstacle in front.
Example 10-3. The checkMovement function
boolean checkMovement()
{
boolean isClear = true; // default return value if no obstacles
if(moveGetState() == MOV_FORWARD)
{
if(lookForObstacle(OBST_FRONT) == true)
{
isClear = false;
}
}
else if(moveGetState() == MOV_BACK)
{
if(lookForObstacle(OBST_REAR_EDGE) == true)
{
isClear = false;
}
}
return isClear;
}
Adding Scanning
In the previous sketch, the robot needs to turn in order to look left and right. Mounting the distance sensor on a servo adds the
ability to rotate the sensor so the robot can 'turn its head' to look around as shown in
Figure 10-14
.
Note
The exact relationship between pulse width and servo angle varies across different servo products. If your servo turns right
when it should turn left, swap the right and left servo angles in the
servoAngles
array:
// servo angles left, right, center
const int servoAngles[] = { 150, 30, 90};
Arduino has a Servo library that can control up to 12 servos, however this is not used in this sketch for two reasons. The Servo
library enables you to send an angle to the servo and carry on executing sketch code while the servo is being moved in the
background, but your code must wait until the servo is facing the desired direction before requesting a reading from the distance
sensor. However, the main reason not to use the Servo library is because it requires exclusive use of one of the Arduino chip's
hardware timers (timer 1) and timers are in short supply on a standard Arduino chip (see
Appendix F
).
The code to control the servo goes in a tab named
Softservo
(see
Example 10-7
).
Example 10-7. The code from the Softservo tab
Example 10-7. The code from the Softservo tab
/*******************************
Softservo.ino
software servo control without using timers
note that these functions block until complete
*******************************/
int servoPin;
void softServoAttach(int pin)
{
servoPin = pin;
pinMode(pin, OUTPUT);
}
// writes given angle to servo for given delay in milliseconds
void softServoWrite(int angle, long servoDelay)
{
int pulsewidth = map(angle, 0, 180, 544, 2400); // width in microseconds
do {
digitalWrite(servoPin, HIGH);
delayMicroseconds(pulsewidth);
digitalWrite(servoPin, LOW);
delay(20); // wait for 20 milliseconds
servoDelay -= 20;
} while(servoDelay >=0);
}
The
softServoAttach
function stores the pin number that the servo is attached to. The
softServoWrite
function converts the desired angle into a pulse width and creates the pulse using
digitalWrite
with a pulse width determined by a call to
delayMicroseconds
. The pulses are sent repeatedly for the duration of the given
servoDelay
which is a period sufficient for the servo to turn to the desired direction.
The
Look
code is similar to the code described at the beginning of this chapter, but here the
lookAt
function calls
softServoWrite
to rotate the servo instead of rotating the entire robot.
Example 10-8
shows the
Look
tab used in the
myRobotScan
sketch.
Example 10-8. The modified Look tab code
/**********************
code to look for obstacles
**********************/
// servo defines
const int sweepServoPin = 9; // pin connected to servo
const int servoDelay = 500; // time in ms for servo to move
const int MIN_DISTANCE = 8; // robot stops when object is nearer (in inches)
const int CLEAR_DISTANCE = 24; // dis� bbbbb> ctance in inches considered attracive to move
const int MAX_DISTANCE = 150; // the maximum range of the distance sensor
// servo angles left, right, center
const int servoAngles[] = { 150, 30, 90};
const byte pingPin = 10; // digital pin 10
void lookBegin()
{
irSensorBegin(); // initialize sensors
softServoAttach(sweepServoPin); /// attaches the servo pin to the servo object
}
// returns true if the given obstacle is detected
boolean lookForObstacle(int obstacle)
{
switch(obstacle) {
case OBST_FRONT_EDGE: return irEdgeDetect(DIR_LEFT) && irEdgeDetect(DIR_RIGHT);
case OBST_LEFT_EDGE: return irEdgeDetect(DIR_LEFT);
case OBST_RIGHT_EDGE: return irEdgeDetect(DIR_RIGHT);
case OBST_FRONT: return lookAt(servoAngles[DIR_CENTER]) <= MIN_DISTANCE;
}
return false;
}
// returns the distance of objects at the given angle
int lookAt(int angle)
{
softServoWrite(angle, servoDelay ); // wait for servo to get into position
int distance, samples;
long cume;
distance = samples = cume = 0;
for(int i =0; i < 4; i++)
{
distance = pingGetDistance(pingPin);
if(distance > 0)
{
// printlnValue(" D= ",distance);
samples++;
cume+= distance;
}
}
if(samples > 0)
distance = cume / samples;
else
distance = 0;
if( angle != servoAngles[DIR_CENTER])
{
Serial.print("looking at dir ");
Serial.print(angle), Serial.print(" distance= ");
Serial.println(distance);
softServoWrite(servoAngles[DIR_CENTER], servoDelay/2);
}
return distance;
}
// function to check if robot can continue moving in current direction
// returns true if robot is not blocked moving in current direction
// this version only tests for obstacles in front
boolean checkMovement()
{
boolean isClear = true; // default return value if no obstacles
if(moveGetState() == MOV_FORWARD)
{
if(lookForObstacle(OBST_FRONT) == true)
{
isClear = false;
}
}
return isClear;
}
// Look for and avoid obstacles using servo to scan
void roam()
{
int distance = lookAt(servoAngles[DIR_CENTER]);
if(distance == 0)
{
moveStop();
Serial.println("No front sensor");
return; // no sensor
}
else if(distance <= MIN_DISTANCE)
{
moveStop();
//Serial.print("Scanning:");
int leftDistance = lookAt(servoAngles[DIR_LEFT]);
if(leftDistance > CLEAR_DISTANCE) {
// Serial.print(" moving left: ");
moveRotate(-90);
}
else {
delay(500);
int rightDistance = lookAt(servoAngles[DIR_RIGHT]);
&� bbbbb conbsp; if(rightDistance > CLEAR_DISTANCE) {
// Serial.println(" moving right: ");
moveRotate(90);
}
else {
// Serial.print(" no clearence : ");
distance = max( leftDistance, rightDistance);
if(distance < CLEAR_DISTANCE/2) {
timedMove(MOV_BACK, 1000); // back up for one second
moveRotate(-180); // turn around
}
else {
if(leftDistance > rightDistance)
moveRotate(-90);
else
moveRotate(90);
}
}
}
}
}
// the following is based on loop code from myRobotEdge
// robot checks for edge and moves to avoid
void avoidEdge()
{
if( lookForObstacle(OBST_FRONT_EDGE) == true)
{
Serial.println("left and right sensors detected edge");
timedMove(MOV_BACK, 300);
moveRotate(120);
while(lookForObstacle(OBST_FRONT_EDGE) == true )
moveStop(); // stop motors if still over cliff
}
else if(lookForObstacle(OBST_LEFT_EDGE) == true)
{
Serial.println("left sensor detected edge");
timedMove(MOV_BACK, 100);
moveRotate(30);
}
else if(lookForObstacle(OBST_RIGHT_EDGE) == true)
{
Serial.println("right sensor detected edge");
timedMove(MOV_BACK, 100);
moveRotate(-30);
}
}
The
lookForObstacle
and
roam
functions are modified from the non-scanning version to use the appropriate servo angles for looking left, right, and center. The
servo angles are stored in the array
servoAngle
(swap the left and right values if your servo turns in the wrong direction). The
lookAt
function now rotates the servo to the desired angle instead of moving the entire robot.
Hardware Required
The TV remote control sketch requires an infrared decoder module. TSOP4838 (or the equivalent PNA4602) modules (
Figure 11-1
) have power and signal pins oriented to enable them to plug directly into the socket on the motor shield.
You will also need an infrared remote control—almost any controller from a TV or DVD player will do.
Figure 11-1. Infrared Decoder Module
If you have a wireless device that passes serial data such as a Bluetooth module, you can wirelessly control the robot by
connecting the serial output of the adapter to the Arduino serial input and wiring up the power leads. If you are using a Leonardo,
note that the TX/RX pins (digital 1 and 0) are accessed through
Serial1
rather than
Serial
, so modify your code accordingly (you'll need to replace all instances of Serial with Serial1 in all the tabs of your sketch).
Figure 11-5. IR Receiver Module with leads twisted to for better fit into socket
The
irReceivePin
is the pin that the module is connected to. This pin is defined at the top of the main sketch tab:
const byte irReceivePin = A3;
// analog pin 3
The Arduino analog input pins can also be used as digital pins, but the pin numbers are not the same—analog pin 3 is not digital
pin 3! The
A3
constant is the Arduino way of referring to the digital pin number associated with the analog input (the
irrecv
object expects the digital pin number). On Leonardo, analog input 3 is used as digital input 21; on a standard ATmega328
board like the Uno, A3 is digital pin 17. If you use Arduino constants to refer to the digital pin assignments for the analog input
pins, the correct values will automatically be assigned.
A numeric value is provided for each remote keypress detected. The specific key values decoded will depend on the remote
controller you use.
Example 11-3
shows code for the
Remote
tab with support for the IR receiver.
Example 11-3. The Remote tab code
// robot remote commands
#include <IRremote.h> // IR remote control library
IRrecv irrecv(irReceivePin);
decode_results results;
// Command constants
const char MOVE_FORWARD = 'f'; // move forward
const char MOVE_BACK = 'b'; // move backward
const char MOVE_LEFT = 'l'; // move left
const char MOVE_RIGHT = 'r'; // move right
const char PIVOT_CCW = 'C'; // rotate 90 degrees CCW
const char PIVOT_CW = 'c'; // rotate 90 degrees CW
const char PIVOT = 'p'; // rotation angle (minus rotates CCW)
const char HALT = 'h'; // stop
// not used in this example
const char MOVE_SPEED = 's';
const char MOVE_SLOWER = 'v'; // reduce speed
const char MOVE_FASTER = '^'; // increase speed
//IR remote keycodes:replace this with codes for your remote
// See text for procedure for obtaining codes.
const long IR_MOVE_FORWARD = 1064;
const long IR_MOVE_BACK = 3112;
const long IR_MOVE_LEFT = 1128;
const long IR_MOVE_RIGHT = 2152;
const long IR_PIVOT_CW = 136;
const long IR_PIVOT_CCW = 1160;
const long IR_HALT = 2216� jjjjj `��;
int commandState = MOV_STOP; // what robot is told to do
void remoteBegin(byte irPin)
{
irrecv.enableIRIn(); // Start the receiver
}
void remoteService()
{
if (irrecv.decode(&results))
{
if (results.decode_type != UNKNOWN)
{
//Serial.println(results.value); // uncomment to see raw result
convertIrToCommand(results.value);
}
irrecv.resume(); // Receive the next value
}
// additional support for serial commands
if(Serial.available() )
{
int cmd = Serial.read();
processCommand(cmd);
}
}
void convertIrToCommand(long value)
{
{
switch(value)
{
case IR_MOVE_LEFT : processCommand(MOVE_LEFT); break;
case IR_MOVE_RIGHT : processCommand(MOVE_RIGHT); break;
case IR_MOVE_FORWARD : processCommand(MOVE_FORWARD); break;
case IR_MOVE_BACK : processCommand(MOVE_BACK); break;
case IR_PIVOT_CCW : processCommand(PIVOT_CCW); break;
case IR_PIVOT_CW : processCommand(PIVOT_CW); break;
case IR_HALT : processCommand(HALT); break;
// case IR_SLOWER : processCommand(SLOWER); break;
// case IR_FASTER : processCommand(FASTER); break;
}
}
}
void changeCmdState(int newState)
{
if(newState != commandState)
{
Serial.print("Changing Cmd state from "); Serial.print( states[commandState]);
Serial.print(" to "); Serial.println(states[newState]);
commandState = newState;
}
}
void processCommand(int cmd)
{
int val = 0;
if( cmd == MOVE_SPEED) {
val = Serial.parseInt();
}
else if( cmd == PIVOT) {
val = Serial.parseInt();
}
processCommand(cmd, val);
}
void processCommand(int cmd, int val)
{
byte speed;
//Serial.write(cmd); // uncomment to echo
switch(cmd)
{
case MOVE_LEFT : changeCmdState(MOV_LEFT); moveLeft(); break;
case MOVE_RIGHT : changeCmdState(MOV_RIGHT); moveRight(); break;
case MOVE_FORWARD : changeCmdState(MOV_FORWARD); moveForward(); break;
case MOVE_BACK : changeCmdState(MOV_BACK); moveBackward(); break;
case PIVOT_CCW : changeCmdState(MOV_ROTATE); moveRotate(-90); break;
case PIVOT_CW : changeCmdState(MOV_ROTATE); moveRotate(90); break;
case PIVOT : changeCmdState(MOV_ROTATE); moveRotate(val); break;
case HALT : changeCmdState(MOV_STOP); moveStop(); break;
case MOVE_SPEED : speed = val; moveSetSpeed(speed); break;
// case SLOWER � jjjjj `��: moveSlower(speedIncrement); break;
// case FASTER : moveFaster(speedIncrement); break;
case '\r' : case '\n': break; // ignore cr and lf
default : Serial.print('['); Serial.write(cmd); Serial.println("] Ignored"); break;
}
}
The
remoteBegin(irReceivePin);
function called in
setup
initializes the IRdecode library.
ireReceivePin
is the pin the ir decoder module is connected to, in this case, analog pin 3. Because the library expects a digital pin number, the
Arduino constant
A3
is used.
Planning
Think Before You Code
It helps to think about your project and be clear on what you want it to achieve before you start coding. Tinkering around without a
plan is a good way to learn and to have fun, but it can make a large project too cumbersome to manage.
Simplify
Spending time simplifying code will be repaid in reduced debug time. Complex code can be difficult to debug or enhance,
particularly when you come back to it after a while. Looking at each completed function with an eye to seeing if there is a simpler
way o� ssim `� � � � f achieving the functionality can result in cleaner code that is easier to maintain.
Experiment
If what you have tried isn't working, try something new. Software problems may actually be a hardware issue (and vice versa).
Be Tenacious
Interesting projects usually come with difficult problems—overcoming these is part of the reward for a job well done.
Have Fun
Isn't that why you started this project in the first place?
Ardumoto
This popular H-bridge can be used instead of the Adafruit shield described in the text if you have a two wheeled robot (the shield
only supports two motors). It also lacks the convenient layout for the analog sensors and you will need to add two 3 pin headers
for the servo and distance sensor connections. The Motor code for Ardumoto is shown in
Example B-1
.
Note
This code uses the Servo library. If you want to build the infrared remote control project with continuous rotation servos,
you will need to ensure that the
IRremote
library is configured to use a timer other than Timer 1) because the Servo library requires Timer 1. See
Modifying a Library to Change Timer Allocation
for timer usage and details on how to configure timers for the
IRremote
library.
Example B-1. RobotMotor library code for th� zzzzz qervo)e Ardumoto shield
/*******************************************************
RobotMotor.cpp // Ardumoto version
low level motor driver for use with ardumoto motor shield and 2WD robot
Michael Margolis May 8 2012
********************************************************/
#include <Arduino.h>
#include "RobotMotor.h"
const int differential = 0; // % faster left motor turns compared to right
/****** motor pin defines *************/
// Pins connected to the motor driver. The PWM pins control the speed, and the
// other pins are select forward and reverse
// Motor uses pins : 3,11,12,13
const byte M_PWM_PIN[2] = {11,3}; // ardumoto v13
const byte M_DIR_PIN[2] = {13,12};
/* end of motor pin defines */
int motorSpeed[2] = {0,0}; // motor speed stored here (0-100%)
// tables hold time in ms to rotate robot 360 degrees at various speeds
// this enables conversion of rotation angle into timed motor movement
// The speeds are percent of max speed
// Note: low cost motors do not have enough torque at low speeds so
// the robot will not move below this value
// Interpolation is used to get a time for any speed from MIN_SPEED to 100%
const int MIN_SPEED = 40; // first table entry is 40% speed
const int SPEED_TABLE_INTERVAL = 10; // each table entry is 10% faster speed
const int NBR_SPEEDS = 1 + (100 - MIN_SPEED)/ SPEED_TABLE_INTERVAL;
int speedTable[NBR_SPEEDS] = {40, 50, 60, 70, 80, 90, 100}; // speeds
int rotationTime[NBR_SPEEDS] = {5500, 3300, 2400, 2000, 1750, 1550, 1150}; // time
void motorBegin(int motor)
{
pinMode(M_DIR_PIN[motor], OUTPUT);
motorStop(motor);
}
// speed range is 0 to 100
void motorSetSpeed(int motor, int speed)
{
motorSpeed[motor] = speed; // save the value
speed = map(speed, 0,100, 0,255); // scale to PWM range
analogWrite(M_PWM_PIN[motor], speed); // write the value
}
void motorForward(int motor, int speed)
{
digitalWrite(M_DIR_PIN[motor], HIGH);
motorSetSpeed(motor, speed);
}
void motorReverse(int motor, int speed)
{
digitalWrite(M_DIR_PIN[motor], LOW);
motorSetSpeed(motor, speed);
}
void motorStop(int motor)
{
analogWrite(M_PWM_PIN[motor], 0);
}
void motorBrake(int motor)
{
// Ardumoto does not support brake, so just stop the motor
analogWrite(M_PWM_PIN[motor], 0);
}
The Arduino code that sends the data is in the tab named
DataDisplay
(
Example C-2
), you can copy the code into any sketch you want to debug, or you can simply add the tab to the sketch.
Example C-2. the DataDisplay tab code
// DataDisplay
void dataDisplayBegin(int nbrItems, char* labels[], int minRange[], int maxRange[] )
{
for(int i = 1; i < nbrItems; i++)
{
sendLabel(i, labels[i]);
sendRange(i, minRange[i], maxRange[i]);
}
}
void sendLabel( int row, char *label)
{
sendString("Label"); sendValue(row); sendString(label); Serial.println();
}
void sendRange( int row, int min, int max)
{
sendString("Range"); sendValue(row); sendValue(min); sendValue(max); Serial.println();
}
This sketch talks to Arduino using the serial port and you need to ensure that the Processing sketch is using the same port that
is connected to your robot. The port Arduino uses is displayed on in the Arduino IDE. You set the Processing port by changing
the value of the variable
portIndex
. When starting the Processing sketch, you will see a list of the ports on your computer.
portIndex
is the position of the Arduino port in this list, but note that the index starts from 0, so the default value of 1 for
portIndex
is for the second port in the list.
A robot tethered via USB is not very convenient when you want to see what the robot is doing while moving. Adding a wireless
serial device such as Bluetooth or XBee can be a big help when debugging or tuning your robot. If you are using a Leonardo,
note that the TX/RX pins (digital 1 and 0) are accessed through
Serial1
rather than
Serial
, so modify your code accordingly (you'll need to replace all instances of Serial with Serial1 in all the tabs of your sketch).
A standard board like the Uno uses the same
Serial
object as USB and although you don't need to modify the example code, you will need to disconnect the wireless device from the
pins when uploading code. This is because the wireless device uses the same pins (digital 1 and 0) as USB.
<������ p�� /div>
Note
Here is the voltage divider formula for these resistor values:
R2 / R1 + R2
. Substituting the chosen values results in:
2200/(18000 + 2200)
= 0.109
Therefore the voltage on the terminal will be the battery voltage times 0.109. For example, 10 volts at the battery will be dropped
to just under the 1.1 volt range of the internal reference.
The resistors can be attached to the battery terminals as shown in
Figure D-2
, but a more permanent solution is to solder the resistors to the shield as shown in
Figure D-3
and
Figure D-4
.
Figure D-2. Resistors added to shield to monitor battery
Figure D-3. Voltage Divider Resistors soldered to Vin and Gnd pins
Figure D-4. Voltage Divider Resistors soldered to Vin and Gnd pins
The code to read and interpret the voltage is in the
Battery
tab (
Example D-1
). This code reads the output of the voltage divider using
analogRead
and converts this into the battery voltage expressed in millivolts. This is compared to preset thresholds levels so an LED can be
flashed to indicate low and critical battery levels. The code can also detect if the optional charger plug is connected to stop robot
movement while being recharged.
Example D-1. Battery tab code
// code to monitor battery voltage
/***������ q***************************************************************
* LED starts flashing when volage drops below warning level
* mark space ratio increses from 10% to 50% as voltage decreses from warning to critical
* robot shuts down when battery below critical and led flashes SOS
*
* LED mark space ratio changes from 10% to 90% as voltage increases to full
*****************************************************************/
If you have not wired the robot to use a charger, then call
batteryBegin
with two parameters: the pin that the voltage divider is connected to and the LED pin:
batteryBegin(alogBatteryPin, ledPin)
The
myrobotBatteryMonitor
example sketch (
Example D-2
) in the download shows how to use the battery monitor function.
Example D-2. Battery monitor example sketch
/******************************************************************************
myRobotBatteryMonitor.ino
sketch to demonstrate battery voltage monitoring
based on myRobotWander
Robot wanders using forward scanning for obstacle avoidance
LED blinks when battery runs low, robot goes to sleep when battery is critical.
Created by Michael Margolis 22 July 2012
******************************************************************************/
#include "robotDefines.h" // global defines
#include <AFMotor.h> // adafruit motor shield library
#include "RobotMotor.h" // 2wd or 4wd motor library
const int ledPin = 13; // onboard LED
const int alogBatteryPin = 5; // input on analog 5
const int chargerDetectedPin = 2; // digital pin 2
// Setup runs at startup and is used configure pins and init system variables
void setup()
{
Serial.begin(9600);
blinkNumber(8); // open port while flashing. Needed for Leonardo only
lookBegin();
moveBegin();
//batteryBegin(alogBatteryPin, ledPin);
batteryBegin(alogBatteryPin, ledPin, chargerDetectedPin);
pinMode(ledPin, OUTPUT);
Serial.println("Ready");
}
void loop()
{
// roam();
batteryCheck();
}
// function to indicate numbers by flashing the built-in LED
void blinkNumber( byte number) {
pinMode(LED_PIN, OUTPUT); // enable the LED pin for output
while(number--) {
digitalWrite(LED_PIN, HIGH); delay(100);
digitalWrite(LED_PIN, LOW); delay(400);
}
}
Trickle Charging
The build chapters in the beginning of the book described a simple trickle charger that you can use to recharge NiMH batteries.
This section describes how to use the charger as well as some important points to ensure that you don't damage your batteries.
Trickle charging is a method of recharging NiMH batteries that provides a slow but steady charging current which should fully
recharg�� fu te 5 AA cells in around 14 to 16 hours. The charger has been designed for cells with a rated capacity of 2000
to 2500 mAh (milliampere hours). Cells with a higher rating can be used but they will require a longer charging period.
Warning
Do not try to charge non-rechargeable batteries.
The batteries start charging when a DC power supply is plugged into the charging socket and the power switch is turned on. The
charging circuit is designed for use with a 12 volt supply with a 2.1mm plug (positive on the center connector). Cells with the
suggested rating should handle the trickle charge current for long periods, however it is good practice to keep your charge
session to 24 hours or less, particularly if your DC supply could be delivering a little more than the recommended 12 volts.
Digital I/O
pinMode(pin, mode);
Configures a digital pin to read (input) or write (output) a digital value; see
http://arduino.cc/en/Reference/PinMode
digitalRead(pin);
Reads a digital value (HIGH or LOW) on a pin set for input; see
http://arduino.cc/en/Reference/DigitalRead
digitalWrite(pin, value);
Writes the digital value (HIGH or LOW) to a pin set for output; see
http://arduino.cc/en/Reference/DigitalWrite
pulseIn(pin, pulseType, timeout);
Returns the pulse width in microseconds of a changing digital signal on the given pin.
pulseType
(either HIGH or LOW) determines if duration is for a high or low pulse.
timout
is an optional value indicating how long to wait for a pulse (the default is one second); see
http://arduino.cc/en/Reference/PulseIn
Analog I/O
analogRead(pin);
Reads a value from the specified analog pin. The value ranges from 0 to 1023 for voltages that range from 0 to
the reference voltage (5 volts by default, but can be changed by using analogReference; see
http://arduino.cc/en/Reference/AnalogRead
analogReference(type);
Configures the reference voltage used for analog input. This is used in the battery monitor code discussed in
Appendix D
; see
http://arduino.cc/en/Reference/AnalogReference
Math functions
min(x,y);
Returns the smaller of two numbers; see
http://arduino.cc/en/Reference/Min
max(x,y);
Returns the larger of two numbers; see
http://arduino.cc/en/Reference/Max
constrain(x,lower,upper);
Constrains the value of x to be between the lower and upper range; see
http://arduino.cc/en/Reference/Constrain
map(x,fromLow,fromHigh,destLow,destHigh);
Scales a value from one range to another range. The result will have the same proportion within the destination
range as in the source range. The following code scales the analogRead value to a percentage of the full scale
reading:
int val = analogRead(0);
int percent = map(val, 0,1023, 0,100)
See
http://arduino.cc/en/Reference/Map
The
break
statement is necessary to prevent execution falling through to the following case statement. See
http://arduino.cc/en/Reference/SwitchCase
array
An array is a collection of variables accessed using an index number. The first element of an Arduino array is
accessed using an index of 0. An array can be initialized when it is declared by placing values in curly
brackets. The following declares an array named
motorSpeed
with two elements that will store the speed for the left and right motors and initialize the speed values to 0:
const int NUMBER_OF_MOTORS = 2;
int motorSpeed[NUMBER_OF_MOTORS] = {0,0}; // motor speed stored here (0-100%)
see:
http://arduino.cc/en/Reference/Array
#include "header.h"
This makes functions and variables declared in the specified file available to your sketch. See
http://arduino.cc/en/Reference/Include
Appendix F. Arduino Pin and Timer Usage
The tables in this section show the pin and timer resources used by the projects in this book. You can use the same pin
assignments for the Leonardo boards or the standard ATmega328 boards such as the Uno. However, there are subtle low level
differences between these boards, so if you are adding capabilities that use additional pins or resources beyond those
described in this book, then check the documentation on pin and resource usage for your board.
The first code fragment determines the timer to be used with a Leonardo board (the Arduino build process will use the
c�� wil le="Chapt `ode in this fragment if the chip is an
ATmega32U4
). The uncommented line contains:
#define IR_USE_TIMER4_HS
which results in the library using Timer 4. However, Timer 4 is also used to control one of the motors in the 4WD robot. If you
have the 4WD robot and want to use the infrared remote control library, you need to find a free timer to use. You can't easily
change the motor library because the pin for Timer 4 is hard wired to the motor controller chip. But you can change the remote
timer by commenting out the line for Timer 4 and uncommenting a line that enables a free timer. The Leonardo has 5 timers but
as shown in
Table F-2
, only Timer 1 is available. The code to disable Timer 4 and enable Timer 1 is as follows:
// Leonardo or Teensy 2.0
#elif defined(__AVR_ATmega32U4__)
#define IR_USE_TIMER1 // tx = pin 14
// #define IR_USE_TIMER3 // tx = pin 9
// #define IR_USE_TIMER4_HS // tx = pin 10
Brian Jepson
Revision History
2012-09-12 First release
2012-10-03 Second release
��