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.
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.
Parts list
12 parts, plus the jumper wires. They retail for about $33; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 4 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 2 × LED, redIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 2 × LED, blueIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
- 1 × Piezo speakerIn Starter Kit, Claw Machine Kit, Vending Machine Kit.
Kits that can build it
These 10 starter kits have every part in the circuit. Each kit's page lists what else it can build.
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 |
|---|---|
| LED, red (1) pin C | Arduino Uno R3 pin GND |
| LED, red (2) pin C | Arduino Uno R3 pin GND |
| LED, blue (1) pin C | Arduino Uno R3 pin GND |
| LED, blue (2) pin C | Arduino Uno R3 pin GND |
| Pushbutton pin 2a | Arduino Uno R3 pin GND |
| Piezo speaker pin 2 | Arduino Uno R3 pin GND |
| Resistor, 220 Ω (1) pin 1 | Arduino Uno R3 pin 12 |
| Resistor, 220 Ω (2) pin 1 | Arduino Uno R3 pin 11 |
| Resistor, 220 Ω (3) pin 1 | Arduino Uno R3 pin 10 |
| Resistor, 220 Ω (4) pin 1 | Arduino Uno R3 pin 9 |
| Piezo speaker pin 1 | Arduino Uno R3 pin 7 |
| Pushbutton pin 1a | Arduino Uno R3 pin 2 |
| LED, red (1) pin A | Resistor, 220 Ω (1) pin 2 |
| LED, red (2) pin A | Resistor, 220 Ω (2) pin 2 |
| LED, blue (1) pin A | Resistor, 220 Ω (3) pin 2 |
| LED, blue (2) pin A | Resistor, 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.
// 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 usesINPUT_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.