A 4WD Bluetooth Robot Based on Arduino
In this post, I show off a Bluetooth four-wheeled mobile robot with Arduino Uno. It uses the classic “yellow” DC gear motors but It has an HC-SR04 Ultrasonic Sensor to get the distances to the surrounding objects. One remarkable aspect is that the Phone app to control the robot can show these distances in a sonar-like graph, i.e., the app lets bidirectional serial communication with the Arduino Uno, one direction to control the DC gear motors and the other to ask for the distances measures. The post makes a review like a little state of the art about similar and popular projects and lists its main features for the reader.
Introduction
Developing a wheeled mobile robot with Arduino is maybe the most suitable project to introduce the world of digital technology, programming, electronics and the Arduino platform. On this page, you will find all the information to build a mobile robot controlled by Bluetooth with Arduino and powered by DC motors. Moreover, there is a lot of wheeled robot projects with different features which can be used as a reference to replicate or study:
- A mobile robot by Humberto Higinio - Type of wheeled transmission: 4WD - Battery: One lipo 2S of 7.4V - Main functionality: Line follower - Features: Ultrasonic sensor HC-SR04 - Motor Driver: L298N - Connectivity: No one - Control board: Arduino Nano
- A mobile robot by Iván Espinoza - Type of wheeled transmission: 2WD - Battery: 2 x 18650 of 3.7V - Main functionality: Generic mobile robot - Features: Servomotor SG90 + Ultrasonic sensor HC-SR04 - Motor Driver: L293D - Connectivity: No one - Control board: Arduino Uno
- The kit Elegoo V3 review by DroneBot Workshop - Type of wheeled transmission: 4WD - Battery: 2 x 18650 of 3.7V - Features: Servomotor SG90 + Ultrasonic sensor HC-SR04 - Connectivity: Bluetooth - Motor Driver: L298N - Control board: Arduino Uno
- The robot kit by UtGo - Type of wheeled transmission: 4WD - Battery: 2 x 18650 of 3.7V - Connectivity: Bluetooth - Motor Driver: Adafruit Motor Shield V1 - Control board: Arduino Uno - Android App: Generic Bluetooth RC Controller
- The soccer robot kit ANTT Robotics - Type of wheeled transmission: 2WD - Battery: 2 x 18650 of 3.7V - Features: Servomotor SG90 + Ultrasonic sensor HC-SR04 + Line Follower - Connectivity: Bluetooth - Motor Driver: L298N - Control board: Arduino Uno - Android App: There is no link for the used App, but the generic App could work.
Circuit and components
The components are:
- 1 Arduino Uno R3
- 1 Adafruit Motor Driver V1
- 4 Generic “Yellow” DC Gear Motor 3V-6V
- 2 Battery 18650
- 1 Battery holder for 18650
- 1 Breadboard mini
- 1 Servomotor Futaba 3003
- 1 Ultrasonic sensor HC-SR04
- 1 Bluetooth module HC05
- 1 Protoboard mini
- 1 Acrylic platform to mount the components
- Jumpers male-male to make the connections
The mapping between the circuit component pins and the Arduino and shield pins is:
COMPONENT NAME | COMPONENT PIN | ARDUINO PIN | SHIELD PIN |
---|---|---|---|
Servomotor Futaba 3003 | GND | GND | GND |
VCC | 5V | + | |
Signal | D9 | SERVO_2 | |
Ultrasonic sensor HC-SR04 | GND | GND | GND |
VCC | 5V | +5V | |
ECHO | A3 | A3 | |
TRIGGER | A4 | A4 | |
Bluetooth module HC05 | GND | GND | GND |
VCC | 5V | +5V | |
RX | A0 | A0 | |
TX | A1 | A1 |
Code
Libraries and Variables
The first part of the code consists to include the libraries and define the variables and constants that are going to use. The libraries used are:
AFMotor.h
to use the Adafruit Motor Shield.SoftwareSerial.h
to enable serial communication with the HC05 Bluetooth module.Servo.h
Optional if you want to use the standard servo functions. In this case, I wrote a function to control the servo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <AFMotor.h>
#include <SoftwareSerial.h>
#include <Servo.h>
// Pins for the bluetooth module
const int TxPin = A0; // Attach to the RX module's pin
const int RxPin = A1; // Attach to the TX module's pin
SoftwareSerial SerialBluetooth(RxPin,TxPin);
// Pins for the HC-SR04 ultrasonic sensor
const int echoPin = A3; //attach pin A3 Arduino to pin Echo of HC-SR04
const int trigPin = A4; //attach pin A4 Arduino to pin Trig of HC-SR04
// Pin for the Servomotor -> SERVO_2 in Adafruit Motor Shield
const int servoPin = 9;
int positionIndex = 0;
// Variables to store the received data
const byte numChars = 32; // Num of chars to receive
char receivedChars[numChars]; // Buffer to receive data
char tempChars[numChars]; // Temporary array for use when parsing
char messageFromBluetooth[numChars] = {0};
int integerFromBluetooth = 0;
boolean newData = false;
// Motor objects declaration for Adafruit Motor Shield
AF_DCMotor motorshield_dc_1(1);
AF_DCMotor motorshield_dc_2(2);
AF_DCMotor motorshield_dc_3(3);
AF_DCMotor motorshield_dc_4(4);
// Variables for motor control
int speed = 255;
char phoneCommand[numChars] = {0};
int motorDuration;
// Functions headers
void MoveForward(int speed = 255, int duration = 50);
void MoveBackward(int speed = 255, int duration = 50);
/* Create servo object to control a servo. It is optional
if you want to control the servo with the servo.h library */
Servo myservo;
Setup() Function
The `setup()’ function defines the pin modes, the serial communication baud rate for the main serial port and other software serial ports for the Bluetooth module in an independent way.
This is a notable aspect of this project due that the main serial can be left open for debugging purposes. This feature is not present in the shown projects in the last section and the inconvenience is that each time you have to program the Arduino, you have to disconnect the Bluetooth module.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void setup()
{
// Define pin modes for RX and TX
pinMode(RxPin, INPUT);
pinMode(TxPin, OUTPUT);
// Initialize the serial communication:
Serial.begin(9600); //baud rate
// Initialize the Bluetooth serial communication with the Arduino:
SerialBluetooth.begin(9600); //baud rate
// Pin to control the servomotor without the servo.h library
pinMode(servoPin, OUTPUT);
/// Sensor HC-SR04 Pins
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
// Set 0 as initial speed value for the motors
motorshield_dc_1.setSpeed(0);
motorshield_dc_2.setSpeed(0);
motorshield_dc_3.setSpeed(0);
motorshield_dc_4.setSpeed(0);
///motors Fast stop
Stop();
/* Attaches the servo on pin 9 to the servo object
Commented because the code uses a own defined function
to control the servo */
// myservo.attach(9);
}
Communication Functions
The communication protocol is based on this Arduino introductory tutorial which shows how to receive serial data to the Arduino using complete words, numbers, letters and symbols instead of using only letters.
The first function is receiveDataFromPhone()
which defines the <
and >
characters to delimitate the complete message to be received by the Arduino which can be a set of words, numbers, letters and symbols in only one line-ending or “sentence” for the full message.
This function checks if new data is available in the serial port and if no new data or line-ending message has been received yet. If this occurs, the full message is read character by character from the start maker until the end marker character is reached and stored in an array of chars
named receivedChars[numChars]
. The maximum number of chars that can be stored is 32 which is set in the numChars
variable. The process finishes when the \0
character is read and it is signaling to set the boolean variable newData
to true.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void receiveDataFromPhone()
{
// Get ready for waiting another new data
newData = false;
static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '<';
char endMarker = '>';
char rc;
while (SerialBluetooth.available() > 0 && newData == false)
{
rc = SerialBluetooth.read();
if (recvInProgress == true)
{
if (rc != endMarker)
{
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars)
{
ndx = numChars - 1;
}
}
else
{
receivedChars[ndx] = '\0'; // terminate the string
recvInProgress = false;
ndx = 0;
newData = true;
}
}
else if (rc == startMarker)
{
recvInProgress = true;
}
}
}
To split the data the function named SplitReceivedDate()
is used. The full message is split character by character using the comma (,
) punctuation mark as the tokenizer symbol for each part of the message.
Each split part of the message is stored in global variables that will be used by the entire code. The message is made of two parts: one string and one integer. The first is the name of the motor command and the second is the duration of this motor command that will be executed by the robot. These two parts are stored in the next two variables:
messageFromBluetooth
that will be copied tophoneCommand
integerFromBluetooth
that will be copied tomotorDuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void SplitReceivedData()
{
if (newData == true)
{
/*
This temporary copy is necessary to protect the original data
because strtok() replaces the commas with \0
*/
strcpy(tempChars, receivedChars);
// Used by strtok() as an index
char * strtokIndex;
// Get the first part - the string
strtokIndex = strtok(tempChars, ",");
strcpy(messageFromBluetooth, strtokIndex);
// Get the second part - the int. This continues where the previous call left off
strtokIndex = strtok(NULL, ",");
// Convert this part to an integer
integerFromBluetooth = atoi(strtokIndex);
// Copy the received data to the Robot variables
strcpy(phoneCommand,messageFromBluetooth);
motorDuration = integerFromBluetooth;
// ShowSplitData(); // Uncomment to print the received and tokenized data
}
}
The function showSplitData()
can show in the terminal the split message that has been received and tokenized.
1
2
3
4
5
6
7
void showSplitData()
{
Serial.print("Message: ");
Serial.print(messageFromBluetooth);
Serial.print(" | Integer: ");
Serial.println(integerFromBluetooth);
}
Logic Functions
The next part of the code consists of some functions that are in charge of controlling the robot’s movements and the function of the HC-SR04 ultrasonic sensor.
The first is the ChooseCommand()
function. It compares the value in the phoneCommand
variable against a const string for the four standard movements: Forward, Backward, Left, Right and finally with a GetDistance option if the user wants to obtain the distance to the surrounding objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void ChooseCommand()
{
// Choose Forward direction
if (strcmp(phoneCommand,"Forward") == 0 && newData == true)
{
Serial.print("Forward");
Serial.print(" | duration: ");
Serial.print(motorDuration);
Serial.println("ms");
MoveForward(speed, motorDuration);
}
// Choose Backward direction
if (strcmp(phoneCommand,"Backward") == 0 && newData == true)
{
Serial.print("Backward");
Serial.print(" | duration: ");
Serial.print(motorDuration);
Serial.println("ms");
MoveBackward(speed, motorDuration);
}
// Choose a Left turn
if (strcmp(phoneCommand,"Left") == 0 && newData == true)
{
Serial.print("Left");
Serial.print(" | duration: ");
Serial.print(motorDuration);
Serial.println("ms");
TurnLeft(motorDuration);
}
// Choose a Right turn
if (strcmp(phoneCommand,"Right") == 0 && newData == true)
{
Serial.print("Right");
Serial.print(" | duration: ");
Serial.print(motorDuration);
Serial.println("ms");
TurnRight(motorDuration);
}
// Make a sweep with the servo and get the distance
if (strcmp(phoneCommand,"GetDistance") == 0 && newData == true)
{
MakeSonar();
}
}
Motor Control Functions
The next functions control the motors directly using the commands from the Adafruit Motor Shield. There are four functions, one for each movement. To control the motor, the speed and the duration of the movement are required. The speed is set to 255, which is the maximum value, and the duration comes from the user and can be in the range between 250ms and 2000ms.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void MoveForward(int speed = 255, int duration = 50)
{
// Set the back motor's speed for traction
motorshield_dc_1.setSpeed(speed);
motorshield_dc_4.setSpeed(speed);
// Decrease the front motors speed for power management
motorshield_dc_2.setSpeed(25);
motorshield_dc_3.setSpeed(25);
// Set the motor's direction
motorshield_dc_1.run(FORWARD);
motorshield_dc_2.run(FORWARD);
motorshield_dc_3.run(FORWARD);
motorshield_dc_4.run(FORWARD);
delay(duration);
Stop();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void MoveBackward(int speed = 255, int duration = 50)
{
// Set the back motor's speed for traction
motorshield_dc_1.setSpeed(speed);
motorshield_dc_4.setSpeed(speed);
// Decrease the front motor's speed for power management
motorshield_dc_2.setSpeed(25);
motorshield_dc_3.setSpeed(25);
// Set the motor's direction
motorshield_dc_1.run(BACKWARD);
motorshield_dc_2.run(BACKWARD);
motorshield_dc_3.run(BACKWARD);
motorshield_dc_4.run(BACKWARD);
delay(duration);
Stop();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void TurnLeft (int duration)
{
// Set the right motor's speed for traction
motorshield_dc_1.setSpeed(255);
motorshield_dc_2.setSpeed(255);
// Set the motor's direction
motorshield_dc_1.run(FORWARD);
motorshield_dc_2.run(FORWARD);
motorshield_dc_3.run(RELEASE);
motorshield_dc_4.run(RELEASE);
delay(duration);
Stop();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void TurnRight (int duration)
{
// Set the left motor's speed to turn to right
motorshield_dc_3.setSpeed(255);
motorshield_dc_4.setSpeed(255);
// Set the motor's direction
motorshield_dc_1.run(RELEASE);
motorshield_dc_2.run(RELEASE);
motorshield_dc_3.run(FORWARD);
motorshield_dc_4.run(FORWARD);
delay(duration);
Stop();
}
HC-SR04 Functions
The last function MakeSonar()
is in charge of operating the HC-SR04 ultrasonic sensor. This function obtains the distance at 3 different angles: 60°, 90° and 125°. These positions can be reached thanks to the servomotor Futaba 3003 which the ultrasonic sensor is attached to. In the last part of the function, the string distances
contains the 3 values and it is sent back to the phone with the distance data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void MakeSonar()
{
// Move the servo
servoPulse(servoPin,60);
delay(200);
long distance1 = GetDistance();
servoPulse(servoPin,90);
delay(200);
long distance2 = GetDistance();
servoPulse(servoPin,125);
delay(200);
long distance3 = GetDistance();
// Displays the distance on the Serial Monitor
Serial.print("[");
Serial.print(distance1);
Serial.print(",");
Serial.print(distance2);
Serial.print(",");
Serial.print(distance3);
Serial.println("]");
// Send the distance to Bluetooth
String distances = (String) distance1 + "," + (String) distance2 + "," + (String) distance3;
SerialBluetooth.println(distances);
}
The function servoPulse()
moves the servomotor Futaba 3003 converting the desired angle into microseconds which are used to activate the servomotor using the PWM technique.
1
2
3
4
5
6
7
8
9
10
void servoPulse (int servoPin, int angle)
{
int pwm = (angle*11) + 440; // Convert angle to microseconds
for (int i = 0; i < 100; i++) {
digitalWrite(servoPin, HIGH);
delayMicroseconds(pwm);
digitalWrite(servoPin, LOW);
delayMicroseconds(2420-pwm); // Refresh cycle of servo
}
}
The function GetDistance()
controls the activation of the HC-SR04 in a standard way to measure the wave’s time travel when it hits an object. Then, the distance is calculated taking into account the sound`s speed and the measured time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
long GetDistance () {
// Clears the trigPin condition
digitalWrite(trigPin, LOW);
delayMicroseconds(5);
// Sets the trigPin HIGH (ACTIVE) for 10 microseconds
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// Reads the echoPin, returns the sound wave travel time in microseconds
long duration = pulseIn(echoPin, HIGH);
// Calculating the distance
long distance = duration * 0.034 / 2; // Speed of sound wave divided by 2 (go and back)
return distance;
}
The Android App
The phone app is made with the platform MIT App Inventor for Android Phones. The main advantage consists in that this platform lets to program using blocks, runs online, and lets to connect your phone through WiFi, making the programming so simple.
The app lets to move the robot in four main directions: forward, backward, turn to the left and turn to the right. Besides it lets to measure the distance to the surrounding objects at 3 different angles: 60°, 90° and 125° and draw this information with small circles in a graph like a sonar on the phone.
The main screen of the app can be seen in Figure 2. From up to bottom, it consists of the next elements:
- The buttons do connect/disconnect the Bluetooth module from the Arduino.
- A Label box to show numerically the values of the distance measure.
- A Slider to control the movement duration in a range between 250ms and 2000ms
- A sonar-like graph to draw the location of the surrounding obstacles detected through a distance measure.
- The buttons for controlling the robot’s movements and the button that executes a distance measure.
Bluetooth Connection Blocks
The blocks to control the connection between the Bluetooth HC05 module and the Arduino are shown in Figure 3a.
Slider Blocks
The coding blocks to map the Slider values to the movement’s duration of the robot are shown in Figure 3b. The mapping is done by calculating the quotient of the Slider’s values between 250 and multiplying this quotient by 250. This results in steps or intervals of 250 instead of continuous values, which reduces the duration values to eight possible values: 250, 500, 750,1000, 1250, 1500, 1750 and 2000 if the Slider has limited to a maximum value of 2000.
Blocks to Get and Graph the Distance
To get the distance to the surrounding objects, the app uses the sets of blocks which are shown in Figure 3c. These blocks send the instruction for the robot to measure the distance. Immediately after, the app checks the Bluetooth buffer for the acquired values contained in a list of values separated by commas (,
). Remember that the robot measures the distance at 3 different angles (60°, 90° and 125°). After these instructions, each value is extracted from the list and drawn in the graph using the graph’s width and height as the limits to calculate the center’s position of the point that represents a detected obstacle.
On the other hand, Figure 3d shows the blocks to draw the arcs and lines that shape the sonar aspect of the graph. This uses a variable called delta
to define how many arcs will be drawn. By default, this value is set to 6, and this number is the base on which the blocks calculate the coordinates of the six arcs to be drawn.
Movements Control Blocks
For controlling the robot’s movements, the app has four sets of blocks which are shown in Figure 3d. Each one of these sets sent a string value with the command’s name and the desired duration of the movement set with the Slider control.