Obstacle detection is applicable to any robot that moves from an initial position to a goal position avoiding any obstacle in its path. The process of detecting obstacles is applied for a variety of robots, including a mobile robot and a robot arm. In this tutorial, you will learn how to use the HC-SR04 sensor with an Arduino board and determine the detection range of the sensor in certain conditions.
Different projects may have different requirements. At the end of this tutorial, you will have a flexible structure that can be used in different robots and make it possible to add more sensors, or use only one sensor, or use another type of sensor (for example, infrared sensor).
If you plan to build advanced robots in a productive and professional manner, this is the point where you can start.
Before starting to connect the sensor and write the first line of code, let’s be sure that we have all the hardware parts. Here is the list of hardware that I use to write the tutorial:
- 1 X Arduino UNO
- 1 X USB cable
- 1 X HC-SR04
- 1 X Breadboard
- female-to-female/male-to-male/female-to-male jumper wires
Table of Contents
Toggle1. Read the HC-SR04 output with Arduino
In this part of the article, I will show you how to connect one HC-SR04 sensor to Arduino and write the Arduino sketch that reads and transforms the sensor’s output.
For the moment, I use a USB cable to power the Arduino UNO. The 5V USB port of a personal computer or laptop provides enough power to run a 5V Arduino and the three ultrasonic sensors.
The above set up is just for connecting and testing the ultrasonic sound detection system. When the sensor and the Arduino board will be mounted on a mobile robot, the entire detection system will run on batteries.
1.1 Connect the sensor to the Arduino
First, let’s have a look on the HC-SR04 specifications:
- Operating Voltage: DC 5V
- Operating Current: 15mA
- Operating Frequency: 40KHz
- Range: from 2cm to 4m
- Ranging Accuracy: 3mm
- Trigger Input Signal: 10µS TTL pulse
The operating voltage of the sensor is the same as the Arduino 5V output pin – DC 5V. The operating current for an ultrasonic sensor is 15mA, which can be supported by Arduino even if it is connected to a USB port of a computer. The 5V output pin of Arduino UNO is suitable for ~400 mA on USB, ~900 mA when using an external power adapter. At this step, we will take into consideration only these two specifications and start to connect the sensor to the board.
For connections, I use female-to-male jumper wires, a breadboard, one HC-SR04, and an Arduino UNO board.
Connections:
- Vcc -> breadboard -> 5V
- Trig -> pin 3 (digital pin)
- Echo -> pin 2 (digital pin)
- GND -> breadboard -> GND
1.2 Write the code sample for the sensor
Once the sensor is connected to the Arduino board, we can start writing the sketch to read the output and transform the reading from the sensor. For writing the sketch, I use the Arduino IDE. I like to work with simple tools and don’t spend time on customizations and accessories. For the moment, the Arduino IDE satisfies my needs in terms of programming a microcontroller.
Before writing the first line of code, let’s recapitulate how an ultrasonic sensor works.
It pings a sound wave (for at least 10us according to specifications), which travels through the air, and if there is an object that reflects the sound wave, the sensor measures the time that ping took to return to the receiver. To calculate the distance between sensor and object detected, we consider the travel time and the speed of the sound.
Let’s begin building our ultrasonic sensor measurement sketch:
C++
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
/*
:Version 1.0
:Author: Dragos Calin
:Email: dragos@intorobotics.com
:License: BSD
:Date: 06/04/2020
*/
/*define the sensor’s pins*/
uint8_t trigPin = 3;
uint8_t echoPin = 2;
unsigned long timerStart = 0;
int TIMER_TRIGGER_HIGH = 10;
int TIMER_LOW_HIGH = 2;
float timeDuration, distance;
/*The states of an ultrasonic sensor*/
enum SensorStates {
TRIG_LOW,
TRIG_HIGH,
ECHO_HIGH
};
SensorStates _sensorState = TRIG_LOW;
void startTimer() {
timerStart = millis();
}
bool isTimerReady(int mSec) {
return (millis() – timerStart) < mSec;
}
/*Sets the data rate in bits per second and configures the pins */
void setup() {
Serial.begin(9600);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
/*Switch between the ultrasonic sensor states*/
switch (_sensorState) {
/* Start with LOW pulse to ensure a clean HIGH pulse*/
case TRIG_LOW: {
digitalWrite(trigPin, LOW);
startTimer();
if (isTimerReady(TIMER_LOW_HIGH)) {
_sensorState = TRIG_HIGH;
}
} break;
/*Triggered a HIGH pulse of 10 microseconds*/
case TRIG_HIGH: {
digitalWrite(trigPin, HIGH);
startTimer();
if (isTimerReady(TIMER_TRIGGER_HIGH)) {
_sensorState = ECHO_HIGH;
}
} break;
/*Measures the time that ping took to return to the receiver.*/
case ECHO_HIGH: {
digitalWrite(trigPin, LOW);
timeDuration = pulseIn(echoPin, HIGH);
/*
distance = time * speed of sound
speed of sound is 340 m/s => 0.034 cm/us
*/
Serial.print(“Distance measured is: “);
Serial.print(timeDuration * 0.034 / 2);
Serial.println(” cm”);
_sensorState = TRIG_LOW;
} break;
}//end switch
}//end loop
|
Distance measured sketch result
Once the sensor is connected to Arduino and the output is transformed in centimeters, we can go on and do the tests to determine the range of the sensor.
2. Operating detection range of the HC-SR04
We aim to detect all objects in a particular area in front of the sensor regardless of the shape or size of the object. Because in the end, we plan to use this sensor for object detection, firstly we have to determine the operational detection range of one sensor. Next, we have to calculate how to position each sensor unit for maximum efficiency in the detection of the objects.
The size, shape, and orientation of the object target affect the maximum distance at which it can be detected. For example, round shape objects or sound-absorbing materials such as fabrics reflect less energy or nothing directly back to the sensor. The ultrasonic sensor is not affected by optical characteristics such as color, reflectivity, transparency, or opaqueness. Also, objects may be introduced into the detection zone from any direction.
The first step in determining the range of an HC-SR04 ultrasonic sensor is to decide the range that you need for your robot. Let me explain what it means!
For example, I have a 2WD mobile chassis, and I want to use it to detect and avoid obstacles. I know from the specification that the HC-SR04 sensor’s range is between 2 and 400 centimeters.
I did a test with one sensor, and the result was that under 5 centimeters, the sensor performed no reliable measurements. 5cm is a minimum detection range starting from the sensor head. The zone area between 0cm and 5cm should be considered as a blind zone.
The next step was to determine the operating detection range that we need for our application. This is the range in which the sensor can do a detection or measurement during all times with high accuracy. If we search in the specifications, we can see a maximum detection range of 400 centimeters. This range couldn’t be taken into consideration in this case. These values are most probably taken when all conditions are very good to optimal. In a real-life application, we are using the sensor in a noisy environment with different temperatures and humidity.
The HC-SR04 is not a highly precise sensor but is good enough to use it for learning how to build robots. For this reason, I took into consideration an operating detection range of 100 centimeters – 1 meter in front of the sensor. This range is enough to learn how to build an autonomous mobile robot with DIY components and test it in a living room area.
HC-SR04 operating detection range
The ultrasonic beam angle for HC-SR04 is typically 10-15 degrees and conically shaped. I add in the above picture the optimal values when the sensor accuracy is high in the range of 5 to 100cm.
For the moment, we detect the objects and know the operating detection range. In the next part of the tutorial, I will show you how to write classes and build a flexible structure that can be used in different robots and make it possible to add more sensors.
3. Build classes in tabs
Each class will have a tab in the Arduino IDE. Using tabs allows us to keep things better organized. The classes will be written in files with extension .h.
Step 1: To create a new tab in Arduino IDE, go to the top right and press on button 1. Then select from menu 2 “New Tab“:
Create a new Tab to write a class
Step 2: In the lower part of the IDE should appear a new field. In this field, we write the name of the new file. For the first tab, type “Range.h” – the file where we will store the class for range detection sensors – and then press the OK button.
Range.h
Step 3: At this point, you should see in your Arduino IDE a tab like this:
Tab Range.h
Follow steps 1 and 2 to create a new tab for class Timer. The tab name is Timer.h.
Tab Timer.h
From now on in this article, we will work on these three tabs.
The first tab with main and loop -“Ultrasonic_Sensors_to_Detect_Obstacles”. In the Range.h tab is written the class for ultrasonic sensor, and in Timer.h is the timer class.
Let’s us see the code in each of the three tabs:
3.1 Ultrasonic_Sensors_to_Detect_Obstacles
Ultrasonic_Sensors_to_Detect_Obstacles
C++
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
|
/*
:Version 2.0
:Author: Dragos Calin
:Email: dragos@intorobotics.com
:License: BSD
:Date: 06/04/2020
:Last update: 09/04/2020
:Apply best practice methods and create two classes: Range and Timer
*/
#include “Range.h”
#include “Timer.h”
/*create an object for each sensor*/
Range range(3, 2);
Timer timerLoop;
void setup() {
Serial.begin(9600);
}
void loop() {
if (timerLoop.isTimeForLoop(50)) { /*loop for every 50 milliseconds*/
Serial.print(“Distance measured is: “);
Serial.print(range.sensorRange());
Serial.println(” cm”);
timerLoop.startTimer();
}
}//end loop
|
On Lines 11-12 we import our required classes.
Lines 15-16: We are using object-oriented programming, and we have to instantiate the Range and Timer classes to create an object and an instance of the class.
Line 19: we are in the setup function and the first thing we do is to open the serial port and set data rate to 9600 bps.
Lines 23 and 27: We check the time and publish every 50 milliseconds.
3.2 Range.h
Tab Range.h
C++
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
#ifndef RANGE_H
#define RANGE_H
#include “Timer.h”
Timer tm;
class Range {
enum SensorStates {
TRIG_LOW,
TRIG_HIGH,
ECHO_HIGH
};
SensorStates _sensorState = TRIG_LOW;
int _TrigPin;
int _EchoPin;
int _triggerHigh = 10;
int _timerLow = 2;
public:
Range(int TRIG_PIN, int ECHO_PIN) {
_TrigPin = TRIG_PIN;
_EchoPin = ECHO_PIN;
pinMode(_TrigPin, OUTPUT);
pinMode(_EchoPin, INPUT);
}
float sensorRange() {
switch (_sensorState) {
case TRIG_LOW: {
trigStateLOW();
tm.startTimer();
if (tm.isTimerReady(_timerLow)) {
_sensorState = TRIG_HIGH;
}
} break;
case TRIG_HIGH: {
trigStateHIGH();
tm.startTimer();
if (tm.isTimerReady(_triggerHigh)) {
_sensorState = ECHO_HIGH;
}
} break;
case ECHO_HIGH: {
trigStateLOW();
_timeDuration = pulseIn(_EchoPin, HIGH);
_sensorState = TRIG_LOW;
return (_timeDuration * 0.034 / 2);
} break;
}
}
private:
float _timeDuration, _distance;
void trigStateLOW() {
digitalWrite(_TrigPin, LOW);
}
void trigStateHIGH() {
digitalWrite(_TrigPin, HIGH);
}
};
#endif
|
Lines 1-2 and 68: prevent multiple includes of the header. It checks if a unique value of Range.h is defined. Then if it’s not defined, it defines Range.h and continues with the rest of the page.
Line 4: we import our Timer class.
Line 6: tm object is created from the class Timer.
Lines 9-14: Enumerate the states of the sensor. We start to deactivate the trigger pin, then activate the trigger, and in the end, we activate the echo pin to return the time duration of the impulse.
Lines 22-27: The constructor of the class Range. In constructor are defined the pins and the mode of the pins.
Lines 29-54: I use the switch statement to select one-by-one the states of the sensor. In the first case TRIG_LOW, ensure the trigger pin is low, then count two milliseconds until we switch to TRIG_HIGH – the second state of the sensor.
In the case of TRIG_HIGH, ensure that the trigger pin is high and count ten milliseconds until switching to the next state ECHO_HIGH.
In the case of ECHO_HIGH, ensure trigger pin is low, read the time duration of the pulse, set the next state as TRIG_LOW, and transform in centimeters the readings from the sensor.
Lines 57-65: In the private section of the class are added two functions. One function is to change the trigger state to low, and a second function to change the trigger state to high.
3.3 Timer.h
Tab Timer.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#ifndef TIMER_H
#define TIMER_H
class Timer {
private:
unsigned long _timerStart = 0;
public:
void startTimer() {
_timerStart = millis();
}
bool isTimerReady(int _mSec) {
return (millis() – _timerStart) < _mSec;
}
bool isTimeForLoop(int _mSec) {
return (millis() – _timerStart) > _mSec;
}
};
#endif
|
Lines 1,2 and 21: prevent multiple includes of the header. It checks if a unique value of Timer.h is defined. Then if it’s not defined, it defines Timer.h and continues with the rest of the page.
Lines 4-5: In the private section of the class, define the time to start variable.
Lines 8-10: the function to start counting the time using millis(). I prefer to use millis() instead of delay() from two reasons: millis() is more accurate than delay(), and is a non-blocking alternative of delay().
Lines 12-18: Two functions that do the same thing, check if the time passed and return true. Depending on the situation, we call one of the two functions.
Compile and upload the sketch to Arduino.
After going through this tutorial, you learn how to use millis(), how to build classes, determine the detection range, and print the output of an ultrasonic sensor.