Zombie tripwire on an Arduino Uno
An ultrasonic tripwire across a corridor: every walker is counted on a display, a traffic light rises from green to red, and a siren sounds for a horde. The whole build, an Arduino Uno and 4 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 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
- Sensors
- Sound
- Lights
- Displays
- Difficulty: Intermediate
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 7
How it works
An ultrasonic tripwire across a corridor: every walker is counted on a display, a traffic light rises from green to red, and a siren sounds for a horde.
Arduino Uno R3 runs the sketch below. The circuit around it: Ultrasonic ranger, Piezo speaker, Traffic light module, Four-digit display, TM1637. pinMode sets TRIG as an output. digitalWrite drives TRIG LOW. Serial.begin opens the serial port at 115200 baud. tone plays a note on PIEZO. millis reads the time since power-up, so the sketch can time things without stopping.
Parts list
7 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 × Ultrasonic rangerIn Halloween Prop Kit.
- 2 × Power, 5 V
- 1 × Piezo speakerIn Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Traffic light module
- 1 × Four-digit display, TM1637In Pinball Table Kit, Spaceship Panel Kit.
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 |
|---|---|
| Piezo speaker pin 1 | Arduino Uno R3 pin 10 |
| Ultrasonic ranger pin TRIG | Arduino Uno R3 pin 3 |
| Ultrasonic ranger pin ECHO | Arduino Uno R3 pin 2 |
| Traffic light module pin G | Arduino Uno R3 pin A0 |
| Traffic light module pin Y | Arduino Uno R3 pin A1 |
| Traffic light module pin R | Arduino Uno R3 pin A2 |
| Four-digit display, TM1637 pin CLK | Arduino Uno R3 pin A3 |
| Four-digit display, TM1637 pin DIO | Arduino Uno R3 pin A4 |
| Ultrasonic ranger pin VCC | 5 V |
| Ultrasonic ranger pin GND | Ground |
| Piezo speaker pin 2 | Ground |
| Traffic light module pin GND | Ground |
| Four-digit display, TM1637 pin VCC | 5 V |
| Four-digit display, TM1637 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.
// Zombie tripwire: an HC-SR04 across a corridor, a traffic light, a TM1637
// and a piezo siren, on an Uno.
//
// HC-SR04 TRIG -> pin 3, ECHO -> pin 2
// traffic light green -> A0, yellow -> A1, red -> A2
// TM1637 CLK -> A3, DIO -> A4
// piezo pin 10 and GND
//
// The sensor looks across the corridor. Anything closer than 120 cm is a
// walker: it is counted once, however long it stands there. The traffic light
// shows how busy the last 20 seconds were:
//
// 0 or 1 walker green quiet
// 2 or 3 walkers yellow walkers about
// 4 or more red HORDE, and the siren sounds
//
// The TM1637 shows the total since power-up.
//
// # Counting a thing once: edge detection with hysteresis
//
// Asking "is something there?" 15 times a second would count one walker 15
// times a second. What matters is the moment the answer changes from no to
// yes: an edge. `blocked` remembers the last answer, and the walker is counted
// only on the way from false to true.
//
// A walker at exactly 120 cm would flicker in and out of the beam, so the
// beam is two thresholds, not one: it trips under 120 cm but lets go only
// past 140 cm. That gap is hysteresis, and it is why a thermostat does not
// click on and off at the set temperature either.
//
// # The last 20 seconds
//
// Eight timestamps are kept in a ring: a new crossing overwrites the oldest.
// "How many in the last 20 seconds" is a count of the entries younger than
// that, so old walkers fade out on their own and the light drops back to
// green with nobody resetting it.
#include <TM1637Display.h>
const uint8_t TRIG = 3;
const uint8_t ECHO = 2;
const uint8_t GREEN_LED = A0;
const uint8_t YELLOW_LED = A1;
const uint8_t RED_LED = A2;
const uint8_t PIEZO = 10;
const int TRIP_CM = 120;
const int CLEAR_CM = 140;
const unsigned long WINDOW_MS = 20000UL;
const int RING = 8;
TM1637Display display(A3, A4);
unsigned long seen[RING];
int next = 0;
int total = 0;
bool blocked = false;
int threat = -1; // 0 green, 1 yellow, 2 red; -1 so the first pass prints
unsigned long lastScan = 0;
unsigned long lastSiren = 0;
bool sirenHigh = false;
// Centimeters to the nearest thing, or 0 if no echo came back.
int measureCm() {
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG, LOW);
unsigned long us = pulseIn(ECHO, HIGH, 25000UL);
return (int)(us / 58);
}
int recent(unsigned long now) {
int n = 0;
for (int i = 0; i < RING; i++) {
if (seen[i] != 0 && now - seen[i] < WINDOW_MS) n++;
}
return n;
}
void setLight(int level) {
digitalWrite(GREEN_LED, level == 0);
digitalWrite(YELLOW_LED, level == 1);
digitalWrite(RED_LED, level == 2);
}
void setup() {
Serial.begin(115200);
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
pinMode(GREEN_LED, OUTPUT);
pinMode(YELLOW_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
pinMode(PIEZO, OUTPUT);
display.setBrightness(0x0f);
display.showNumberDec(0);
Serial.println("zombie tripwire armed");
}
void loop() {
unsigned long now = millis();
if (now - lastScan >= 70) {
lastScan = now;
int cm = measureCm();
bool inBeam = cm > 0 && cm < TRIP_CM;
bool outOfBeam = cm == 0 || cm > CLEAR_CM;
if (inBeam && !blocked) {
blocked = true;
total++;
seen[next] = now;
next = (next + 1) % RING;
display.showNumberDec(total);
Serial.print("walker #");
Serial.print(total);
Serial.print(" at ");
Serial.print(cm);
Serial.println(" cm");
} else if (outOfBeam) {
blocked = false;
}
}
int n = recent(now);
int level = n >= 4 ? 2 : (n >= 2 ? 1 : 0);
if (level != threat) {
threat = level;
setLight(level);
Serial.println(level == 2 ? "threat: HORDE" : (level == 1 ? "threat: walkers about" : "threat: quiet"));
if (level != 2) noTone(PIEZO);
}
// the siren: two notes, swapped every 300 ms, without stopping the loop
if (level == 2 && now - lastSiren >= 300) {
lastSiren = now;
sirenHigh = !sirenHigh;
tone(PIEZO, sirenHigh ? 960 : 640);
}
}
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(GREEN_LED, OUTPUT);
pinMode(YELLOW_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
pinMode(PIEZO, OUTPUT);
display.setBrightness(0x0f);
display.showNumberDec(0);
Serial.println("zombie tripwire armed");
}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();
if (now - lastScan >= 70) {
lastScan = now;
int cm = measureCm();
bool inBeam = cm > 0 && cm < TRIP_CM;
bool outOfBeam = cm == 0 || cm > CLEAR_CM;
if (inBeam && !blocked) {
blocked = true;
total++;
seen[next] = now;
next = (next + 1) % RING;
display.showNumberDec(total);
Serial.print("walker #");
Serial.print(total);
Serial.print(" at ");
Serial.print(cm);
Serial.println(" cm");
} else if (outOfBeam) {
blocked = false;
}
}
int n = recent(now);
int level = n >= 4 ? 2 : (n >= 2 ? 1 : 0);
if (level != threat) {
threat = level;
// ... 12 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- 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. - A passive piezo stays silent when you just set the pin HIGH. Piezo speaker
A passive piezo needs a changing signal. Use tone(pin, frequency) or a PWM wave. Around 2 to 4 kHz is loudest. A buzzer with a built in oscillator makes sound from plain DC instead. - Sound is very quiet or the pin seems overloaded. Piezo speaker
Drive the piezo from a pin directly, or through a 100 ohm resistor. For a bigger buzzer, switch it with a transistor. Check that you wired the right two legs and joined grounds. - One color never lights, or all three light together. Traffic light module
Check each wire goes to its own pin labeled R, Y, G, and that GND is connected. A pin driven high lights its LED. Resistors are already on the board, so do not add more. - Lights seem dim or the wrong color comes on in a sequence. Traffic light module
Confirm the pins in your code match the wires, and that the module gets the right supply voltage. Keep each state's delay long enough to see, and turn the old color off before the next. - The display does nothing. Four-digit display
Connect CLK and DIO to two digital pins, plus VCC and GND. It is not I2C, so no address and no Wire library. Use a TM1637 library and give it the same two pins. - Digits look wrong or in a strange order. Four-digit display
Check the library's segment mapping and digit order. The colon is bit 7 of the second digit. Brightness is set separately and can be 0 to 7.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.