Built-in project · Arduino Uno R3

Police lights and siren on an Arduino Uno

Police lights and siren on an Arduino: red and blue LEDs flash in turn while a piezo wails or yelps, and a button changes the mode, all with millis(). The whole build, an Arduino Uno and 10 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.

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

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

The circuit runs here once the page has loaded.

What you'll learn

  • Doing several things at once with millis
  • Making a sound with the tone function
  • Stepping through modes with one button
  • Debouncing a button in software
  • Sweeping a pitch up and down
  • Difficulty: Beginner
  • Time: About 20 minutes
  • Board: Arduino Uno R3
  • Parts: 12

How it works

Two red LEDs on pins 12 and 11, two blue LEDs on pins 10 and 9, a piezo on pin 7 and a button on pin 2 make a police light bar. The lights flash red three times, then blue three times, each flash 60 ms.

The button steps through four modes: wail, yelp, lights only and off. Wail sweeps the siren from 650 to 1500 Hz and back every four seconds. Yelp does the same sweep eight times a second.

Nothing waits. The sketch uses millis to check three things each pass: the button, the lights and the siren. That is how it flashes and sounds at the same time.

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
LED, red (1) pin CArduino Uno R3 pin GND
LED, red (2) pin CArduino Uno R3 pin GND
LED, blue (1) pin CArduino Uno R3 pin GND
LED, blue (2) pin CArduino Uno R3 pin GND
Pushbutton pin 2aArduino Uno R3 pin GND
Piezo speaker pin 2Arduino Uno R3 pin GND
Resistor, 220 Ω (1) pin 1Arduino Uno R3 pin 12
Resistor, 220 Ω (2) pin 1Arduino Uno R3 pin 11
Resistor, 220 Ω (3) pin 1Arduino Uno R3 pin 10
Resistor, 220 Ω (4) pin 1Arduino Uno R3 pin 9
Piezo speaker pin 1Arduino Uno R3 pin 7
Pushbutton pin 1aArduino Uno R3 pin 2
LED, red (1) pin AResistor, 220 Ω (1) pin 2
LED, red (2) pin AResistor, 220 Ω (2) pin 2
LED, blue (1) pin AResistor, 220 Ω (3) pin 2
LED, blue (2) pin AResistor, 220 Ω (4) pin 2

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
// Police lights and siren: two red LEDs, two blue LEDs, a piezo and one
// button on an Arduino Uno.
//
//   red LEDs    pins 12 and 11, each through 220 ohms to GND
//   blue LEDs   pins 10 and 9, each through 220 ohms to GND
//   button      pin 2 and GND (INPUT_PULLUP: a press reads LOW)
//   piezo       pin 7 and GND
//
// Press the button to step through four modes:
//
//   wail    the siren rises from 650 to 1500 Hz and falls back, about every
//           four seconds, while the lights flash
//   yelp    the same sweep, but fast: eight times a second
//   lights  the lights flash and the siren is quiet
//   off     everything off
//
// # Two things at once, without delay()
//
// The lights and the siren each keep their own clock with millis(). Every
// time loop() goes round it asks "is it time for the next flash?" and "is it
// time to move the siren's pitch?", does whichever is due and moves on. With
// delay() the board could only do one of them at a time, and the button would
// be ignored while it waited.

const uint8_t RED[2] = {12, 11};
const uint8_t BLUE[2] = {10, 9};
const uint8_t BUTTON = 2;
const uint8_t PIEZO = 7;

enum Mode { WAIL, YELP, LIGHTS, OFF };
const char *const MODE_NAME[4] = {"wail", "yelp", "lights only", "off"};
Mode mode = WAIL;

const unsigned int LOW_HZ = 650;
const unsigned int HIGH_HZ = 1500;

// The flash pattern: red, red, red, then blue, blue, blue, 60 ms each flash.
// Even steps are on, odd steps are off.
uint8_t flashStep = 0;
unsigned long flashAt = 0;

// The siren: a pitch that climbs and falls, moved every 10 ms.
unsigned long sirenAt = 0;
unsigned long sirenStarted = 0;

bool lastButton = HIGH;
unsigned long buttonAt = 0;

void setLights(bool red, bool blue) {
  for (uint8_t i = 0; i < 2; i++) {
    digitalWrite(RED[i], red ? HIGH : LOW);
    digitalWrite(BLUE[i], blue ? HIGH : LOW);
  }
}

void updateLights(unsigned long now) {
  if (mode == OFF) {
    setLights(false, false);
    return;
  }
  if (now - flashAt < 60) return;
  flashAt = now;
  flashStep = (flashStep + 1) % 12;
  bool on = flashStep % 2 == 0;
  bool redTurn = flashStep < 6;
  setLights(on && redTurn, on && !redTurn);
}

void updateSiren(unsigned long now) {
  if (mode != WAIL && mode != YELP) {
    noTone(PIEZO);
    return;
  }
  if (now - sirenAt < 10) return;
  sirenAt = now;
  // One cycle up and back down: 4000 ms for the wail, 125 ms for the yelp.
  unsigned long cycle = mode == WAIL ? 4000 : 125;
  unsigned long t = (now - sirenStarted) % cycle;
  unsigned long half = cycle / 2;
  // 0 at the bottom of the sweep, `half` at the top.
  unsigned long rise = t < half ? t : cycle - t;
  unsigned int hz = LOW_HZ + (unsigned long)(HIGH_HZ - LOW_HZ) * rise / half;
  tone(PIEZO, hz);
}

