Find the emergency broadcast on an Arduino Uno
Turn a knob across the FM band to find a hidden station: the static fades and a traffic light warms from red to green as you close in, and a display shows the frequency. 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 $30. On Mokxi they're free.
What you'll learn
- Sensors
- Lights
- Sound
- Displays
- Reading a knob with an analog pin
- Difficulty: Intermediate
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 7
How it works
Turn a knob across the FM band to find a hidden station: the static fades and a traffic light warms from red to green as you close in, and a display shows the frequency.
Arduino Uno R3 runs the sketch below. The circuit around it: Traffic light module, Four-digit display, TM1637, Piezo speaker, Potentiometer. pinMode sets RED_LED as an output. analogRead measures the voltage on A1 as a number. 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 $30; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Traffic light module
- 1 × Four-digit display, TM1637In Pinball Table Kit, Spaceship Panel Kit.
- 2 × Power, 5 V
- 1 × Piezo speakerIn Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × PotentiometerIn Halloween Prop Kit, Starter Projects 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 |
| Four-digit display, TM1637 pin DIO | Arduino Uno R3 pin 7 |
| Four-digit display, TM1637 pin CLK | Arduino Uno R3 pin 6 |
| Traffic light module pin G | Arduino Uno R3 pin 4 |
| Traffic light module pin Y | Arduino Uno R3 pin 3 |
| Traffic light module pin R | Arduino Uno R3 pin 2 |
| Potentiometer pin 2 | Arduino Uno R3 pin A0 |
| Traffic light module pin GND | Ground |
| Four-digit display, TM1637 pin VCC | 5 V |
| Four-digit display, TM1637 pin GND | Ground |
| Piezo speaker pin 2 | Ground |
| Potentiometer pin 1 | Ground |
| Potentiometer pin 3 | 5 V |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Find the emergency broadcast: a tuning knob, a TM1637, a traffic light and a
// piezo on an Uno.
//
// potentiometer wiper -> A0, ends to 5 V and GND
// TM1637 CLK -> pin 6, DIO -> pin 7
// traffic light red -> pin 2, yellow -> pin 3, green -> pin 4
// piezo pin 10 and GND
//
// Somewhere between 88.0 and 108.0 MHz one station is still on the air. Its
// frequency is picked at random each time the sketch starts. Turn the knob:
// the display shows where you are tuned and the piezo hisses with static. The
// closer you get, the less static, the traffic light goes from red (cold)
// through yellow (warm) to green, and on the station itself the static stops
// and the broadcast beeps through. The Serial Monitor prints the signal as it
// changes, and the message once you are locked on.
//
// # Hot and cold: searching with a number that peaks
//
// The sketch never tells you where the station is. It only gives you the
// signal strength, which is 100 on the station's frequency and falls away in a
// straight line to 0 at 1.5 MHz either side. Finding the peak of something you
// can only measure is called hill climbing, and it is how a real radio
// is tuned by ear, and how a robot finds a heat source. Turn toward more.
//
// # A knob that does not sit still
//
// An analog reading wobbles by a count or two even when nobody touches the
// knob, and a tuner that jitters between 95.3 and 95.4 is annoying. The
// reading goes through an exponential moving average (a quarter of the new
// value, three quarters of the old one) and the frequency is computed from
// that, so the display settles. The average also needs a little time to catch
// up when the knob moves; that is the price of smoothness.
//
// # Static is random notes, not a sound file
//
// A piezo plays one note at a time, so static is faked: every 40 ms there is a
// chance, bigger when the signal is weak, of a short note at a random pitch.
// The chance is (100 - signal) percent, so at 100 there is never any static.
#include <TM1637Display.h>
const uint8_t POT = A0;
const uint8_t RED_LED = 2;
const uint8_t YELLOW_LED = 3;
const uint8_t GREEN_LED = 4;
const uint8_t PIEZO = 10;
const int BAND_LOW = 880; // 88.0 MHz, in tenths of a MHz
const int BAND_HIGH = 1080; // 108.0 MHz
const int REACH = 15; // the signal fades out 1.5 MHz either side
const int LOCK_AT = 85; // percent
TM1637Display display(6, 7);
int target; // the hidden station, in tenths of a MHz
float knob = 0; // the smoothed reading
bool firstRead = true;
int shownFreq = -1;
int lastBucket = -1;
int lightColor = -1;
bool locked = false;
unsigned long lockedAt = 0;
bool messageSent = false;
unsigned long lastTick = 0;
unsigned long lastBeep = 0;
int signalFor(int freq) {
int away = abs(freq - target);
if (away >= REACH) return 0;
return 100 - away * 100 / REACH;
}
// Written only when the colour changes, so the lamps are not rewritten 25
// times a second for nothing.
void setLight(int percent) {
int color = percent < 30 ? 0 : (percent < LOCK_AT ? 1 : 2);
if (color == lightColor) return;
lightColor = color;
digitalWrite(RED_LED, color == 0);
digitalWrite(YELLOW_LED, color == 1);
digitalWrite(GREEN_LED, color == 2);
}
void printTuned(int freq) {
Serial.print(freq / 10);
Serial.print('.');
Serial.print(freq % 10);
}
void setup() {
Serial.begin(115200);
pinMode(RED_LED, OUTPUT);
pinMode(YELLOW_LED, OUTPUT);
pinMode(GREEN_LED, OUTPUT);
pinMode(PIEZO, OUTPUT);
display.setBrightness(0x0f);
// Nothing is wired to A1, so it reads noise: a different station each Run.
randomSeed(analogRead(A1));
target = random(BAND_LOW + 20, BAND_HIGH - 20);
Serial.println("radio on: find the emergency broadcast");
}
void loop() {
unsigned long now = millis();
if (now - lastTick < 40) return;
lastTick = now;
int raw = analogRead(POT);
if (firstRead) {
knob = raw;
firstRead = false;
} else {
knob += (raw - knob) / 4.0;
}
int freq = BAND_LOW + (int)(knob * (BAND_HIGH - BAND_LOW) / 1023.0 + 0.5);
int percent = signalFor(freq);
if (freq != shownFreq) {
shownFreq = freq;
display.showNumberDecEx(freq, 0x20, false);
}
setLight(percent);
// report each time the signal moves into another tenth of the dial
int bucket = percent / 10;
if (bucket != lastBucket) {
lastBucket = bucket;
Serial.print("tuned to ");
printTuned(freq);
Serial.print(" MHz, signal ");
Serial.print(percent);
Serial.println("%");
}
bool nowLocked = percent >= LOCK_AT;
if (nowLocked && !locked) {
locked = true;
lockedAt = now;
messageSent = false;
Serial.print("LOCKED on ");
printTuned(freq);
Serial.println(" MHz: emergency broadcast");
} else if (!nowLocked) {
locked = false;
}
if (locked) {
// the broadcast: a two-note signal, no static
if (now - lastBeep >= 400) {
lastBeep = now;
tone(PIEZO, ((now / 400) & 1) ? 1318 : 988, 160);
}
if (!messageSent && now - lockedAt >= 1500) {
messageSent = true;
Serial.println("message: SHELTER AT GRID 7, WATER AND MEDICINE, COME IN FROM THE EAST");
}
} else if ((int)random(100) < 100 - percent) {
tone(PIEZO, random(200, 3000), 30);
}
}
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(RED_LED, OUTPUT);
pinMode(YELLOW_LED, OUTPUT);
pinMode(GREEN_LED, OUTPUT);
pinMode(PIEZO, OUTPUT);
display.setBrightness(0x0f);
// Nothing is wired to A1, so it reads noise: a different station each Run.
randomSeed(analogRead(A1));
target = random(BAND_LOW + 20, BAND_HIGH - 20);
Serial.println("radio on: find the emergency broadcast");
}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 - lastTick < 40) return;
lastTick = now;
int raw = analogRead(POT);
if (firstRead) {
knob = raw;
firstRead = false;
} else {
knob += (raw - knob) / 4.0;
}
int freq = BAND_LOW + (int)(knob * (BAND_HIGH - BAND_LOW) / 1023.0 + 0.5);
int percent = signalFor(freq);
if (freq != shownFreq) {
shownFreq = freq;
display.showNumberDecEx(freq, 0x20, false);
}
setLight(percent);
// report each time the signal moves into another tenth of the dial
int bucket = percent / 10;
if (bucket != lastBucket) {
lastBucket = bucket;
Serial.print("tuned to ");
printTuned(freq);
Serial.print(" MHz, signal ");
// ... 30 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- 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. - 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. - The reading jumps around or only covers part of the range. Potentiometer
Wire the two outer legs to 5 V and ground and the middle leg (the wiper) to an analog pin. Using the wrong two legs gives a dead or partial range. - Turning the knob does nothing, or the value is stuck at 0 or 1023. Potentiometer
Check that the wiper goes to an analog input (A0 to A5 on an Uno), not a digital pin. Make sure both ends are connected, since a floating end gives noise.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.