Built-in project · Arduino Uno R3

Maze-solving robot on an Arduino Uno

A two-wheel robot with three Sharp infrared distance sensors. It keeps centered in a corridor, turns left at every opening (the left-hand rule), and turns around at a dead end. The whole build, an Arduino Uno and 5 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 25 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $65. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Sensors
  • Motors
  • Using the Motor driver
  • Using the Robot car chassis
  • Difficulty: Intermediate
  • Time: About 25 minutes
  • Board: Arduino Uno R3
  • Parts: 10

How it works

A two-wheel robot with three Sharp infrared distance sensors. It keeps centered in a corridor, turns left at every opening (the left-hand rule), and turns around at a dead end.

Arduino Uno R3 runs the sketch below. The circuit around it: Motor driver, DRV8833, Robot car chassis, 2WD, IR distance sensor, Sharp GP2Y0A21. pinMode sets AIN1 as an output. analogRead measures the voltage on pin as a number. analogWrite sends a PWM signal out on pinA. delay waits 200 milliseconds. Serial.begin opens the serial port at 115200 baud.

Parts list

10 parts, plus the jumper wires. They retail for about $65; 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
Arduino Uno R3 pin GNDGround
Motor driver, DRV8833 pin BIN2Arduino Uno R3 pin 10
Motor driver, DRV8833 pin BIN1Arduino Uno R3 pin 9
Motor driver, DRV8833 pin AIN2Arduino Uno R3 pin 6
Motor driver, DRV8833 pin AIN1Arduino Uno R3 pin 5
IR distance sensor, Sharp GP2Y0A21 (1) pin VOArduino Uno R3 pin A0
IR distance sensor, Sharp GP2Y0A21 (2) pin VOArduino Uno R3 pin A1
IR distance sensor, Sharp GP2Y0A21 (3) pin VOArduino Uno R3 pin A2
Motor driver, DRV8833 pin VM6 V
Motor driver, DRV8833 pin GNDGround
Robot car chassis, 2WD pin L+Motor driver, DRV8833 pin AOUT1
Robot car chassis, 2WD pin L-Motor driver, DRV8833 pin AOUT2
Robot car chassis, 2WD pin R+Motor driver, DRV8833 pin BOUT1
Robot car chassis, 2WD pin R-Motor driver, DRV8833 pin BOUT2
IR distance sensor, Sharp GP2Y0A21 (1) pin GNDGround
IR distance sensor, Sharp GP2Y0A21 (1) pin VCC5 V
IR distance sensor, Sharp GP2Y0A21 (2) pin GNDGround
IR distance sensor, Sharp GP2Y0A21 (2) pin VCC5 V
IR distance sensor, Sharp GP2Y0A21 (3) pin GNDGround
IR distance sensor, Sharp GP2Y0A21 (3) pin VCC5 V

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
// Maze-solving robot: the left-hand rule, with three Sharp distance sensors.
//
//   GP2Y0A21 left VO on A0, front on A1, right on A2 (VCC to 5V, GND to ground)
//   DRV8833: AIN1 pin 5, AIN2 pin 6 (left motor)   BIN1 pin 9, BIN2 pin 10 (right motor)
//
// # The left-hand rule
//
// Put a hand on the left wall and keep walking, and in any maze whose walls are
// all joined to the outside you will walk every corridor and come out. The robot
// does the same with a short list of rules, checked in this order every time:
//
//   1. an opening on the left: go forward until the wheels are past the
//      corner, turn left, and drive into the new corridor
//   2. the front is clear: drive on, steering to stay centered
//   3. an opening on the right: turn right
//   4. otherwise it is a dead end: turn around
//
// # Reading a Sharp sensor
//
// It does not give a distance. It gives a voltage that falls as the wall gets
// farther away, roughly 33.9 / (distance in cm + 4.74), so the sketch turns the
// voltage back into centimeters with the same curve. Closer than 10 cm the
// output drops again, which reads a very near wall as a far one. That is why the
// maze corridors in this project are wider than that.
//
// # Staying centered
//
// With a wall on each side, the difference between the left and right distances
// is how far the robot is off center. Each wheel speeds up or slows down in
// proportion to it, the simplest kind of feedback.
//
// # Timed turns
//
// A turn is a number of milliseconds of spinning on the spot, tuned for this
// chassis. A real robot would count wheel pulses instead, which a slot sensor
// on each wheel makes easy.

const int LEFT_EYE = A0;
const int FRONT_EYE = A1;
const int RIGHT_EYE = A2;

const int AIN1 = 5;
const int AIN2 = 6;
const int BIN1 = 9;
const int BIN2 = 10;

const float OPEN_CM = 30.0;
const float BLOCKED_CM = 15.0;
const int CRUISE = 170;
const int SPIN = 170;
const float KP = 3.0;

