Hallberg Gary - Building A Simple Rover (Arduino Short Reads. Book 5) - 2021
Hallberg Gary - Building A Simple Rover (Arduino Short Reads. Book 5) - 2021
Gary Hallberg
The idea underpinning the Arduino short reads series is to provide a comprehensive, easy to follow
tutorial set and reference guide for anybody wanting to learn about Arduino and basic electronics.
Having a short reads series means that students and hobbyists can select key topics of interest in the
field with minimal outlay. The series aims to provide an easy introduction to all topics of interest and
expand on these topics to provide a broader and deeper understanding of that focus area. The books
are currently only available in Kindle format to provide a very inexpensive package backed up by video
content and interactive social media.
The series is aimed at youngsters and adults interested in getting into electronics and it takes a modern
approach, combining the use of the inexpensive software driven Arduino controller board, with a
multitude of sensors and discreet electronic components. The experiments in this series of books are
easy to follow, inexpensive to implement and compelling for all interested in STEM education. I hope
to inspire anybody looking for a future career in technology or to simply to have fun.
The first book of this series looks at the Arduino microcontroller and explains its operation and purpose.
Experiments look to show you how to set up the programming environment and drive LEDs as well as
read input from switches, thermistors and photocells. Book 1 will give you a solid foundation of how
some basic electronic components work and how to use them to make simple designs.
Books 6 to 8 in this Arduino short reads series are still being written but the series focuses on the
following:
If you find this series of books useful then please leave your review and rating on
Amazon.
Follow North Border Tech Training on Facebook and Twitter for the latest news and
insights as to how this series will develop.
Foreword
Book 1 of this series sets out to provide a basic understanding of the Arduino platform, how to program
it and how to interface simple electronics. This book takes a different tack to the others in so far that it
is more project based. Those who have followed the previous books will be able to apply that knowledge,
but there is also enough information presented to allow those who are new to this series to understand
and follow the experiments provided you have a basic understanding of Arduino.
In this book we will build a simple autonomous vehicle. We will also look at utilizing Arduino shields
for the first time. The code for the rover will be very modular such that the code for any operational
function can be substituted with more sophisticated code. You will have a great test bed for developing
your skills and applying them to a popular area of STEM education that is both fun and rewarding.
Our rover will have the ability to follow lines, avoid objects and we will build a PC based app with
‘Processing’ to control the rover via Bluetooth.
I have no doubt the skills learnt here will allow you to develop more engaging and useful projects.
Without further delay let us get into the content.
Prerequisites for this Book
This book assumes you have read Book 1 of the series (‘First Steps with Arduino’) or you already have
some experience of the Arduino platform, how to set up the IDE and undertake some basic Arduino
programming tasks. Basic functions such as ‘digitalRead’ and ‘digitalWrite’ are not covered here
but are explained in Book 1. We will call on some of the material covered in Books 3 and 4 as motor
control and object avoidance play a key part in our rover build, and so I will touch on some key aspects
of these subjects so that anybody starting with this book of the series, and does not have the relevant
knowledge, can still follow the experiments.
Download the Code
You can download the Arduino Code for the experiments in this book from GitHub using the link below.
https://github.com/ghallberg-nbtt/congenial-tribble
I recommend that you do this as I have removed some of the comments from the code in the book
listings to aid readability in print form. I strongly recommend that you comment your code heavily to
facilitate understanding and to become familiar with best engineering practice.
Chapter 1: The Chassis, Motors, Power and
Basic Components
In this chapter we will look at the foundation for your rover. You will need a chassis, a pair of geared
DC motors and a decent battery power source. We will also look at some of the motor control electronics
and sensors that you will need. We will not look at these in much detail here but will cover them all in
the following chapters.
The Chassis
I will be using an extremely common and inexpensive chassis. This ships with geared DC motors
suitable for the chassis and the kit has the benefit of component mounting holes already in place. A
photograph of the kit is shown in Figure 1-1.
Over-voltage of DC Motors
A pair of 18650 cells would deliver 7.4V. This has the potential to supply a voltage higher than the
maximum rated for the 6V motors. In general, we will not be running the motors at full speed and so
the average voltage supplied to the motors will be much less than the maximum rated voltage. However,
it is good practice to measure the output voltage with a meter with the battery under load. The load is
applied by switching on the unit. In my setup the output voltage of the battery was 8V without load and
6.4V under load, so I have no issue running the motors at this voltage. If you want to drop the voltage,
and I will for the servo in Chapter 5, you can use a diode or a few diodes as in Figure 1-5. When you pass
a current through the diode the voltage across that diode will drop by about 0.6V. Therefore, you can
use a few diodes to drop the voltage. You need to ensure that the diode can safely pass the maximum
current of the circuit. Our DC motors draw about 160mA and so a 1N4001 diode is fine to use.
Figure 1-5: Using Diodes to drop a Supply Voltage
Image source: The author
The Sensors
Figure 1-6 shows the collection of sensors that I will be using. We will cover these in detail in later
chapters. There is the HC-05 Bluetooth module, a pair of TCRT5000 based sensors for line following
and an HC-SR04 ultrasonic range finding sensor for obstacle avoidance.
Summary
In this chapter we explored the desired functionality of the rover and took a brief look at all the major
components. You were introduced to the concepts of Arduino shields.
Chapter 2: The Motor Drive System
In this chapter we will look at how to drive the chassis by controlling the speed and direction of each
motor. We will explore the power electronics needed to protect your Arduino board. We will look at the
H-bridge to control direction and how to use Pulse Width Modulation to control the speed of each
motor.
Transistors
You can consider a transistor as a voltage-controlled switch. They also have applications as amplifiers,
but we will not cover that area in this book. Transistors come in many forms, but we will be using the
most common Bipolar Junction Transistor (BJT). Figure 2-2 shows the symbol for a BJT transistor.
They come in two forms NPN and PNP. This example uses the NPN type.
• 2 TinkerKit connectors for two Analog Inputs (in white), connected to A2 and A3
• 2 TinkerKit connectors for two Aanlog Outputs (in orange in the middle), connected to PWM
outputs on pins D5 and D6
• 2 TinkerKit connectors for the TWI interface (in white with 4 pins), one for input and the other
one for output
The shield has fixed pin connections to the Arduino board as shown in Table 2-1.
Function pins per Ch. A pins per Ch. B
Direction D12 D13
PWM D3 D11
Brake D9 D8
Current Sensing A0 A1
void setup() {
//Set digital control pins as output
pinMode (MOTOR_A_DIRECTION, OUTPUT);
pinMode (MOTOR_A_BRAKE, OUTPUT);
pinMode (MOTOR_B_DIRECTION, OUTPUT);
pinMode (MOTOR_B_BRAKE, OUTPUT);
//Ensure motors are stopped
brake ("right");
brake ("left");
delay (4000); // Pause before starting sequence
}
void loop() {
//forward for 2 seconds at full speed
forward ("right", 255);
forward ("left", 255);
delay (2000);
//reverse for 2 seconds at full speed
reverse ("right", 255);
reverse ("left", 255);
delay (2000);
//forward hard left turn at full speed
forward ("right", 255);
brake ("left");
delay (2000);
//reverse hard left turn at full speed
reverse ("right", 255);
brake ("left");
delay (2000);
//forward hard right turn at full speed
forward ("left", 255);
brake ("right");
delay (2000);
//reverse hard right turn at full speed
reverse ("left", 255);
brake ("right");
delay (2000);
//forward for 2 seconds at low speed
forward ("right", 80);
forward ("left", 80);
delay (2000);
//reverse for 2 seconds at low speed
reverse ("right", 80);
reverse ("left", 80);
delay (2000);
// stop
brake ("right");
brake ("left");
delay (2000);
}
Summary
In this chapter you learnt about the power constraints of the Arduino board and why you need extra
electronics to control the motor. You learnt how to use a transistor as a voltage-controlled switch to
provide a means to power the motor. You learnt about the principle of Pulse Width Modulation and
how it can be used to control the speed of the motor. You also learnt about the H-Bridge and how this
device can be used to control the direction of a DC motor. On the programming side you learnt how to
write functions to control the motor and how they can be called in turn to drive and turn a vehicle in all
directions.
Chapter 3: The Application Framework
Our rover has different operating modes. These are line following, obstacle avoidance and remote
control. We need a software framework to select between these modes and provide modularity such
that it is easy to swap out the software for any given operational mode and so provide a decent test bed
for improving and enhancing your rover in the future. In this chapter we will build the application
framework.
void setup() {
//Set digital control pins as output
pinMode (MOTOR_A_DIRECTION, OUTPUT);
pinMode (MOTOR_A_BRAKE, OUTPUT);
pinMode (MOTOR_B_DIRECTION, OUTPUT);
pinMode (MOTOR_B_BRAKE, OUTPUT);
pinMode (SWITCH, INPUT_PULLUP);
pinMode (LED, OUTPUT);
//Ensure motors are stopped
brake ("right");
brake ("left");
digitalWrite (LED, LOW); //Turn off LED
delay (2000); // Pause before starting
Serial.begin (9600);
}
void loop() {
//read switch and debounce
currentButton = digitalRead(SWITCH); // Read the switch state
if (previousButton != currentButton) // If switch pressed
{
delay(5); // Wait 5ms
currentButton = digitalRead(SWITCH); // Read switch again
}
You will notice that the final piece of code in the block sets the ‘previousButton’ to the
‘currentButton’. If we did not do this, the code in the ‘transition block’ would execute over the entire
time the switch is in a ‘LOW’ state rather than just the ‘HIGH’ to ‘LOW’ transition.
We have three tasks to do on the button press and the first ‘switch’ and ‘case’ needs to be placed
within the transition block indicated above. First, we increment the function and reset it to 1 if the value
is 5. We then call the first ‘switch’ and ‘case’ to flash the LED. We do this by calling a function and
passing the number of flashes as an argument. Finally, we want to set the ‘printOnce’ flag to ‘false’
as the button press indicates updated information we will print to the serial monitor.
printOnce = false;
//Increment function selection
function++; // increment function by 1
if (function == 5) {
function = 1; //allow the function to cycle 1 thru 4
}
//Flash LED
switch (function) {
case 1:
//Standby
//1 LED flash
flashLED (function);
break;
case 2:
//Line follower
//2 LED flashes
flashLED (function);
break;
case 3:
//Object avoidance
//3 LED flashes
flashLED (function);
break;
case 4:
//Bluetooth control
//4 LED flashes
flashLED (function);
break;
}
}
The next ‘switch’ and ‘case’ statement calls the respective code block that will control the rover
function. For the standby function we can simply apply the brakes to each motor. To test the code, we
simply print the selected function to the serial monitor and only do this once by check that the
‘printOnce’ flag is set to ‘false’ and then set it to ‘true’. It will be set to ‘false’ again on each button
press.
switch (function) {
case 1:
//Standby
brake ("right");
brake ("left");
if (!printOnce) {
Serial.println ("Function 1 - Standby");
}
printOnce = true;
break;
case 2:
//Line follower
if (!printOnce) {
Serial.println ("Function 2 - Line Follower");
}
printOnce = true;
break;
case 3:
//Object avoidance
if (!printOnce) {
Serial.println ("Function 3 - Object Avoidance");
}
printOnce = true;
break;
case 4:
//Bluetooth control
if (!printOnce) {
Serial.println ("Function 4 - Bluetooth Control");
}
printOnce = true;
break;
}
The code should generate an output like the one shown in Figure 3-3.
Figure 3-3: The Serial Monitor Output for the Application Framework
Image source: The author
Summary
In this chapter you used the ‘switch’ and ‘case’ statement to create a framework for each operating
function for the rover. You used a pushbutton switch and an LED to change and indicate the selected
the function. You learnt how to debounce a switch in software.
Chapter 4: Line Following
In this chapter we will look at how we can add a line following capability to the rover. This application
has uses in manufacturing to allow automated vehicles to follow a predefined route.
Figure 4-3: The Layout to Test the TCRT5000 Line Following Sensor
Image source: The author
The Digital Output (DO) pin of the TCRT5000 sensor is connected to I/O pin 5 and the Analog Output
(AO) is connected to Analog in A2 of the Arduino. A0 and A1 are used by the motor shield although we
have not enabled them to date.
We will test the sensor by applying some black insulation tape to a piece of white paper. We want to
explore the distances at which the sensor triggers the DO output and look at the range of analog output
signals from the AO pin.
Next type in or copy the Sketch as shown in Listing 4-1.
/*
TCRT5000 Test
Copyright 2020 Gary Hallberg
Licensed under MIT https://github.com/ghallberg-nbtt/congenial-
tribble/blob/master/LICENSE
*/
void setup() {
pinMode (DO_PIN, INPUT); //Read DO from sensor
Serial.begin (9600);
}
void loop() {
int DO = digitalRead (DO_PIN);
int A0 = analogRead (AO_PIN);
if (DO == HIGH) {
Serial.print ("DO HIGH - sensor not triggered. ");
} else {
Serial.print ("DO LOW - sensor triggered. ");
}
Serial.print ("Analog Output: ");
Serial.println (A0);
delay (250);
}
Listing 4-1: The Sketch to Test the TCRT5000 Line Following Sensor
Listing source: The author
Make note of the height that the sensor triggers and adjust the potentiometer to explore how the
sensitivity affects the performance of the sensor. Even experiment how different colored materials
affect readings. My experimental setup is shown in Figure 4-4.
Figure 4-4: My Experimental Setup to Test the TCRT5000 Line Following Sensor
Image source: The author
I discovered that the sensor either triggers continuously when the pot is adjusted fully anti-clockwise
or does not trigger at all if the potentiometer is adjusted fully clockwise. I got the sensor to work
properly at a maximum height of 38mm. I was able to trim the potentiometer more clockwise and get
it to work at a suitable height for mounting on my chassis. I did not find much difference when the color
of the background material was changed from white to a darker color.
You can choose to use a black background and white line or vice versa simply by choosing which DO
output state is corresponds to that color.
Your serial monitor output should look like that in Figure 4-5.
Figure 4-5: The Serial Monitor Output when Testing the TCRT5000 Line Following Sensor
Image source: The author
void setup() {
//Set digital control pins as output
pinMode (MOTOR_A_DIRECTION, OUTPUT);
pinMode (MOTOR_A_BRAKE, OUTPUT);
pinMode (MOTOR_B_DIRECTION, OUTPUT);
pinMode (MOTOR_B_BRAKE, OUTPUT);
pinMode (SWITCH, INPUT_PULLUP);
pinMode (LED, OUTPUT);
pinMode (RIGHT_SENSOR, INPUT);
pinMode (LEFT_SENSOR, INPUT);
//Ensure motors are stopped
brake ("right");
brake ("left");
digitalWrite (LED, LOW); //Turn off LED
delay (2000); // Pause before starting
Serial.begin (9600);
}
void loop() {
//read switch and debounce
currentButton = digitalRead(SWITCH); // Read the switch state
if (previousButton != currentButton) // If switch pressed
{
delay(5); // Wait 5ms
currentButton = digitalRead(SWITCH); // Read switch again
}
void followLine() {
//read state of sensors
boolean lineDetectedRight = digitalRead (RIGHT_SENSOR);
boolean lineDetectedLeft = digitalRead (LEFT_SENSOR);
if (!lineDetectedRight && !lineDetectedLeft) {
//Drive forward
forward ("right", 60);
forward ("left", 60);
} else if (lineDetectedRight && !lineDetectedLeft) {
//Turn right
brake ("right");
forward ("left", 60);
} else if (!lineDetectedRight && lineDetectedLeft) {
//Turn left
forward ("right", 60);
brake ("left");
} else {
//Both sensors trigger - stop!
brake ("right");
brake ("left");
}
}
Inside the ‘switch’ and ‘case’ for the rover function ‘2’, we added a call to a function with the following
line of code.
followLine();
The code within this function follows:
void followLine() {
//read state of sensors
boolean lineDetectedRight = digitalRead (RIGHT_SENSOR);
boolean lineDetectedLeft = digitalRead (LEFT_SENSOR);
if (!lineDetectedRight && !lineDetectedLeft) {
//Drive forward
forward ("right", 60);
forward ("left", 60);
} else if (lineDetectedRight && !lineDetectedLeft) {
//Turn right
brake ("right");
forward ("left", 60);
} else if (!lineDetectedRight && lineDetectedLeft) {
//Turn left
forward ("right", 60);
brake ("left");
} else {
//Both sensors trigger - stop!
brake ("right");
brake ("left");
}
}
First, we read the state of the right-hand and left-hand sensors and assign the values to Boolean
variables. If both sensors return a ‘LOW’ state, then the Boolean variable are both ‘false’ and we drive
the vehicle forward. Note that the speed is low. This should be set to just above the minimum that will
move the vehicle forward. If the speed is too high, momentum will cause the rover to overshoot and
veer off the line.
If the right-hand sensor triggers, then the Boolean variable ‘lineDetectedRight’ will be ‘true’
whilst ‘lineDetectedLeft’ will be ‘false’. The rover turns to the right. Likewise, if the left-hand
sensor triggers.
The only other possible state is when both sensors trigger, and the rover then stops.
You can see a video demonstration of my rover here (https://youtu.be/6StfzXZLizE).
Summary
In this chapter you learnt how the TCRT5000 infrared sensor works and how it can be used to make a
vehicle follow a line. You looked at the principles of line following using 2 sensors and put this into
practice by developing a simple Sketch.
Chapter 5: Object Avoidance
The ability of a rover or robot vehicle to avoid objects is standard in pretty much every shop bought kit.
However, from my experience I have found the functionality somewhat basic in every kit that I have
tested. This is probably the intention, as manufacturers would encourage you to reprogram their
products and enhance this functionality. What I propose we do in this chapter is take a step up from
the basic functionality and implement a more sophisticated object avoidance algorithm that will provide
very satisfactory results. But first we need to look at some of the key components and do some
calibration work.
Sweep Sensors
A sweep sensor is simply a sensor mounted on a servo motor that rotates from side to side while taking
distance readings. It can be used as a form of radar, as a ‘picture’ of the object environment can be built
up by associating a distance to the object with the angle at which the reading was taken. Figure 5-4
shows a HC-SR04 based sweep sensor mounted to one of my robot vehicles and we will be using a sweep
sensor with our rover.
Servo Motors
Before we move on to some experimental work, we need to understand how servo motors work.
A servomotor is a rotary or linear actuator that allows for precise control of angular or linear position.
Amongst other things, they are found in radio-controlled models, automated door locks and the like.
Images of servos can be seen in Figure 5-5.
Servo Operation
You will see that all servos have 3 wires. Usually red and brown are +5V and ground respectively, and
the positional control wire is usually orange. They work on the principle of PWM to define the angle.
They require a positional feedback mechanism, and the simplest type is a potentiometer built into the
motor casing.
PWM operation of the servo motor is illustrated in Figure 5-6. A PWM pulse is triggered every 20ms.
This is generally consistent across all DC servo motors and so makes programming easy with the use of
an Arduino library. If a 1ms pulse is present in the period, the servo rotates to 0 0. If the pulse is 1.5ms,
the servo rotates 900. Finally, if the pulse is 2ms, the servo rotates to 180 0. If the pulse is between these
values the servo rotates to the proportionate angle.
Figure 5-6: Servo PWM Pulses
Image source: The author
If you want the servo to hold its position and resist any rotational torque from the load, simply resend
the same pulse width every 20ms.
Controlling the servo with an Arduino would be difficult if we wrote the code from scratch. However,
there is a predefined library that can be imported that has the functions already encoded and so by
accessing them, the task of controlling the servo is easy. We will be using the built-in library called
‘Servo.h’ by Michael Margolis, so check you have that loaded by accessing ‘Manage Libraries’ from
the Arduino IDE.
#include <Servo.h>
void setup()
{
pinMode (SWITCH, INPUT_PULLUP);
//Create the Servo Object and pass pin number
myServo.attach(SERVO);
}
void loop()
{
myServo.write(90); //moves the servo to 90 deg
while (digitalRead (SWITCH) == HIGH) {
}
myServo.write(30); //moves the servo to right
delay (500);
myServo.write(150); //moves the servo to left
delay (500);
}
We need to create an object of the servo class and call it ‘myServo’. We also pass the I/O pin number
to which the control lead of the servo is connected.
myServo.attach(SERVO);
To move the servo, we simply append the ‘write’ command to the ‘myServo’ object and pass the angle
of rotation in degrees.
myServo.write(90);
I mounted my servo on a servo mounting plate. You will see that the servo is lower than in my test build.
I found from experimentation that the sensor was too high to detect some lower objects and so lowered
the whole unit this way. The HC-SR04 mounting plates are inexpensive, readily available, shop bought
components. My setup is shown in Figure 5-9.
Figure 5-9: The HC-SR04 and Servo Attachment
Image source: The author
void setup() {
//Set digital control pins as output
pinMode (MOTOR_A_DIRECTION, OUTPUT);
pinMode (MOTOR_A_BRAKE, OUTPUT);
pinMode (MOTOR_B_DIRECTION, OUTPUT);
pinMode (MOTOR_B_BRAKE, OUTPUT);
pinMode (SWITCH, INPUT_PULLUP);
pinMode (LED, OUTPUT);
pinMode (RIGHT_SENSOR, INPUT);
pinMode (LEFT_SENSOR, INPUT);
pinMode (TRIGGER, OUTPUT);
pinMode (ECHO, INPUT);
//Ensure motors are stopped
brake ("right");
brake ("left");
digitalWrite (LED, LOW); //Turn off LED
delay (2000); // Pause before starting
Serial.begin (9600);
//Create the Servo Object and pass pin number
myServo.attach(SERVO);
myServo.write(90); //look ahead
delay (500);
void loop() {
//read switch and debounce
currentButton = digitalRead(SWITCH); // Read the switch state
if (previousButton != currentButton) // If switch pressed
{
delay(5); // Wait 5ms
currentButton = digitalRead(SWITCH); // Read switch again
}
void followLine() {
//read state of sensors
boolean lineDetectedRight = digitalRead (RIGHT_SENSOR);
boolean lineDetectedLeft = digitalRead (LEFT_SENSOR);
if (!lineDetectedRight && !lineDetectedLeft) {
//Drive forward
forward ("right", 60);
forward ("left", 60);
} else if (lineDetectedRight && !lineDetectedLeft) {
//Turn right
brake ("right");
forward ("left", 60);
} else if (!lineDetectedRight && lineDetectedLeft) {
//Turn left
forward ("right", 60);
brake ("left");
} else {
//Both sensors trigger - stop!
brake ("right");
brake ("left");
}
}
void avoidObject() {
//Take a reading to detect object
distance = measureDistance();
if (distance < range) {
brake ("left");
brake ("right");
delay (250); //stop for 1/4 second
int direction = random(2); //0 = right and 1 = left
if (direction == 0) {
myServo.write (30);
} else {
myServo.write (150);
}
delay (500);
//Take another object reading
distance = measureDistance();
myServo.write(90); //look ahead
delay (500);
if (distance < range) {
//look in opposite direction
if (direction == 0) {
myServo.write (150);
} else {
myServo.write (30);
}
delay (500);
//Take distance reading in opposite direction
distance = measureDistance();
myServo.write(90); //look ahead
delay (500);
if (distance < range) {
//Reverse
reverse ("right", speed);
reverse ("left", speed);
delay (2000);
if (direction == 0) {
brake ("right");
forward ("left", speed);
} else {
forward ("right", speed);
brake ("left");
}
delay (500);
} else {
//turn in that direction
if (direction == 0) {
//reverse a little
reverse ("right", speed);
reverse ("left", speed);
delay (500);
//Turn right
brake ("right");
forward ("left", speed);
delay (500);
} else {
//reverse a little
reverse ("right", speed);
reverse ("left", speed);
delay (500);
//Turn left
forward ("right", speed);
brake ("left");
delay (500);
}
}
} else {
//Turn in that direction
if (direction == 0) {
//reverse a little
reverse ("right", speed);
reverse ("left", speed);
delay (500);
//Turn right
brake ("right");
forward ("left", speed);
delay (500);
} else {
//reverse a little
reverse ("right", speed);
reverse ("left", speed);
delay (500);
//Turn left
forward ("right", speed);
brake ("left");
delay (500);
}
}
} else {
forward ("right", speed);
forward ("left", speed);
}
}
float measureDistance () {
digitalWrite(TRIGGER, LOW);
delayMicroseconds(2);
digitalWrite(TRIGGER, HIGH);
delayMicroseconds(10);
digitalWrite(TRIGGER, LOW);
duration = pulseIn(ECHO, HIGH);
float functionDistance = (duration / 58);
return functionDistance;
}
I chose a speed of 120 and an object range of 40cm. I also created a function in the ‘switch’ and ‘case’
for the object avoidance selection. This will hold all the object avoidance code although there is another
function to measure the distance to the object.
avoidObject ();
The flow of the code in the ‘avoidObject’ function follows the flow of the flowchart of Figure 5-10.
Take some time to follow it through and understand the process. You will also notice that I reverse the
rover a little before turning the vehicle otherwise it tends to hit the object it is trying to avoid!
I created a function to measure the distance and it returns the result to the floating-point variable
‘distance’.
distance = measureDistance();
Rather than ‘void’ which means the function does not return a value, we declare this function as
‘float’ since the returned value will be a floating-point number. To initiate the HC-SR04, we first set
the ‘TRIGGER’ pin ‘LOW’ for 2 microseconds just to ensure it is in a ‘LOW’ state. We then set it ‘HIGH’. We
then need to wait for 10 microseconds before setting it ‘LOW’ again. We then use a handy function built
into the Arduino programming language (‘pulseIn’) to read in the duration of the pulse on the ‘ECHO’
pin. We can then use the given formula to calculate the distance in centimeters and return this value.
You can see a video demonstration of the rover here (https://youtu.be/6ktThzcmW_Y).
Summary
In this you learnt about object avoidance algorithms and how to use the HC-SR04 ultrasonic sensor to
give the rover object avoidance capabilities. You learnt about ‘Time of Flight’ principles and how the
HC-SR04 sensor uses them to calculate a distance to an object. You learnt about servo motors and how
they can be used to create a sweep sensor. You learnt how to use functions in Arduino code to measure
the width of a pulse and how libraries can be used to make your code development easier.
Chapter 6: Remote Control using Bluetooth
In this chapter we will look to control the rover remotely via a Bluetooth app. We could do this via a
smartphone, but not everybody has a smartphone. I figured that if you are developing Arduino
Sketches, then you will have access to some computing device, and we can use that if it supports
Bluetooth. We will develop our own program using the Processing IDE and the Processing
programming language. You will see that the Processing IDE is remarkably like the Arduino IDE as is
the programming language. But first, we need to understand a bit about Bluetooth.
What is Bluetooth?
Bluetooth is a short-range wireless technology that was designed to do away with the computer’s jungle
of wires. The technology is highly commoditized, meaning it is extremely cheap and you can find
Bluetooth functionality in all sorts of devices such as headsets, speakers, keyboards, and the like.
Bluetooth components can be used as master and slave pairs to provide 2-way serial connections in
place of cables and this is how we will use the technology. We should be able to achieve a maximum
range of 20m.
void setup() {
size(800, 500);
cp5 = new ControlP5(this);
font = createFont("arial", 20); //Use arial font for buttons 20pt
cp5.addButton("forward") //"forward" is the name of button
.setPosition(440, 60) //x and y coords of upper left corner of button
.setSize(120, 120) //(width, height)
.setFont(font)
;
cp5.addButton("right")
.setPosition(580, 200)
.setSize(120, 120)
.setFont(font)
;
cp5.addButton("reverse")
.setPosition(440, 340)
.setSize(120, 120)
.setFont(font)
;
cp5.addButton("left")
.setPosition(300, 200)
.setSize(120, 120)
.setFont(font)
;
cp5.addButton("stop")
.setPosition(440, 200)
.setSize(120, 120)
.setFont(font)
;
cp5.addButton("high") //High speed
.setPosition(100, 140)
.setSize(120, 80)
.setFont(font)
;
cp5.addButton("low") //Low speed
.setPosition(100, 280)
.setSize(120, 80)
.setFont(font)
;
void reverse(){
port.write('b');
}
void right(){
port.write('r');
}
void left(){
port.write('l');
}
void stop(){
port.write('s');
}
void high(){
port.write('2');
}
void low(){
port.write('1');
}
You then need to create objects for classes in these libraries and an object for the font we will use.
ControlP5 cp5; //Create ControlP5 object
PFont font; // Object for the font
Serial port; //Object of the serial port
The structure of Processing is like Arduino in that there is a ‘setup’ function and a ‘draw’ function that
is the same as the ‘loop’ function in Arduino. Our GUI is static and so we can place all the code in the
‘setup’ function as we only need to draw the GUI once.
We need to create a window and I chose a size of 800 pixels by 500 pixels.
size(800, 500);
The next line of code tells the compiler we are running the ‘cp5’ object we created in ‘this’ context.
This context means the GUI application we are creating.
cp5 = new ControlP5(this);
Contexts are a fundamental part of many programming environments and the concept is difficult to
understand and articulate, so it is one of those you just need to go with until such time as you have
enough experience to fully understand.
Next, we set a font for our titles and button names.
font = createFont("arial", 20);
Creating a button is straightforward. The code adds a button with the specified title, sets the position
of the top left-hand corner, sets the size of the button, and applies the font.
cp5.addButton("forward") //"forward" is the name of button
.setPosition(440, 60) //x and y coords of upper left corner of button
.setSize(120, 120) //(width, height)
.setFont(font)
;
Setting the background color of the window and adding some title text completes the GUI element.
background(77, 166, 255); // background color of window (r, g, b)
//title for our window
fill(0, 0, 0); //text color (r, g, b)
textFont(font);
text("ROVER CONTROL", 325, 30); // ("text", x coordinate, y coordinate)
fill(80, 80, 80); //text color (r, g, b)
text("direction", 460, 490); // ("text", x coordinate, y coordinate)
text("speed", 133, 395); // ("text", x coordinate, y coordinate)
Next, we need to look at how to set up the serial communications. The communications port we will be
using will be the number assigned by your Arduino board and can be retrieved from the Arduino IDE
as in Figure 6-4.
Figure 6-4: Finding the Arduino COM Port Number
Image source: The Arduino IDE
It is useful to list all the COM ports available. These will be displayed in a small monitor window at the
bottom of the Processing IDE when the program is run. My Arduino board is using COM3 and setup
for 9600bps.
printArray(Serial.list()); //prints all available serial ports
port = new Serial(this, "COM3", 9600); //com port of the Arduino
The beauty of using a library like ‘controlP5’ is that all the complex coding to change the mouse over
color, the on click color and click event is taken care of. All that the Sketch needs is a function created
that has the text of each button as its name. The code within that function will then be executed on each
click of the respective button. All we need to do is send an ASCII character over the serial port to the
Arduino. The function for the forward button is below and sends the character ‘f’.
void forward(){
port.write('f'); //Send 'f' to serial port
}
#include <Servo.h>
void setup() {
//Set digital control pins as output
pinMode (MOTOR_A_DIRECTION, OUTPUT);
pinMode (MOTOR_A_BRAKE, OUTPUT);
pinMode (MOTOR_B_DIRECTION, OUTPUT);
pinMode (MOTOR_B_BRAKE, OUTPUT);
pinMode (SWITCH, INPUT_PULLUP);
pinMode (LED, OUTPUT);
pinMode (RIGHT_SENSOR, INPUT);
pinMode (LEFT_SENSOR, INPUT);
pinMode (TRIGGER, OUTPUT);
pinMode (ECHO, INPUT);
//Ensure motors are stopped
brake ("right");
brake ("left");
digitalWrite (LED, LOW); //Turn off LED
delay (2000); // Pause before starting
Serial.begin (9600);
//Create the Servo Object and pass pin number
myServo.attach(SERVO);
myServo.write(90); //look ahead
delay (500);
void loop() {
//read switch and debounce
currentButton = digitalRead(SWITCH); // Read the switch state
if (previousButton != currentButton) // If switch pressed
{
delay(5); // Wait 5ms
currentButton = digitalRead(SWITCH); // Read switch again
}
if (previousButton == HIGH && currentButton == LOW) // Detect a button
press
{
printOnce = false;
//Increment function selection
function++; // increment function by 1
if (function == 5) {
function = 1; //allow the function to cycle 1 thru 4
}
//Flash LED
switch (function) {
case 1:
//Standby
//1 LED flash
flashLED (function);
break;
case 2:
//Line follower
//2 LED flashes
flashLED (function);
break;
case 3:
//Object avoidance
//3 LED flashes
flashLED (function);
break;
case 4:
//Bluetooth control
//4 LED flashes
flashLED (function);
break;
}
}
previousButton = currentButton; // Reset button value
//Act on function selection
switch (function) {
case 1:
//Standby
brake ("right");
brake ("left");
if (!printOnce) {
Serial.println ("Function 1 - Standby");
}
printOnce = true;
break;
case 2:
//Line follower
if (!printOnce) {
Serial.println ("Function 2 - Line Follower");
}
printOnce = true;
followLine();
break;
case 3:
//Object avoidance
if (!printOnce) {
Serial.println ("Function 3 - Object Avoidance");
}
printOnce = true;
avoidObject ();
break;
case 4:
//Bluetooth control
if (!printOnce) {
Serial.println ("Function 4 - Bluetooth Control");
brake ("right");
brake ("left");
}
printOnce = true;
remoteControl();
break;
}
}
void followLine() {
//read state of sensors
boolean lineDetectedRight = digitalRead (RIGHT_SENSOR);
boolean lineDetectedLeft = digitalRead (LEFT_SENSOR);
if (!lineDetectedRight && !lineDetectedLeft) {
//Drive forward
forward ("right", 60);
forward ("left", 60);
} else if (lineDetectedRight && !lineDetectedLeft) {
//Turn right
brake ("right");
forward ("left", 60);
} else if (!lineDetectedRight && lineDetectedLeft) {
//Turn left
forward ("right", 60);
brake ("left");
} else {
//Both sensors trigger - stop!
brake ("right");
brake ("left");
}
}
void avoidObject() {
//Take a reading to detect object
distance = measureDistance();
if (distance < range) {
brake ("left");
brake ("right");
delay (250); //stop for 1/4 second
int direction = random(2); //0 = right and 1 = left
if (direction == 0) {
myServo.write (30);
} else {
myServo.write (150);
}
delay (500);
//Take another object reading
distance = measureDistance();
myServo.write(90); //look ahead
delay (500);
if (distance < range) {
//look in opposite direction
if (direction == 0) {
myServo.write (150);
} else {
myServo.write (30);
}
delay (500);
//Take distance reading in opposite direction
distance = measureDistance();
myServo.write(90); //look ahead
delay (500);
if (distance < range) {
//Reverse
reverse ("right", speed);
reverse ("left", speed);
delay (2000);
if (direction == 0) {
brake ("right");
forward ("left", speed);
} else {
forward ("right", speed);
brake ("left");
}
delay (500);
} else {
//turn in that direction
if (direction == 0) {
//reverse a little
reverse ("right", speed);
reverse ("left", speed);
delay (500);
//Turn right
brake ("right");
forward ("left", speed);
delay (500);
} else {
//reverse a little
reverse ("right", speed);
reverse ("left", speed);
delay (500);
//Turn left
forward ("right", speed);
brake ("left");
delay (500);
}
}
} else {
//Turn in that direction
if (direction == 0) {
//reverse a little
reverse ("right", speed);
reverse ("left", speed);
delay (500);
//Turn right
brake ("right");
forward ("left", speed);
delay (500);
} else {
//reverse a little
reverse ("right", speed);
reverse ("left", speed);
delay (500);
//Turn left
forward ("right", speed);
brake ("left");
delay (500);
}
}
} else {
forward ("right", speed);
forward ("left", speed);
}
}
float measureDistance () {
digitalWrite(TRIGGER, LOW);
delayMicroseconds(2);
digitalWrite(TRIGGER, HIGH);
delayMicroseconds(10);
digitalWrite(TRIGGER, LOW);
duration = pulseIn(ECHO, HIGH);
float functionDistance = (duration / 58);
return functionDistance;
}
void remoteControl() {
if (Serial.available()) { //if data is available to read
char val = Serial.read();
if (val == 'f') { //if f received
forward ("right", speedBluetooth);
forward ("left", speedBluetooth);
}
if (val == 'b') { //if b received
reverse ("right", speedBluetooth);
reverse ("left", speedBluetooth);
}
if (val == 'r') { //if r received
brake ("right");
forward ("left", speedBluetooth);
}
if (val == 'l') { //if l received
forward ("right", speedBluetooth);
brake ("left");
}
if (val == 's') { //if s received
brake ("right");
brake ("left");
}
if (val == '1') { //if 1 received
speedBluetooth = 120;
}
if (val == '2') { //if 2 received
speedBluetooth = 220;
}
}
}
Listing 6-2: Code added to Drive the Rover via Remote Control
Listing source: The author
Although our Sketch is quite large now, there is little code added to control the rover and little to review.
I added a variable for the speed under remote control since this can be changed via the app we
developed.
int speedBluetooth = 120;
Within the ‘case’ statement for the Bluetooth control we reference the function for the remote control
of the rover. This is called ‘remoteControl()’. Also, you will see that I have added calls to the brake
function for each motor. If I did not do this the rover will be driving forward looking for an object to
avoid as we must toggle through that app function beforehand.
//Bluetooth control
if (!printOnce) {
Serial.println ("Function 4 - Bluetooth Control");
brake ("right");
brake ("left");
}
printOnce = true;
remoteControl();
Taking a look at the ‘remoteControl()’ function listed in Listing 6-2. We first start with a check to
see if data is available to read from the serial port buffer and only execute the block of code if there is.
if (Serial.available()) {
//execute this block if data available
}
All we then need to do is read the data and act based on the value of ‘char’ when there is a match in the
‘if’ statement.
char val = Serial.read();
All we need to do now is add the Bluetooth functionality in place of the wired serial port. You can see a
video demonstration of the experiment here (https://youtu.be/8MuJOBgqzZU).
This is all that you need to do. Since there is no additional programming to do on the rover. All you
need to do now is select function 4 and run the Processing app. Have fun driving!
An image of my final rover build is shown in Figure 6-13.
Figure 6-13: The Final Rover Build
Image source: The author
Summary
In this chapter you learnt about Bluetooth communications and learnt how to use the HC-05 Bluetooth
module. You learnt how to download and install the Processing IDE and developed a simple GUI to
control the rover remotely. You learnt how to install Bluetooth devices on a Windows PC.
Epilogue
I have had a great time developing and building the rover for this book. I hope you have found the
content useful and engaging. You will be left with a platform you can use to develop more sophisticated
functionality. Also, this book promised to help you make a simple rover. I believe that what we have
produced is a step up from ‘out of the box’ products on the market today in terms of the software and
the scope of the functionality certainly is not out of the skill limits of the novice engineer. Like all large
projects, just break it down into smaller more manageable tasks in the way you did here, and you can
produce a sophisticated machine. You can be proud of yourself.
In the subsequent books in the series, we will use our knowledge gained to look at other focus areas and
create ever more challenging, creative and interesting projects. Whatever your goals are, I hope to have
inspired you and helped you some way to achieving those goals.
If you like this book and the series of books, please leave a review on the Amazon website. For the latest
news on other books in the series you can following my Facebook page, Twitter or follow me as an
author on Amazon.
About the Author
Gary Hallberg has over 34 years of experience in the telecommunications and data communications
industries. He started his career working with circuit switched voice and data technologies in PDH and
SDH environments and then moved on to work with early packet switched networks using Frame Relay
and ATM. His career shifted focus to IP routing when the Internet started to grow, designing large scale
service provider networks using leading edge equipment from vendors such as Juniper and Cisco. Gary
attained Cisco Certified Professional status during this time. Presently, he is working for a global
equipment vendor with a focus on Ethernet networks and is at the forefront of network evolution,
providing infrastructures for SD-WAN, Network Functions Virtualization, SDN and 5G backhaul. Gary
has a Bachelor of Engineering degree in electronic engineering and a Master of Philosophy degree in
robotics and manufacturing.