Built-in project · Arduino Uno R3

Fire fighting robot on an Arduino Uno

Three flame sensors steer a 2WD car on an L298N to the fire, then a relay runs a water pump while a servo sweeps the nozzle until the flame is out. The whole build, an Arduino Uno and 8 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.

Advanced About 45 minutes to build and run. Runs in your browser. Free, and no account needed.

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

The circuit runs here once the page has loaded.

What you'll learn

  • Reading digital flame sensors to decide a direction
  • A state machine: search, turn, approach, spray
  • Driving two motors through an L298N with speed and direction
  • Switching a pump with a relay and sweeping a servo
  • Choosing pins so the Servo library does not break PWM
  • Difficulty: Advanced
  • Time: About 45 minutes
  • Board: Arduino Uno R3
  • Parts: 16

How it works

Three flame sensors face left, center and right. Each one pulls its digital output LOW when it sees a flame. With no flame, the car spins slowly on the spot to search. If the left or right sensor sees fire, the car turns toward it. If the center sensor sees it, the car drives straight ahead.

The center sensor also has an analog output on A0. That reading gets lower as the flame gets closer. Below CLOSE (300), the car stops, the relay turns on the water pump, and the servo sweeps the nozzle between 60 and 120 degrees.

Once no sensor has seen a flame for OUT_MS (1 second), the pump stops, the nozzle goes back to 90 degrees and the search starts again.

Parts list

16 parts, plus the jumper wires. They retail for about $50; 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
Relay module pin INArduino Uno R3 pin 12
Hobby servo pin PWMArduino Uno R3 pin 11
Motor driver module, L298N pin IN4Arduino Uno R3 pin 9
Motor driver module, L298N pin IN3Arduino Uno R3 pin 8
Motor driver module, L298N pin IN2Arduino Uno R3 pin 7
Motor driver module, L298N pin IN1Arduino Uno R3 pin 6
Motor driver module, L298N pin ENAArduino Uno R3 pin 5
Motor driver module, L298N pin ENBArduino Uno R3 pin 3
Flame sensor (2) pin AOArduino Uno R3 pin A0
Flame sensor (1) pin DOArduino Uno R3 pin A1
Flame sensor (2) pin DOArduino Uno R3 pin A2
Flame sensor (3) pin DOArduino Uno R3 pin A3
Robot car chassis, 2WD pin L+Motor driver module, L298N pin OUT1
Robot car chassis, 2WD pin L-Motor driver module, L298N pin OUT2
Motor driver module, L298N pin 12V7.4 V
Motor driver module, L298N pin GNDGround
Robot car chassis, 2WD pin R+Motor driver module, L298N pin OUT3
Robot car chassis, 2WD pin R-Motor driver module, L298N pin OUT4
Flame sensor (1) pin GNDGround
Flame sensor (1) pin VCC5 V
Flame sensor (2) pin GNDGround
Flame sensor (2) pin VCC5 V
Flame sensor (3) pin GNDGround
Flame sensor (3) pin VCC5 V
Hobby servo pin GNDGround
Hobby servo pin VCC5 V
Relay module pin GNDGround
Relay module pin VCC5 V
Mini water pump pin V+Relay module pin NO
Relay module pin COM6 V
Mini water pump pin V-Ground

The note on the canvas

Word for word, as it sits beside the circuit in the editor.

Try it
1. Press Run. The car spins slowly: it is searching.
2. Drag the left flame sensor's flame in to about 45 cm. The car turns left toward it.
3. Put the left one back to "no flame" and drag the middle one to 45 cm. The car drives straight at the fire.
4. Drag the middle flame in to 30 cm. The car stops, the pump runs and the nozzle sweeps.
5. Drag the flame back out to "no flame". A second later the pump stops and the search starts again.

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
// Fire fighting robot: three flame sensors find the fire, a 2WD car on an
// L298N drives to it, and a water pump on a relay puts it out while a servo
// sweeps the nozzle from side to side.
//
//   flame sensors   left DO on A1, center DO on A2, right DO on A3
//                   center AO on A0 (how close the flame is)
//                   VCC 5V, GND; DO goes LOW when a flame is in range
//   L298N           ENA pin 5, IN1 pin 6, IN2 pin 7    left motor
//                   ENB pin 3, IN3 pin 8, IN4 pin 9    right motor
//                   (both EN jumpers off), 12V from the 7.4 V pack
//   nozzle servo    signal on pin 11, 5V and GND
//   pump relay      IN on pin 12; the 6 V pump runs through COM and NO
//
// ENB is on pin 3, not pin 10, because the Servo library takes Timer1 on an
// Uno, and Timer1 is the PWM on pins 9 and 10. With ENB on pin 10 the right
// motor would lose its speed control the moment the servo attaches.
//
// HOW IT FINDS THE FIRE. Each sensor's comparator pulls DO low when the
// infrared it sees passes the trimmer, about half a meter from a lighter at
// the middle setting. The robot:
//
//   1. spins slowly on the spot while no sensor sees a flame (SEARCH),
//   2. turns toward the side sensor that sees one (TURN),
//   3. drives straight at it once the center sensor sees it (APPROACH),
//   4. stops when the center AO says the flame is close, below CLOSE, and
//      runs the pump with the nozzle sweeping (SPRAY),
//   5. keeps spraying until no sensor has seen a flame for OUT_MS, then
//      parks the nozzle and goes back to searching.
//
// AO falls as the flame comes closer (more infrared, more current through
// the phototransistor), so "close" is a low reading. CLOSE = 300 is about
// 40 cm here; raise it to stop farther away, lower it to get closer.
//
// In Mokxi the car stays in the middle of its window, so the distance to the
// fire is the slider on each flame sensor: drag the center one in to 45 cm
// and the car drives, in to 30 cm and it sprays, out to "no flame" and it
// goes back to searching.