// Spinning on the spot from a standstill. The motors take about 150 ms to get
// up to speed, so a half turn takes less than twice the time of a quarter.
const unsigned long TURN_90_MS = 270;
const unsigned long TURN_180_MS = 500;
const unsigned long PAST_CORNER_MS = 250;
const unsigned long ENTER_MS = 350;

char path[24];
int steps = 0;

void motor(int pinA, int pinB, int speed) {
  if (speed > 255) speed = 255;
  if (speed < -255) speed = -255;
  analogWrite(pinA, speed > 0 ? speed : 0);
  analogWrite(pinB, speed < 0 ? -speed : 0);
}

void drive(int left, int right) {
  motor(AIN1, AIN2, left);
  motor(BIN1, BIN2, right);
}

float distanceCm(int pin) {
  float volts = analogRead(pin) * 5.0 / 1023.0;
  if (volts < 0.3) return 99.0;
  return 33.9 / volts - 4.74;
}

void note(char move) {
  if (steps < 23) {
    path[steps++] = move;
    path[steps] = 0;
  }
  Serial.print("path: ");
  Serial.println(path);
}

// Stop, then spin on the spot for `ms`. Stopping first means every turn starts
// from rest, so the same time gives the same angle.
void turn(int direction, unsigned long ms) {
  drive(0, 0);
  delay(200);
  drive(SPIN * direction, -SPIN * direction);
  delay(ms);
  drive(0, 0);
  delay(100);
}

void setup() {
  Serial.begin(115200);
  pinMode(AIN1, OUTPUT);
  pinMode(AIN2, OUTPUT);
  pinMode(BIN1, OUTPUT);
  pinMode(BIN2, OUTPUT);
  path[0] = 0;
  Serial.println("Mokxi Arduino Uno: maze-solving robot");
  Serial.println("ready: left hand on the wall");
  delay(500);
}

void loop() {
  float left = distanceCm(LEFT_EYE);
  float front = distanceCm(FRONT_EYE);
  float right = distanceCm(RIGHT_EYE);

  if (left > OPEN_CM) {
    Serial.println("opening on the left: turning left");
    drive(CRUISE, CRUISE);
    delay(PAST_CORNER_MS);
    turn(-1, TURN_90_MS);
    drive(CRUISE, CRUISE);
    delay(ENTER_MS);
    note('L');
    return;
  }

  if (front > BLOCKED_CM) {
    // Both walls near: steer by the difference. One wall gone: hold straight.
    float error = 0;
    if (right < OPEN_CM) error = left - right;
    int correction = (int)(KP * error);
    drive(CRUISE - correction, CRUISE + correction);
    delay(20);
    return;
  }

  if (right > OPEN_CM) {
    Serial.println("wall ahead, opening on the right: turning right");
    turn(1, TURN_90_MS);
    note('R');
  } else {
    Serial.println("dead end: turning around");
    turn(1, TURN_180_MS);
    note('U');
  }
}

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() {
  Serial.begin(115200);
  pinMode(AIN1, OUTPUT);
  pinMode(AIN2, OUTPUT);
  pinMode(BIN1, OUTPUT);
  pinMode(BIN2, OUTPUT);
  path[0] = 0;
  Serial.println("Mokxi Arduino Uno: maze-solving robot");
  Serial.println("ready: left hand on the wall");
  delay(500);
}

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() {
  float left = distanceCm(LEFT_EYE);
  float front = distanceCm(FRONT_EYE);
  float right = distanceCm(RIGHT_EYE);

  if (left > OPEN_CM) {
    Serial.println("opening on the left: turning left");
    drive(CRUISE, CRUISE);
    delay(PAST_CORNER_MS);
    turn(-1, TURN_90_MS);
    drive(CRUISE, CRUISE);
    delay(ENTER_MS);
    note('L');
    return;
  }

  if (front > BLOCKED_CM) {
    // Both walls near: steer by the difference. One wall gone: hold straight.
    float error = 0;
    if (right < OPEN_CM) error = left - right;
    int correction = (int)(KP * error);
    drive(CRUISE - correction, CRUISE + correction);
    delay(20);
    return;
  }

  if (right > OPEN_CM) {
    Serial.println("wall ahead, opening on the right: turning right");
  // ... 8 more lines in the editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The motor does not turn or only one direction works. Motor driver
    Power VM from 2.7 to 10.8 V and drive both inputs of a channel. One HIGH and one LOW spins it. Swap to reverse. Both LOW coasts, both HIGH brakes.
  • Speed control is jerky or the driver gets hot. Motor driver
    Use PWM on one input and hold the other steady. The chip gives about 1.5 A per bridge, so stay below that, and join grounds.
  • 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 distance reading is noisy or odd. IR distance sensor
    Add a capacitor of about 10 uF across the sensor supply. Average several readings. The output changes about every 38 ms, so do not read faster than that.
  • Close objects read as far away. IR distance sensor
    The output peaks near 6 cm and falls closer than that. Keep objects beyond the minimum range, and convert the voltage to distance using the datasheet curve, which is not linear.
  • Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
    Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
  • Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
    Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.