How to Build a Rocket Launch Alarm Clock with ESP32
Morning alarm with countdown animation, launch lights, and liftoff sound
Updated

What you'll build
Build an ESP32 alarm clock that turns waking up into a rocket launch sequence. The OLED shows the current time and your set alarm time throughout the day. When the alarm fires, a 10-second countdown fills the screen with large descending digits while the WS2812B LED ring fills segment by segment like a fuelling indicator. At zero the LEDs burst into a liftoff pattern and the piezo buzzer plays an ascending tone sequence until you press snooze or dismiss.
The DS3231 real-time clock module keeps accurate time through power cuts because it has a temperature-compensated crystal and runs from a CR2032 coin cell when the main supply is off. On first power-up the sketch detects that the RTC has lost power and sets its registers from the compile timestamp automatically. You will learn how to read and write I2C clock registers, drive individually addressable LEDs with FastLED, and implement a two-button interface for setting the alarm.
A snooze press holds the alarm for five extra minutes. A second snooze press or letting the sequence run to completion dismisses until the next day. The whole build fits on a small breadboard and takes under an hour to wire.
What you are building
This guide covers a self-contained alarm clock with a launch countdown. The device has five jobs:
- Display the current time on the OLED and keep it accurate via the DS3231.
- Accept a user-set alarm time using the Set and Snooze / Confirm buttons.
- Trigger a 10-second LED and buzzer countdown when the alarm time is reached.
- Respond to snooze (five-minute hold) and dismiss (cancel sequence) during the alarm.
- Survive a power cut with the time intact because of the DS3231 coin-cell backup.
Out of scope: Wi-Fi NTP synchronisation, multiple alarms, deep-sleep power saving, and display dimming are all reasonable follow-ons but are not covered here.
Upload and calibrate
Install these four libraries via the Arduino Library Manager before compiling:
- RTClib (Adafruit)
- FastLED
- Adafruit SSD1306
- Adafruit GFX Library
Flash the sketch to the ESP32. On first power-up, lostPower() on the DS3231 returns true and the sketch sets the RTC registers from the compile timestamp. Recompile and reflash if the time shown is significantly off, because the compile timestamp is baked in at build time.
Open Serial Monitor at 115200 baud. You should see Rocket Alarm ready followed by a line showing the current time and the stored alarm values of alarmHour and alarmMinute.
To set the alarm, press Set (GPIO 14) to enter alarm-set mode. The OLED highlights the alarm time. Each subsequent press of Set advances the alarm by 5 minutes. Press Snooze / Confirm (GPIO 27) to accept the displayed time and return to normal clock mode. The chosen hour and minute are stored in alarmHour and alarmMinute in memory; they reset to defaults on power loss unless you add EEPROM persistence.
The sketch defines NUM_LEDS as 16 to match a 16-LED ring. If your ring has a different count, update NUM_LEDS and the countdown fill logic accordingly. LED_PIN is 4, BUZZER_PIN is 26, BTN_SET is 14, and BTN_SNOOZE is 27.
Troubleshooting
- OLED stays blank after power-up. Confirm SDA and SCL are on GPIO 21 and GPIO 22. Run an I2C scanner sketch; the OLED should appear at address 0x3C and the DS3231 at 0x68. A missing 3V3 connection or swapped SDA/SCL are the most common causes.
- Serial Monitor shows no output or garbled text. Ensure the baud rate is set to 115200. If the ESP32 loops in boot, check that GPIO 14 and GPIO 27 are not pulled high by external hardware on boot — both pins can affect the bootloader.
- Time resets to the compile timestamp after every power cycle. The DS3231 coin cell is missing, flat, or not making contact. Replace the CR2032 and re-flash once to set the correct time.
- LED ring flickers or the ESP32 resets when the alarm fires. The 470 µF capacitor across the ring's 5 V and GND is missing or has reversed polarity. Also verify the ring is drawing from the USB 5 V rail, not the 3V3 output.
- Buzzer makes no sound. Check the SIG wire is on GPIO 26 (BUZZER_PIN). Passive buzzers require an AC signal from
tone(); if you have an active buzzer, replace thetone()calls with a simpledigitalWritepattern. - Alarm does not fire at the right time. Print
alarmHourandalarmMinuteover Serial to confirm the stored values match what you set. The comparison runs in the main loop against the DS3231's current hour and minute, so a mismatch inalarmHouroralarmMinuteis the usual cause.
Going further
Once the basics are working, a natural next step is persisting the alarm time across power cycles by writing alarmHour and alarmMinute to EEPROM. From there, adding Wi-Fi and NTP time synchronisation removes the need to set the RTC manually after long power outages — the DS3231 takes over from the NTP sync and keeps the time accurate between network connections.
For the launch sequence itself, the LED animation and buzzer melody are straightforward to customise. Swapping the piezo for a small class D amplifier and a speaker lets you store and play back a short WAV file from SPIFFS for a more realistic liftoff sound. You could also tie a light-dependent resistor to an ADC pin to auto-dim the OLED at night, keeping the display readable without disturbing sleep.
Wiring diagram
Components needed
Supplier links, prices, and availability are shown as a guide and may change. Schematik may earn a commission from purchases made through affiliate links.
Assembly
Wire the RTC and OLED on I2C
Connect DS3231 and SSD1306 OLED: VCC to 3.3V, GND to ground, SDA to GPIO21, SCL to GPIO22. Both share the I2C bus.
- Install a CR2032 coin cell in the DS3231 for battery backup.
Connect the LED ring
Wire WS2812B 5V to USB 5V, GND to ESP32 GND, and DIN to GPIO4.
- Add a 330 Ω resistor on DIN and a 470 µF capacitor across 5V and GND.
Add buzzer and buttons
Wire piezo buzzer signal to GPIO26 (other lead to GND). Connect snooze button between GPIO27 and GND, set button between GPIO14 and GND.
- Place the snooze button within easy reach for half-asleep slapping.
Upload and set alarm
Flash the sketch. Press SET to enter alarm mode, press SET again to advance by 5 minutes, then press SNOOZE to confirm. The RTC auto-sets from your compile time on first power-up.
- The OLED also shows the DS3231 temperature sensor reading.
Pin assignments
| Pin | Connection | Type |
|---|---|---|
| 3V3 | rtc-1 VCC | POWER |
| GND | rtc-1 GND | GROUND |
| GPIO 21 | rtc-1 SDA | I2C |
| GPIO 22 | rtc-1 SCL | I2C |
| 3V3 | launch-oled-1 VCC | POWER |
| GND | launch-oled-1 GND | GROUND |
| GPIO 21 | launch-oled-1 SDA | I2C |
| GPIO 22 | launch-oled-1 SCL | I2C |
| 5V | launch-led-ring-1 5V | POWER |
| GND | launch-led-ring-1 GND | GROUND |
| GPIO 4 | launch-led-ring-1 DIN | DATA |
| GND | launch-buzzer-1 GND | GROUND |
| GPIO 26 | launch-buzzer-1 SIG | PWM |
| GND | snooze-button-1 GND | GROUND |
| GPIO 27 | snooze-button-1 SIG | DIGITAL |
| GND | set-button-1 GND | GROUND |
| GPIO 14 | set-button-1 SIG | DIGITAL |
Code
#include <Wire.h>
#include <RTClib.h>
#include <FastLED.h>
#include <Adafruit_SSD1306.h>
#define SDA_PIN 21
#define SCL_PIN 22
#define LED_PIN 4
#define NUM_LEDS 16
#define BUZZER_PIN 26
#define BTN_SNOOZE 27
#define BTN_SET 14
Adafruit_SSD1306 display(128, 64, &Wire, -1);
RTC_DS3231 rtc;
CRGB leds[NUM_LEDS];
int alarmHour = 7;
int alarmMinute = 30;
bool alarmFiredToday = false;
bool settingMode = false;
unsigned long lastBtnMs = 0;
void runLaunchSequence() {
for (int t = 10; t >= 0; t--) {
int ledsLit = map(10 - t, 0, 10, 0, NUM_LEDS);
fill_solid(leds, NUM_LEDS, CRGB::Black);
for (int i = 0; i < ledsLit; i++) {
leds[i] = CHSV((10 - t) * 20, 255, 180);
}
FastLED.show();
display.clearDisplay();
display.setTextSize(3);
display.setCursor(50, 20);
display.printf("%d", t);
display.display();
display.setTextSize(1);
tone(BUZZER_PIN, 700 + (10 - t) * 90, 140);
delay(250);
}
for (int i = 0; i < 5; i++) {
fill_solid(leds, NUM_LEDS, CRGB(255, 100 + random(80), 0));
FastLED.show();
tone(BUZZER_PIN, 1600 + i * 200, 80);
delay(100);
fill_solid(leds, NUM_LEDS, CRGB::White);
FastLED.show();
delay(60);
}
fill_solid(leds, NUM_LEDS, CRGB::Black);
FastLED.show();
noTone(BUZZER_PIN);
}
void setup() {
Serial.begin(115200);
delay(100);
Wire.begin(SDA_PIN, SCL_PIN);
if (!rtc.begin()) Serial.println("RTC not found");
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, NUM_LEDS);
FastLED.setBrightness(120);
pinMode(BTN_SNOOZE, INPUT_PULLUP);
pinMode(BTN_SET, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
Serial.println("Rocket Alarm ready");
}
void loop() {
DateTime now = rtc.now();
unsigned long ms = millis();
if (!digitalRead(BTN_SET) && ms - lastBtnMs > 200) {
if (!settingMode) {
settingMode = true;
} else {
alarmMinute += 5;
if (alarmMinute >= 60) { alarmMinute = 0; alarmHour = (alarmHour + 1) % 24; }
}
tone(BUZZER_PIN, 1000, 30);
lastBtnMs = ms;
}
if (!digitalRead(BTN_SNOOZE) && ms - lastBtnMs > 200) {
if (settingMode) {
settingMode = false;
tone(BUZZER_PIN, 1500, 50);
} else if (alarmFiredToday) {
alarmFiredToday = false;
tone(BUZZER_PIN, 550, 80);
}
lastBtnMs = ms;
}
if (now.hour() == alarmHour && now.minute() == alarmMinute &&
!alarmFiredToday && !settingMode) {
runLaunchSequence();
alarmFiredToday = true;
}
if (now.hour() == 0 && now.minute() == 0) alarmFiredToday = false;
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(2);
display.printf(" %02d:%02d:%02d", now.hour(), now.minute(), now.second());
display.setTextSize(1);
display.setCursor(0, 24);
display.printf("Alarm: %02d:%02d %s", alarmHour, alarmMinute,
settingMode ? "[SET]" : alarmFiredToday ? "[DONE]" : "[ARM]");
display.setCursor(0, 40);
display.println("SET=adj SNOOZE=save");
display.setCursor(0, 52);
display.printf("Temp: %.1f C", rtc.getTemperature());
display.display();
uint8_t glow = beatsin8(20, 10, 50);
fill_solid(leds, NUM_LEDS, CRGB(0, 0, glow));
FastLED.show();
delay(100);
}
// Run this and build other cool things at schematik.ioReady to build this?
Open this project in Schematik to get the full wiring diagram, pin assignments, and deployable code for the Rocket Launch Alarm Clock.
Open in Schematik →