#include <Servo.h>

const int ENA = 5, IN1 = 6, IN2 = 7;   // left motor
const int ENB = 3, IN3 = 8, IN4 = 9;   // right motor
const int FLAME_LEFT = A1, FLAME_CENTER = A2, FLAME_RIGHT = A3;
const int FLAME_AO = A0;
const int NOZZLE = 11;
const int PUMP = 12;

const int SEARCH_SPEED = 120;     // slow spin while looking
const int TURN_SPEED = 150;
const int DRIVE_SPEED = 170;
const int CLOSE = 300;            // center AO below this: close enough to spray
const unsigned long OUT_MS = 1000;  // no flame this long: the fire is out

enum State { SEARCH, TURN_LEFT, TURN_RIGHT, APPROACH, SPRAY };
State state = SEARCH;

Servo nozzle;
int nozzleAngle = 90;
int nozzleStep = 3;
unsigned long lastFlame = 0;

// One side of the car, as a signed speed from -255 to 255. Zero brakes.
void side(int en, int a, int b, int s) {
  digitalWrite(a, s > 0 ? HIGH : LOW);
  digitalWrite(b, s < 0 ? HIGH : LOW);
  if (s < 0) s = -s;
  analogWrite(en, s == 0 ? 255 : (s > 255 ? 255 : s));
}

void drive(int left, int right) {
  side(ENA, IN1, IN2, left);
  side(ENB, IN3, IN4, right);
}

bool sees(int pin) {
  return digitalRead(pin) == LOW;
}

void enter(State next, const char *why) {
  if (next == state) return;
  state = next;
  Serial.println(why);
  if (next != SPRAY) {
    digitalWrite(PUMP, LOW);
    nozzleAngle = 90;
    nozzle.write(nozzleAngle);
  }
}

void setup() {
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  pinMode(PUMP, OUTPUT);
  digitalWrite(PUMP, LOW);
  pinMode(FLAME_LEFT, INPUT);
  pinMode(FLAME_CENTER, INPUT);
  pinMode(FLAME_RIGHT, INPUT);
  nozzle.attach(NOZZLE);
  nozzle.write(nozzleAngle);
  drive(0, 0);
  Serial.begin(9600);
  Serial.println("fire robot: searching");
}

void loop() {
  bool left = sees(FLAME_LEFT);
  bool center = sees(FLAME_CENTER);
  bool right = sees(FLAME_RIGHT);
  int near = analogRead(FLAME_AO);
  if (left || center || right) lastFlame = millis();

  if (state == SPRAY) {
    // Keep spraying until no sensor has seen the flame for OUT_MS.
    if (millis() - lastFlame >= OUT_MS) {
      enter(SEARCH, "fire out: searching");
    } else {
      nozzleAngle += nozzleStep;
      if (nozzleAngle >= 120 || nozzleAngle <= 60) nozzleStep = -nozzleStep;
      nozzle.write(nozzleAngle);
    }
  } else if (center && near < CLOSE) {
    drive(0, 0);
    enter(SPRAY, "close: spraying");
    digitalWrite(PUMP, HIGH);
  } else if (center) {
    enter(APPROACH, "flame ahead: driving to it");
  } else if (left) {
    enter(TURN_LEFT, "flame left: turning left");
  } else if (right) {
    enter(TURN_RIGHT, "flame right: turning right");
  } else {
    enter(SEARCH, "no flame: searching");
  }

  switch (state) {
    case SEARCH: drive(SEARCH_SPEED, -SEARCH_SPEED); break;
    case TURN_LEFT: drive(-TURN_SPEED, TURN_SPEED); break;
    case TURN_RIGHT: drive(TURN_SPEED, -TURN_SPEED); break;
    case APPROACH: drive(DRIVE_SPEED, DRIVE_SPEED); break;
    case SPRAY: drive(0, 0); break;
  }
  delay(20);
}

