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.
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.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Motor driver, DRV8833In Claw Machine Kit.
- 1 × Robot car chassis, 2WD
- 3 × IR distance sensor, Sharp GP2Y0A21
- 1 × Power, 6 V
- 3 × 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 |
|---|---|
| Arduino Uno R3 pin GND | Ground |
| Motor driver, DRV8833 pin BIN2 | Arduino Uno R3 pin 10 |
| Motor driver, DRV8833 pin BIN1 | Arduino Uno R3 pin 9 |
| Motor driver, DRV8833 pin AIN2 | Arduino Uno R3 pin 6 |
| Motor driver, DRV8833 pin AIN1 | Arduino Uno R3 pin 5 |
| IR distance sensor, Sharp GP2Y0A21 (1) pin VO | Arduino Uno R3 pin A0 |
| IR distance sensor, Sharp GP2Y0A21 (2) pin VO | Arduino Uno R3 pin A1 |
| IR distance sensor, Sharp GP2Y0A21 (3) pin VO | Arduino Uno R3 pin A2 |
| Motor driver, DRV8833 pin VM | 6 V |
| Motor driver, DRV8833 pin GND | Ground |
| 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 GND | Ground |
| IR distance sensor, Sharp GP2Y0A21 (1) pin VCC | 5 V |
| IR distance sensor, Sharp GP2Y0A21 (2) pin GND | Ground |
| IR distance sensor, Sharp GP2Y0A21 (2) pin VCC | 5 V |
| IR distance sensor, Sharp GP2Y0A21 (3) pin GND | Ground |
| IR distance sensor, Sharp GP2Y0A21 (3) pin VCC | 5 V |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// 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 editorTroubleshooting
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.