void checkButton(unsigned long now) {
  bool reading = digitalRead(BUTTON);
  // A press is a fall from HIGH to LOW; 50 ms of quiet after one ignores the
  // bounce of the contacts.
  if (reading == LOW && lastButton == HIGH && now - buttonAt > 50) {
    mode = (Mode)((mode + 1) % 4);
    sirenStarted = now;
    Serial.print("mode: ");
    Serial.println(MODE_NAME[mode]);
  }
  if (reading != lastButton) buttonAt = now;
  lastButton = reading;
}

void setup() {
  Serial.begin(115200);
  for (uint8_t i = 0; i < 2; i++) {
    pinMode(RED[i], OUTPUT);
    pinMode(BLUE[i], OUTPUT);
  }
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(PIEZO, OUTPUT);
  Serial.println("Police lights and siren. Press the button to change mode.");
  Serial.print("mode: ");
  Serial.println(MODE_NAME[mode]);
}

void loop() {
  unsigned long now = millis();
  checkButton(now);
  updateLights(now);
  updateSiren(now);
}

Code walkthrough

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

updateLights sketch.ino

Counts through 12 steps, one every 60 ms. Even steps are on and odd steps are off. The first six steps use the red LEDs and the last six use the blue ones. In off mode, it turns everything off.

void updateLights(unsigned long now) {
  if (mode == OFF) {
    setLights(false, false);
    return;
  }
  if (now - flashAt < 60) return;
  flashAt = now;
  flashStep = (flashStep + 1) % 12;
  bool on = flashStep % 2 == 0;
  bool redTurn = flashStep < 6;
  setLights(on && redTurn, on && !redTurn);
}

updateSiren sketch.ino

Every 10 ms it works out where it is in the sweep. The cycle is 4000 ms for the wail and 125 ms for the yelp. It converts that to a pitch between LOW_HZ and HIGH_HZ and plays it with tone. Other modes call noTone.

void updateSiren(unsigned long now) {
  if (mode != WAIL && mode != YELP) {
    noTone(PIEZO);
    return;
  }
  if (now - sirenAt < 10) return;
  sirenAt = now;
  // One cycle up and back down: 4000 ms for the wail, 125 ms for the yelp.
  unsigned long cycle = mode == WAIL ? 4000 : 125;
  unsigned long t = (now - sirenStarted) % cycle;
  unsigned long half = cycle / 2;
  // 0 at the bottom of the sweep, `half` at the top.
  unsigned long rise = t < half ? t : cycle - t;
  unsigned int hz = LOW_HZ + (unsigned long)(HIGH_HZ - LOW_HZ) * rise / half;
  tone(PIEZO, hz);
}

checkButton sketch.ino

Looks for the button going from HIGH to LOW, which is a press, and ignores any change within 50 ms of the last one to skip contact bounce. Each press moves to the next mode and restarts the siren sweep.

void checkButton(unsigned long now) {
  bool reading = digitalRead(BUTTON);
  // A press is a fall from HIGH to LOW; 50 ms of quiet after one ignores the
  // bounce of the contacts.
  if (reading == LOW && lastButton == HIGH && now - buttonAt > 50) {
    mode = (Mode)((mode + 1) % 4);
    sirenStarted = now;
    Serial.print("mode: ");
    Serial.println(MODE_NAME[mode]);
  }
  if (reading != lastButton) buttonAt = now;
  lastButton = reading;
}

Troubleshooting

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

  • The button does not change modes This project
    The button goes between pin 2 and ground. A press reads LOW because the pin uses INPUT_PULLUP.
  • Only some of the LEDs flash This project
    Each LED has its own 220 ohm resistor. Check that every one is on the right pin: 12, 11, 10 and 9.
  • The siren is silent This project
    In the lights only and off modes the siren is quiet on purpose. Press the button until it says wail or yelp.
  • The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
    Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure.
  • A pull-up or pull-down resistor is missing, so the input floats and reads random values. Resistor
    Tie the input to 5 V or ground through about 10k. Or use INPUT_PULLUP and wire the switch to ground.
  • The LED does not light, or it lights once and then dies. LED
    LEDs have polarity. The longer leg (anode) goes toward the positive side. Always add a series resistor, about 220 ohms to 1 k ohm on a 5 V pin.
  • A blue or white LED stays dark on 3.3 V. LED
    Blue and white LEDs drop about 3 V, so 3.3 V leaves almost nothing for the resistor. Use a 5 V supply, or a smaller resistor and a red or green LED.
  • The input reads random HIGH and LOW when the button is not pressed. Pushbutton
    A floating pin picks up noise. Add a 10 k pull-down or pull-up resistor, or use INPUT_PULLUP and wire the button between the pin and GND. Then pressed reads LOW.

Change it and keep it

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