Sumo robot on an Arduino Uno
A mini-sumo robot with an ultrasonic eye and two edge sensors. It spins to find the other robot, charges when it is within 40 cm, and backs away from the black border. 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 $45. 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 mini-sumo robot with an ultrasonic eye and two edge sensors. It spins to find the other robot, charges when it is within 40 cm, and backs away from the black border.
Arduino Uno R3 runs the sketch below. The circuit around it: Motor driver, DRV8833, Robot car chassis, 2WD, Ultrasonic ranger, Line sensor, TCRT5000. pinMode sets TRIG as an output. digitalWrite drives TRIG LOW. digitalRead reads whether EDGE_LEFT is HIGH or LOW. analogWrite sends a PWM signal out on b. delay waits START_DELAY_MS milliseconds.
Parts list
10 parts, plus the jumper wires. They retail for about $45; 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
- 1 × Ultrasonic rangerIn Halloween Prop Kit.
- 2 × Line sensor, TCRT5000
- 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 |
| Ultrasonic ranger pin TRIG | Arduino Uno R3 pin 12 |
| Ultrasonic ranger pin ECHO | Arduino Uno R3 pin 11 |
| Motor driver, DRV8833 pin BIN2 | Arduino Uno R3 pin 10 |
| Motor driver, DRV8833 pin BIN1 | Arduino Uno R3 pin 9 |
| Line sensor, TCRT5000 (2) pin DO | Arduino Uno R3 pin 8 |
| Line sensor, TCRT5000 (1) pin DO | 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 |
| 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 |
| Ultrasonic ranger pin VCC | 5 V |
| Ultrasonic ranger pin GND | Ground |
| Line sensor, TCRT5000 (1) pin VCC | 5 V |
| Line sensor, TCRT5000 (1) pin GND | Ground |
| Line sensor, TCRT5000 (2) pin VCC | 5 V |
| Line sensor, TCRT5000 (2) 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.
// Sumo robot: push the other robot out of the ring and stay in it yourself.
//
// HC-SR04 on the front: TRIG pin 12, ECHO pin 11
// Two TCRT5000 line sensors on the front corners: left DO on pin 7, right DO on pin 8
// DRV8833 driver: AIN1 pin 5, AIN2 pin 6 (left motor) BIN1 pin 9, BIN2 pin 10 (right motor)
//
// # The ring
//
// The ring is white board with a black tape border. A line sensor's DO pin
// reads LOW over white and HIGH over black, so HIGH on either front corner
// means that corner has reached the edge.
//
// # Four states
//
// SEARCH spin on the spot, one way and then the other, and ping
// CHARGE the other robot is closer than 40 cm: full speed straight at it
// BACKOFF an edge sensor saw the border: reverse so it is not pushed over
// TURN swing away from the side that saw the border, then search again
//
// A state machine is a robot that is always in exactly one state and has a
// short list of reasons to change. The border always wins: the check for it
// comes first in loop(), because a robot that charges off the ring has lost no
// matter how good its eyes are.
//
// # The ultrasonic eye
//
// The sensor ignores a trigger that comes less than 60 ms after the last one,
// and a ping sent right on the mark can be ignored too, which reads as "nothing
// there" and makes the robot flicker between charging and searching. So the
// pings are 70 ms apart. pulseIn() gets a timeout so that an empty ring costs
// 8 ms a ping and not the sensor's full 38 ms, which would leave the robot blind
// to the border for a long time.
//
// # Starting
//
// Sumo rules make a robot wait before it moves. This one waits three seconds.
const int TRIG = 12;
const int ECHO = 11;
const int EDGE_LEFT = 7;
const int EDGE_RIGHT = 8;
const int AIN1 = 5;
const int AIN2 = 6;
const int BIN1 = 9;
const int BIN2 = 10;
const int CHARGE_CM = 40;
const int PING_GAP_MS = 70;
const unsigned long START_DELAY_MS = 3000;
const unsigned long BACKOFF_MS = 350;
const unsigned long TURN_MS = 320;
const unsigned long SPIN_MS = 1400;
const int FULL = 255;
const int SPIN = 150;
enum State { SEARCH, CHARGE, BACKOFF, TURN };
State state = SEARCH;
unsigned long stateSince = 0;
unsigned long lastPing = 0;
int spinDirection = 1;
int turnDirection = 1;
int distanceCm = 999;
// PWM on one input with the other low is the speed; swap them to reverse.
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);
}
void enter(State next, const char *why) {
if (state != next) {
Serial.println(why);
}
state = next;
stateSince = millis();
}
// Distance to whatever is in front, in centimeters; 999 when nothing is in
// range.
int ping() {
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG, LOW);
unsigned long echo = pulseIn(ECHO, HIGH, 8000);
if (echo == 0) return 999;
return echo / 58;
}
void setup() {
Serial.begin(115200);
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
pinMode(EDGE_LEFT, INPUT);
pinMode(EDGE_RIGHT, INPUT);
pinMode(AIN1, OUTPUT);
pinMode(AIN2, OUTPUT);
pinMode(BIN1, OUTPUT);
pinMode(BIN2, OUTPUT);
Serial.println("Mokxi Arduino Uno: sumo robot");
Serial.println("ready: starting in 3 seconds");
delay(START_DELAY_MS);
Serial.println("fight");
enter(SEARCH, "");
}
void loop() {
unsigned long now = millis();
bool leftEdge = digitalRead(EDGE_LEFT) == HIGH;
bool rightEdge = digitalRead(EDGE_RIGHT) == HIGH;
// The border beats everything else, whatever state the robot is in.
if ((leftEdge || rightEdge) && state != BACKOFF && state != TURN) {
// Turn away from the side that saw it; both at once, turn right.
turnDirection = leftEdge ? 1 : -1;
enter(BACKOFF, leftEdge ? "edge on the left: backing off" : "edge on the right: backing off");
}
if (state == BACKOFF) {
drive(-FULL, -FULL);
if (now - stateSince > BACKOFF_MS) enter(TURN, "");
return;
}
if (state == TURN) {
drive(SPIN * turnDirection, -SPIN * turnDirection);
if (now - stateSince > TURN_MS) enter(SEARCH, "searching");
return;
}
if (now - lastPing >= PING_GAP_MS) {
lastPing = now;
distanceCm = ping();
}
if (distanceCm < CHARGE_CM) {
if (state != CHARGE) {
Serial.print("opponent at ");
Serial.print(distanceCm);
Serial.println(" cm: charging");
}
state = CHARGE;
drive(FULL, FULL);
return;
}
if (state == CHARGE) enter(SEARCH, "lost it: searching");
// Search: spin one way, then the other, so a robot that started facing
// away from the other one finds it either side.
if (now - stateSince > SPIN_MS) {
spinDirection = -spinDirection;
stateSince = now;
}
drive(SPIN * spinDirection, -SPIN * spinDirection);
}
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(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
pinMode(EDGE_LEFT, INPUT);
pinMode(EDGE_RIGHT, INPUT);
pinMode(AIN1, OUTPUT);
pinMode(AIN2, OUTPUT);
pinMode(BIN1, OUTPUT);
pinMode(BIN2, OUTPUT);
Serial.println("Mokxi Arduino Uno: sumo robot");
Serial.println("ready: starting in 3 seconds");
delay(START_DELAY_MS);
Serial.println("fight");
enter(SEARCH, "");
}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() {
unsigned long now = millis();
bool leftEdge = digitalRead(EDGE_LEFT) == HIGH;
bool rightEdge = digitalRead(EDGE_RIGHT) == HIGH;
// The border beats everything else, whatever state the robot is in.
if ((leftEdge || rightEdge) && state != BACKOFF && state != TURN) {
// Turn away from the side that saw it; both at once, turn right.
turnDirection = leftEdge ? 1 : -1;
enter(BACKOFF, leftEdge ? "edge on the left: backing off" : "edge on the right: backing off");
}
if (state == BACKOFF) {
drive(-FULL, -FULL);
if (now - stateSince > BACKOFF_MS) enter(TURN, "");
return;
}
if (state == TURN) {
drive(SPIN * turnDirection, -SPIN * turnDirection);
if (now - stateSince > TURN_MS) enter(SEARCH, "searching");
return;
}
if (now - lastPing >= PING_GAP_MS) {
lastPing = now;
distanceCm = ping();
}
// ... 21 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. - 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. - 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.