Built-in project · Arduino Uno R3

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.

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 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.

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
Ultrasonic ranger pin TRIGArduino Uno R3 pin 12
Ultrasonic ranger pin ECHOArduino Uno R3 pin 11
Motor driver, DRV8833 pin BIN2Arduino Uno R3 pin 10
Motor driver, DRV8833 pin BIN1Arduino Uno R3 pin 9
Line sensor, TCRT5000 (2) pin DOArduino Uno R3 pin 8
Line sensor, TCRT5000 (1) pin DOArduino Uno R3 pin 7
Motor driver, DRV8833 pin AIN2Arduino Uno R3 pin 6
Motor driver, DRV8833 pin AIN1Arduino Uno R3 pin 5
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
Ultrasonic ranger pin VCC5 V
Ultrasonic ranger pin GNDGround
Line sensor, TCRT5000 (1) pin VCC5 V
Line sensor, TCRT5000 (1) pin GNDGround
Line sensor, TCRT5000 (2) pin VCC5 V
Line sensor, TCRT5000 (2) 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
// 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 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.
  • 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.