Scanning obstacle rover on an Arduino Uno
A robot with an ultrasonic sensor on a servo neck. Blocked, it stops, looks right, ahead and left, picks the clearest way and turns that way. Boxed in, it turns around. The whole build, an Arduino Uno and 4 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $47. On Mokxi they're free.
What you'll learn
- Sensors
- Motors
- Moving a servo to an angle
- Using the Motor driver
- Difficulty: Intermediate
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 8
How it works
A robot with an ultrasonic sensor on a servo neck. Blocked, it stops, looks right, ahead and left, picks the clearest way and turns that way. Boxed in, it turns around.
Arduino Uno R3 runs the sketch below. The circuit around it: Motor driver, TB6612FNG, Robot car chassis, 2WD, Hobby servo, Ultrasonic ranger. pinMode sets TRIG as an output. digitalWrite drives TRIG LOW. delay waits 70 milliseconds. Serial.begin opens the serial port at 115200 baud. millis reads the time since power-up, so the sketch can time things without stopping.
Parts list
8 parts, plus the jumper wires. They retail for about $47; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Motor driver, TB6612FNG
- 1 × Robot car chassis, 2WDIn Robot Car Kit.
- 1 × Hobby servoIn Halloween Prop Kit, Christmas Lights Kit, Starter Projects Kit.
- 1 × Ultrasonic rangerIn Halloween Prop Kit, Robot Car Kit.
- 1 × Power, 6 V
- 2 × Power, 5 V
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Motor driver, TB6612FNG pin GND1 | Arduino Uno R3 pin GND |
| Ultrasonic ranger pin TRIG | Arduino Uno R3 pin 12 |
| Ultrasonic ranger pin ECHO | Arduino Uno R3 pin 11 |
| Hobby servo pin PWM | Arduino Uno R3 pin 10 |
| Motor driver, TB6612FNG pin STBY | Arduino Uno R3 pin 9 |
| Motor driver, TB6612FNG pin BIN2 | Arduino Uno R3 pin 8 |
| Motor driver, TB6612FNG pin BIN1 | Arduino Uno R3 pin 7 |
| Motor driver, TB6612FNG pin PWMB | Arduino Uno R3 pin 6 |
| Motor driver, TB6612FNG pin PWMA | Arduino Uno R3 pin 5 |
| Motor driver, TB6612FNG pin AIN2 | Arduino Uno R3 pin 4 |
| Motor driver, TB6612FNG pin AIN1 | Arduino Uno R3 pin 2 |
| Motor driver, TB6612FNG pin VCC | Arduino Uno R3 pin 5V |
| Motor driver, TB6612FNG pin VM | 6 V |
| Robot car chassis, 2WD pin L+ | Motor driver, TB6612FNG pin AO1 |
| Robot car chassis, 2WD pin L- | Motor driver, TB6612FNG pin AO2 |
| Robot car chassis, 2WD pin R- | Motor driver, TB6612FNG pin BO2 |
| Robot car chassis, 2WD pin R+ | Motor driver, TB6612FNG pin BO1 |
| Motor driver, TB6612FNG pin GND3 | Ground |
| Hobby servo pin GND | Ground |
| Hobby servo pin VCC | 5 V |
| Ultrasonic ranger pin VCC | 5 V |
| Ultrasonic ranger pin GND | Ground |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Scanning obstacle rover: an ultrasonic eye on a servo, so it can look around.
//
// HC-SR04 on the servo: TRIG pin 12, ECHO pin 11 (VCC to 5V, GND to ground)
// Servo (the neck): signal pin 10, 90 degrees is straight ahead, 180 is left
// TB6612: PWMA 5, AIN1 2, AIN2 4 (left) PWMB 6, BIN1 7, BIN2 8 (right) STBY 9
//
// A fixed sensor can only say "something is ahead". Put it on a servo and the
// robot can ask which way is clear, which turns a dumb bumper into a decision.
//
// # The loop
//
// cruise drive forward, pinging straight ahead every 70 ms
// blocked something closer than STOP_CM: stop
// scan turn the head to the right, ahead and left, and ping each way
// (three pings, the middle one kept, so one bad echo cannot fool it)
// choose the direction with the longest distance wins
// turn spin toward it by about the angle the head was turned, and go
//
// If even the best way is closer than TRAPPED_CM the robot is in a corner and
// it turns right around instead.
//
// # Turning by angle
//
// A turn is timed, not measured. A standing spin at this speed turns about 0.39
// degrees every millisecond, so 2.6 ms buys a degree. A wheel encoder would
// measure the turn instead (see the wheel-encoder project).
#include <Servo.h>
#include <SparkFun_TB6612.h>
const int TRIG = 12;
const int ECHO = 11;
const int NECK = 10;
const int AIN1 = 2;
const int AIN2 = 4;
const int PWMA = 5;
const int BIN1 = 7;
const int BIN2 = 8;
const int PWMB = 6;
const int STBY = 9;
const int STOP_CM = 25;
const int TRAPPED_CM = 20;
const int CRUISE = 170;
const int SPIN = 170;
const int ANGLES[3] = {20, 90, 160};
const char *NAMES[3] = {"right", "ahead", "left"};
Motor leftMotor = Motor(AIN1, AIN2, PWMA, 1, STBY);
Motor rightMotor = Motor(BIN1, BIN2, PWMB, 1, STBY);
Servo neck;
unsigned long lastPing = 0;
// Distance in centimeters, 999 when nothing answers.
int ping() {
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG, LOW);
unsigned long echo = pulseIn(ECHO, HIGH, 12000);
if (echo == 0) return 999;
return echo / 58;
}
// The middle of three pings: one stray echo cannot change the answer.
int steadyPing() {
int a = ping();
delay(70);
int b = ping();
delay(70);
int c = ping();
if ((a >= b && a <= c) || (a <= b && a >= c)) return a;
if ((b >= a && b <= c) || (b <= a && b >= c)) return b;
return c;
}
void drive(int left, int right) {
leftMotor.drive(left);
rightMotor.drive(right);
}
// Positive degrees turn left (counterclockwise), negative turn right.
void turnBy(int degrees) {
if (degrees == 0) return;
int direction = degrees > 0 ? -1 : 1;
int size = degrees > 0 ? degrees : -degrees;
drive(0, 0);
delay(150);
drive(SPIN * direction, -SPIN * direction);
delay((long)size * 13 / 5);
drive(0, 0);
delay(150);
}
void scanAndChoose() {
int best = -1;
int bestCm = -1;
int seen[3];
for (int i = 0; i < 3; i++) {
neck.write(ANGLES[i]);
delay(450);
seen[i] = steadyPing();
if (seen[i] > bestCm) {
bestCm = seen[i];
best = i;
}
}
neck.write(90);
Serial.print("scan: right ");
Serial.print(seen[0]);
Serial.print(" cm, ahead ");
Serial.print(seen[1]);
Serial.print(" cm, left ");
Serial.print(seen[2]);
Serial.println(" cm");
if (bestCm < TRAPPED_CM) {
Serial.println("boxed in: turning around");
turnBy(180);
} else {
Serial.print("clearest way: ");
Serial.println(NAMES[best]);
turnBy(ANGLES[best] - 90);
}
}
void setup() {
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
neck.attach(NECK);
neck.write(90);
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: scanning obstacle rover");
Serial.println("ready");
delay(600);
}
void loop() {
if (millis() - lastPing < 70) return;
lastPing = millis();
int ahead = ping();
if (ahead < STOP_CM) {
drive(0, 0);
Serial.print("blocked at ");
Serial.print(ahead);
Serial.println(" cm: looking around");
scanAndChoose();
} else {
drive(CRUISE, CRUISE);
}
}
Code walkthrough
The key functions of the sketch, and what each one does.
setup sketch.ino
Runs once when the board starts. It gets the pins and libraries ready.
void setup() {
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
neck.attach(NECK);
neck.write(90);
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: scanning obstacle rover");
Serial.println("ready");
delay(600);
}loop sketch.ino
Runs over and over for as long as the board has power. This is where the project does its work.
void loop() {
if (millis() - lastPing < 70) return;
lastPing = millis();
int ahead = ping();
if (ahead < STOP_CM) {
drive(0, 0);
Serial.print("blocked at ");
Serial.print(ahead);
Serial.println(" cm: looking around");
scanAndChoose();
} else {
drive(CRUISE, CRUISE);
}
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- The motors do not move. Motor driver
Set STBY HIGH. Power the motors at VM (about 4.5 to 13.5 V) and logic at VCC. Join all grounds. Then set the direction pins and a PWM level on the PWM pin. - The motor stops hard instead of coasting. Motor driver
Both input pins HIGH, or PWM at 0, brakes the motor. To let it coast, set both inputs LOW with PWM high. Per channel the chip handles about 1.2 A continuous. - The car drives in circles or goes backwards. Robot car chassis
Motor polarity decides the direction. Swap the two wires on a motor that spins the wrong way. For a straight run, the motors are never identical, so trim the speed on one side. - The motors do not run, or the board resets when they start. Robot car chassis
Motors draw far more current than a pin can give. Use a motor driver such as an L298N or L293D. Power the motors from a separate battery pack and join its ground with the Arduino ground. - The servo jitters, twitches or resets the board when it moves. Hobby servo
Power it from its own 5 V supply, not the Arduino 5 V pin, and join the grounds. A hobby servo can pull several hundred milliamps. Add a 100 uF to 470 uF capacitor across its supply. - The servo hums at the end of its travel or will not reach full angle. Hobby servo
Stay inside the pulse range, about 1000 to 2000 us for most hobby servos. Pushing past the end stop makes it strain. Calibrate the min and max pulse for your servo. - Distance reads zero, or never changes. Ultrasonic ranger
Power it from 5 V and share ground. Send a 10 microsecond HIGH pulse on TRIG, then time ECHO with pulseIn. Check TRIG and ECHO are not swapped. - Readings jump around or are wrong on a 3.3 V board. Ultrasonic ranger
ECHO outputs 5 V, so divide it down for 3.3 V boards. Aim at flat, hard surfaces. The range is about 2 cm to 4 m, and soft or angled targets bounce the sound away.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.