Built-in project · Arduino Uno R3

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.

The circuit runs here once the page has loaded.

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.

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 pinConnects to
Motor driver, TB6612FNG pin GND1Arduino Uno R3 pin GND
Ultrasonic ranger pin TRIGArduino Uno R3 pin 12
Ultrasonic ranger pin ECHOArduino Uno R3 pin 11
Hobby servo pin PWMArduino Uno R3 pin 10
Motor driver, TB6612FNG pin STBYArduino Uno R3 pin 9
Motor driver, TB6612FNG pin BIN2Arduino Uno R3 pin 8
Motor driver, TB6612FNG pin BIN1Arduino Uno R3 pin 7
Motor driver, TB6612FNG pin PWMBArduino Uno R3 pin 6
Motor driver, TB6612FNG pin PWMAArduino Uno R3 pin 5
Motor driver, TB6612FNG pin AIN2Arduino Uno R3 pin 4
Motor driver, TB6612FNG pin AIN1Arduino Uno R3 pin 2
Motor driver, TB6612FNG pin VCCArduino Uno R3 pin 5V
Motor driver, TB6612FNG pin VM6 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 GND3Ground
Hobby servo pin GNDGround
Hobby servo pin VCC5 V
Ultrasonic ranger pin VCC5 V
Ultrasonic ranger pin GNDGround

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// 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.