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
In this guide you will build a rocket-launch alarm clock using an ESP32, a DS3231 real-time clock module, a WS2812B LED ring, a piezo buzzer, an OLED display, and a pair of buttons for setting the time and alarm. When the alarm triggers, instead of a boring beep you get a full 10-second launch countdown -- the OLED displays large descending numbers, the LED ring fills up segment by segment like a fuel gauge, and at zero the LEDs burst into a fiery orange-and-white liftoff animation while the buzzer blasts an ascending rocket roar. A snooze button gives you five more minutes of pre-launch hold, and a dismiss button cuts the engines immediately.
The DS3231 is the gold standard for battery-backed real-time clocks in maker projects. Its temperature-compensated crystal oscillator maintains accuracy to within a couple of minutes per year, and it keeps ticking through power outages thanks to a coin-cell backup. In this project you will learn how to communicate with the DS3231 over I2C, set and read time registers, configure its built-in alarm interrupt to wake the ESP32 from deep sleep, and synchronize the displayed time on the OLED. You will also implement a simple two-button time-setting interface with long-press detection for fast-scrolling through hours and minutes -- a useful UX pattern for any embedded device with limited inputs.
The finished alarm clock is a genuinely useful bedside device that makes waking up a little more entertaining. Because the DS3231's alarm interrupt drives a hardware pin, the ESP32 can spend the night in deep sleep drawing microamps until launch time, making the project practical for battery-powered operation. From here you could add Wi-Fi time synchronization via NTP to eliminate manual time setting, integrate a light sensor to auto-dim the display at night, or swap the buzzer for a small amplifier and speaker to play actual rocket launch audio. It is a beginner-friendly project that teaches RTC interfacing, low-power design, and sequenced multimedia output in one satisfying build.
Wiring diagram
Wiring diagram
Components needed
| Component | Type | Qty | Buy |
|---|---|---|---|
| DS3231 RTC | sensor | 1 | €3.35 |
| SSD1306 OLED | display | 1 | €4.30 |
| WS2812B LED Ring | actuator | 1 | |
| Piezo Buzzer | actuator | 1 | €4.75 |
| Snooze / Confirm Button | other | 1 | €8.30 |
| Set Button | other | 1 | €8.30 |
Prices and availability are indicative and may have been updated by the supplier. 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 →