Code walkthrough

The key functions of the sketch, and what each one does.

side sketch.ino

Drives one side of the car. A signed speed picks the direction: positive sets one input pin HIGH, negative sets the other. The size of the number goes to the enable pin with analogWrite, which sets the speed. A speed of zero brakes.

// One side of the car, as a signed speed from -255 to 255. Zero brakes.
void side(int en, int a, int b, int s) {
  digitalWrite(a, s > 0 ? HIGH : LOW);
  digitalWrite(b, s < 0 ? HIGH : LOW);
  if (s < 0) s = -s;
  analogWrite(en, s == 0 ? 255 : (s > 255 ? 255 : s));
}

enter sketch.ino

Changes the car's state and prints a message, but only when the state is really new. Whenever the new state is not spraying, it turns the pump off and puts the nozzle back at 90 degrees.

void enter(State next, const char *why) {
  if (next == state) return;
  state = next;
  Serial.println(why);
  if (next != SPRAY) {
    digitalWrite(PUMP, LOW);
    nozzleAngle = 90;
    nozzle.write(nozzleAngle);
  }
}

loop sketch.ino

Reads the three sensors and the analog value, and remembers the last time any flame was seen. Then it picks a state. Spray wins when the center sees fire and the reading is below CLOSE. Next come approach, turn left, turn right and search. The final switch sets the motor speeds for that state.

void loop() {
  bool left = sees(FLAME_LEFT);
  bool center = sees(FLAME_CENTER);
  bool right = sees(FLAME_RIGHT);
  int near = analogRead(FLAME_AO);
  if (left || center || right) lastFlame = millis();

  if (state == SPRAY) {
    // Keep spraying until no sensor has seen the flame for OUT_MS.
    if (millis() - lastFlame >= OUT_MS) {
      enter(SEARCH, "fire out: searching");
    } else {
      nozzleAngle += nozzleStep;
      if (nozzleAngle >= 120 || nozzleAngle <= 60) nozzleStep = -nozzleStep;
      nozzle.write(nozzleAngle);
    }
  } else if (center && near < CLOSE) {
    drive(0, 0);
    enter(SPRAY, "close: spraying");
    digitalWrite(PUMP, HIGH);
  } else if (center) {
    enter(APPROACH, "flame ahead: driving to it");
  } else if (left) {
    enter(TURN_LEFT, "flame left: turning left");
  } else if (right) {
    enter(TURN_RIGHT, "flame right: turning right");
  } else {
    enter(SEARCH, "no flame: searching");
  // ... 11 more lines in the editor

Troubleshooting

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

  • The right motor loses speed control when the servo starts This project
    The Servo library takes Timer1 on an Uno, which is the PWM for pins 9 and 10. That is why the right motor enable (ENB) is on pin 3. Keep it there.
  • The car sprays too early or gets too close This project
    Change CLOSE. Raising it makes the car stop farther away. Lowering it makes the car get closer first.
  • The motors do not run This project
    The L298N needs the 7.4 V pack on its 12V pin and a shared ground with the Uno. Both ENA and ENB jumpers should be off, because the sketch drives those pins.
  • The motors do not run or run weakly. Motor driver module
    Check the power jumpers and supply. Motor voltage goes on the +12V terminal, and ground must join the Arduino's ground. The driver drops about 2 V, so use a supply above the motor voltage.
  • Speed control with analogWrite does nothing. Motor driver module
    Remove the ENA and ENB jumpers and connect those pins to PWM pins. With the jumpers on, the channel is always at full speed.
  • 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 sensor triggers all the time or never, even with a flame. Flame sensor
    Turn the potentiometer to set the comparator threshold. DO goes low when a flame is detected, so test for LOW. Sunlight and incandescent bulbs also give infrared and cause false triggers.

Questions and answers

How does the robot know which way the fire is?

Each of the three flame sensors pulls its DO pin low when it sees a flame. The robot turns toward the side sensor that sees it and drives straight once the middle one does.

How does it know when it is close enough to spray?

The middle sensor’s AO voltage falls as the flame gets closer. Below a reading of 300, about 40 cm here, the car stops and the pump starts; change CLOSE to stop nearer or farther.

Why is ENB on pin 3 and not pin 10?

The Servo library uses Timer1 on an Uno, and that timer makes the PWM on pins 9 and 10. Pin 3 keeps speed control on the right motor while the nozzle servo runs.

When does it stop spraying?

When none of the three sensors has seen the flame for one second. It then parks the nozzle, switches the pump off and starts searching again.

Change it and keep it

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