Rescue beacon on an Arduino Uno
A white LED flashes SOS from a table of lengths, but only in the dark: a light sensor wakes it at dusk and a button forces it on. Plays the pattern without delay(). 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.
Beginner About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $27. On Mokxi they're free.
What you'll learn
- Lights
- Sensors
- Choosing and reading a resistor
- How an LED lights and why it needs a resistor
- Reading a button
- Difficulty: Beginner
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 7
How it works
A white LED flashes SOS from a table of lengths, but only in the dark: a light sensor wakes it at dusk and a button forces it on. Plays the pattern without delay().
Arduino Uno R3 runs the sketch below. The circuit around it: Resistor, 220 Ω, LED, white, Pushbutton, Light sensor, Resistor, 10k Ω. pinMode sets BEACON as an output. digitalWrite drives BEACON LOW. digitalRead reads whether TEST is HIGH or LOW. analogRead measures the voltage on SENSOR as a number. Serial.begin opens the serial port at 115200 baud.
Parts list
7 parts, plus the jumper wires. They retail for about $27; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, whiteIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
- 1 × Light sensor
- 1 × Power, 5 V
- 1 × Resistor, 10k ΩIn Starter Kit, Claw Machine Kit, Halloween Prop 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.
- ELEGOO UNO R3 Super Starter Kit
- ELEGOO UNO R3 Most Complete Starter Kit
- Freenove Ultimate Starter Kit
- ELEGOO UNO R3 Basic Starter Kit
- SunFounder Starter Kit for Arduino Uno R3
- SunFounder Inventor Lab Kit
- REXQualis UNO R3 Super Starter Kit
- LAFVIN Super Starter Kit
- keyestudio Super Starter Kit
- Adafruit MetroX Classic 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 |
|---|---|
| Resistor, 220 Ω pin 1 | Arduino Uno R3 pin 6 |
| Pushbutton pin 1a | Arduino Uno R3 pin 2 |
| Light sensor pin 2 | Arduino Uno R3 pin A0 |
| Resistor, 10k Ω pin 1 | Arduino Uno R3 pin A0 |
| LED, white pin A | Resistor, 220 Ω pin 2 |
| LED, white pin C | Ground |
| Pushbutton pin 2b | Ground |
| Light sensor pin 1 | 5 V |
| Resistor, 10k Ω pin 2 | Ground |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Rescue beacon: a white LED that flashes SOS after dark, on an Uno.
//
// A0 the middle of a divider: 5 V, an LDR, A0, 10k, GND
// pin 6 the beacon LED, through 220 ohm to GND
// pin 2 TEST button to GND (forces the beacon on, press again to release)
//
// In daylight the beacon sleeps, because a rescue signal nobody can see is a
// waste of battery. When the light drops it starts: three short flashes, three
// long, three short, a pause, and again. The TEST button turns it on whatever
// the light is, so you can see that it works.
//
// # A pattern is data
//
// Morse is not a program, it is a table. PATTERN lists how long each part of
// SOS lasts, in units of 200 ms, alternating ON, OFF, ON, OFF: the first
// number is a short flash, the second the gap after it, and so on. S is
// "1,1,1,1,1,3" (flash, gap, flash, gap, flash, and a longer gap that ends
// the letter), O is the same with 3s for the flashes. To send something else
// you change the table and not a line of code.
//
// # A player that never waits
//
// The sketch keeps one number, `step`, which is where in the table it is, and
// the time at which that step ends. loop() asks whether the time has come,
// and if it has, goes to the next step and flips the LED. Between those
// moments it is free to read the light sensor and the button, so the beacon
// can be switched off the instant it gets light, even half way through a
// flash. A version with delay() would have to finish the whole SOS first.
//
// # Light and dark with a gap between them
//
// Dusk is not a moment, and a cloud passing over the sensor should not start
// the beacon. Two thresholds, one for going dark and a higher one for coming
// back to light, keep it from flickering in between; the same idea as a
// thermostat's gap.
const uint8_t SENSOR = A0;
const uint8_t BEACON = 6;
const uint8_t TEST = 2;
const int DARK = 250; // below this the beacon wakes (0 to 1023)
const int LIGHT = 500; // above this it goes back to sleep
const unsigned long UNIT_MS = 200;
const unsigned long REST_MS = 5000;
// ON, OFF, ON, OFF ... in units: S (. . .), O (- - -), S (. . .)
const uint8_t PATTERN[] = {1, 1, 1, 1, 1, 3, 3, 1, 3, 1, 3, 3, 1, 1, 1, 1, 1, 7};
const uint8_t STEPS = sizeof(PATTERN);
bool night = false;
bool forced = false;
bool active = false; // the beacon is running (dark, or forced)
uint8_t step = 0;
unsigned long stepEnd = 0;
bool resting = false;
unsigned long sent = 0;
bool lastReading = HIGH;
bool pressedState = HIGH; // the button's debounced state
unsigned long changedAt = 0;
void stopBeacon() {
active = false;
resting = false;
step = 0;
digitalWrite(BEACON, LOW);
}
void startBeacon(unsigned long now) {
active = true;
resting = false;
step = 0;
stepEnd = now + PATTERN[0] * UNIT_MS;
digitalWrite(BEACON, HIGH);
}
void setup() {
Serial.begin(115200);
pinMode(BEACON, OUTPUT);
pinMode(TEST, INPUT_PULLUP);
Serial.println("rescue beacon ready");
}
void loop() {
unsigned long now = millis();
// light and dark, with a gap
int light = analogRead(SENSOR);
if (!night && light < DARK) {
night = true;
Serial.println("dark: beacon on");
} else if (night && light > LIGHT) {
night = false;
Serial.println("daylight: beacon asleep to save the battery");
}
// the test button toggles an override. A reading counts once it has held
// for 40 ms, which rides out the chatter of the contacts on the way down
// and on the way back up.
bool reading = digitalRead(TEST);
if (reading != lastReading) {
lastReading = reading;
changedAt = now;
}
if (reading != pressedState && now - changedAt > 40) {
pressedState = reading;
if (pressedState == LOW) {
forced = !forced;
Serial.println(forced ? "test: beacon forced on" : "test: released");
}
}
bool want = night || forced;
if (want && !active) startBeacon(now);
if (!want && active) stopBeacon();
if (active && now >= stepEnd) {
if (resting) {
startBeacon(now);
} else {
step++;
if (step >= STEPS) {
sent++;
Serial.print("SOS #");
Serial.print(sent);
Serial.println(" sent");
resting = true;
digitalWrite(BEACON, LOW);
stepEnd = now + REST_MS;
} else {
digitalWrite(BEACON, (step % 2 == 0) ? HIGH : LOW);
stepEnd = now + PATTERN[step] * UNIT_MS;
}
}
}
}
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(BEACON, OUTPUT);
pinMode(TEST, INPUT_PULLUP);
Serial.println("rescue beacon ready");
}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();
// light and dark, with a gap
int light = analogRead(SENSOR);
if (!night && light < DARK) {
night = true;
Serial.println("dark: beacon on");
} else if (night && light > LIGHT) {
night = false;
Serial.println("daylight: beacon asleep to save the battery");
}
// the test button toggles an override. A reading counts once it has held
// for 40 ms, which rides out the chatter of the contacts on the way down
// and on the way back up.
bool reading = digitalRead(TEST);
if (reading != lastReading) {
lastReading = reading;
changedAt = now;
}
if (reading != pressedState && now - changedAt > 40) {
pressedState = reading;
if (pressedState == LOW) {
forced = !forced;
Serial.println(forced ? "test: beacon forced on" : "test: released");
}
}
// ... 25 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- 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. - The button seems always pressed, or never changes. Pushbutton
A 4-leg tactile switch has legs joined in pairs. Place it across the breadboard center gap so the pairs sit on different rows. Wired along one side, the legs are already connected. - The reading barely changes, or sits stuck near 0 or the top of the range. Light sensor
An LDR is a resistor, so use it in a divider with a fixed resistor, about 10k, and read the middle node. Without the divider the analog pin sees nothing useful. - Readings go the wrong way: higher in the dark. Light sensor
Which side the LDR sits on decides the direction. LDR on the supply side gives a higher reading in the light. LDR on the ground side gives a lower reading in the light. Swap them if needed.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.