Robot vacuum behaviors on an Arduino Uno
A cleaning robot with two bumpers and a cliff sensor. It spirals outward, then cruises and bounces off walls, and a cliff always wins over a bump: three small behaviors in layers. 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 $42. 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: 7
How it works
A cleaning robot with two bumpers and a cliff sensor. It spirals outward, then cruises and bounces off walls, and a cliff always wins over a bump: three small behaviors in layers.
Arduino Uno R3 runs the sketch below. The circuit around it: Motor driver, DRV8833, Robot car chassis, 2WD, Limit switch, lever microswitch, Line sensor, TCRT5000. pinMode sets BUMP_LEFT as an input with its pull-up on. digitalRead reads whether CLIFF is HIGH or LOW. analogRead measures the voltage on A0 as a number. analogWrite sends a PWM signal out on b. delay waits 150 milliseconds.
Parts list
7 parts, plus the jumper wires. They retail for about $42; 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
- 2 × Limit switch, lever microswitchIn Claw Machine Kit, Vending Machine Kit.
- 1 × Line sensor, TCRT5000
- 1 × Power, 6 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 |
| Limit switch, lever microswitch (2) pin NO | Arduino Uno R3 pin 8 |
| Limit switch, lever microswitch (1) pin NO | Arduino Uno R3 pin 7 |
| Motor driver, DRV8833 pin AIN2 | Arduino Uno R3 pin 6 |
| Motor driver, DRV8833 pin AIN1 | Arduino Uno R3 pin 5 |
| Line sensor, TCRT5000 pin DO | Arduino Uno R3 pin 4 |
| Line sensor, TCRT5000 pin VCC | Arduino Uno R3 pin 5V |
| 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 |
| Limit switch, lever microswitch (1) pin COM | Ground |
| Limit switch, lever microswitch (2) pin COM | Ground |
| Line sensor, TCRT5000 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.
// Robot vacuum behaviors: how a cheap cleaner covers a floor with no map.
//
// DRV8833: AIN1 pin 5, AIN2 pin 6 (left motor) BIN1 pin 9, BIN2 pin 10 (right motor)
// Bumpers: left microswitch on pin 7, right on pin 8 (switch to ground, INPUT_PULLUP)
// Cliff sensor: a TCRT5000 pointing at the floor, DO on pin 4
//
// # Behaviors in layers
//
// There is no map and no plan. The robot has three small behaviors, and the
// higher one always wins (this is called a subsumption architecture):
//
// 1. CLIFF the floor stopped reflecting: back up and turn away. Highest,
// because a fall ends the job
// 2. BUMP a bumper pressed: back up and turn away from that side
// 3. COVER nothing is wrong, so clean: spiral outward for the first few
// seconds, then drive straight until something is hit
//
// Each layer only looks at its own sensors. COVER does not know about walls,
// and CLIFF does not know about covering the floor, yet together they
// cover a room.
//
// # The floor sensor
//
// A TCRT5000 looks down at the floor from a few millimeters. A floor reflects,
// so DO reads LOW. Reach a stair edge, or a black rug that swallows the light,
// and DO reads HIGH. HIGH means "no floor".
//
// # Random turns
//
// Turning by the same angle every time can leave the robot circling the same
// patch. A random angle between 60 and 150 degrees fixes that. Turns are
// timed spins: from a standstill, about 3 ms to the degree.
const int AIN1 = 5;
const int AIN2 = 6;
const int BIN1 = 9;
const int BIN2 = 10;
const int BUMP_LEFT = 7;
const int BUMP_RIGHT = 8;
const int CLIFF = 4;
const int CRUISE = 180;
const int SPIN = 170;
const unsigned long SPIRAL_MS = 5000;
enum Mode { SPIRAL, CRUISING };
Mode mode = SPIRAL;
unsigned long modeSince = 0;
void motor(int a, int b, int speed) {
if (speed >= 0) {
analogWrite(b, 0);
analogWrite(a, speed);
} else {
analogWrite(a, 0);
analogWrite(b, -speed);
}
}
void drive(int left, int right) {
motor(AIN1, AIN2, left);
motor(BIN1, BIN2, right);
}
// Positive turns left, negative turns right, in degrees.
void turn(int degrees) {
int direction = degrees > 0 ? -1 : 1;
long size = degrees > 0 ? degrees : -degrees;
drive(0, 0);
delay(150);
drive(SPIN * direction, -SPIN * direction);
delay(size * 3);
drive(0, 0);
delay(150);
}
// Back up for `ms`, then turn away by a random angle. `away` is 1 to turn
// left (the obstacle was on the right) and -1 to turn right.
void retreat(const char *why, unsigned long ms, int away) {
Serial.println(why);
drive(-CRUISE, -CRUISE);
delay(ms);
int degrees = random(60, 151);
Serial.print("turning ");
Serial.print(away > 0 ? "left " : "right ");
Serial.print(degrees);
Serial.println(" degrees");
turn(away * degrees);
mode = CRUISING;
modeSince = millis();
}
void setup() {
pinMode(BUMP_LEFT, INPUT_PULLUP);
pinMode(BUMP_RIGHT, INPUT_PULLUP);
pinMode(CLIFF, INPUT);
pinMode(AIN1, OUTPUT);
pinMode(AIN2, OUTPUT);
pinMode(BIN1, OUTPUT);
pinMode(BIN2, OUTPUT);
randomSeed(analogRead(A0));
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: robot vacuum behaviors");
Serial.println("ready: spiraling out");
modeSince = millis();
}
void loop() {
// Layer 1: the cliff beats everything.
if (digitalRead(CLIFF) == HIGH) {
retreat("cliff: backing up", 450, random(0, 2) ? 1 : -1);
return;
}
// Layer 2: bumpers.
bool left = digitalRead(BUMP_LEFT) == LOW;
bool right = digitalRead(BUMP_RIGHT) == LOW;
if (left || right) {
retreat(left ? "bumped on the left: backing up" : "bumped on the right: backing up", 350, left ? -1 : 1);
return;
}
// Layer 3: cover the floor.
unsigned long elapsed = millis() - modeSince;
if (mode == SPIRAL) {
if (elapsed > SPIRAL_MS) {
mode = CRUISING;
modeSince = millis();
Serial.println("spiral done: cruising");
return;
}
// The inside wheel starts slow and speeds up, so the circle widens.
int inside = 70 + elapsed / 45;
drive(inside, CRUISE);
} else {
drive(CRUISE, CRUISE);
}
delay(10);
}
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(BUMP_LEFT, INPUT_PULLUP);
pinMode(BUMP_RIGHT, INPUT_PULLUP);
pinMode(CLIFF, INPUT);
pinMode(AIN1, OUTPUT);
pinMode(AIN2, OUTPUT);
pinMode(BIN1, OUTPUT);
pinMode(BIN2, OUTPUT);
randomSeed(analogRead(A0));
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: robot vacuum behaviors");
Serial.println("ready: spiraling out");
modeSince = millis();
}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() {
// Layer 1: the cliff beats everything.
if (digitalRead(CLIFF) == HIGH) {
retreat("cliff: backing up", 450, random(0, 2) ? 1 : -1);
return;
}
// Layer 2: bumpers.
bool left = digitalRead(BUMP_LEFT) == LOW;
bool right = digitalRead(BUMP_RIGHT) == LOW;
if (left || right) {
retreat(left ? "bumped on the left: backing up" : "bumped on the right: backing up", 350, left ? -1 : 1);
return;
}
// Layer 3: cover the floor.
unsigned long elapsed = millis() - modeSince;
if (mode == SPIRAL) {
if (elapsed > SPIRAL_MS) {
mode = CRUISING;
modeSince = millis();
Serial.println("spiral done: cruising");
return;
}
// The inside wheel starts slow and speeds up, so the circle widens.
int inside = 70 + elapsed / 45;
drive(inside, CRUISE);
} else {
// ... 4 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 input reads the wrong level, or floats when released. Limit switch
Use COM and one contact. NC is closed at rest, NO closes when pressed. Add a pull-up or INPUT_PULLUP so the pin never floats. - Readings chatter when the lever moves. Limit switch
Contacts bounce for a few milliseconds. Debounce in software with a 10 to 20 ms delay, or add a small capacitor across the switch. - It cannot tell black from white. Line sensor
Keep the sensor about 2 mm to 10 mm above the surface. Set the trimmer on the module for the surface you use. Too far gives no change, and too close can saturate the eye. - The readings change with room light. Line sensor
Sunlight and bulbs contain infrared and confuse the sensor. Shade it from outside light. Calibrate on the actual black and white surfaces with analogRead, not just a fixed guess.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.