A fully-featured alarm clock built on a Raspberry Pi Pico W, written in Rust using the Embassy async framework.
Left: Alarm enabled with lightsaber icon indicator. Center: The completed alarm clock showing the OLED display, NeoPixel ring with hour markers, and three illuminated control buttons (green, blue, yellow). Right: Alarm clock in operation with active NeoPixel LEDs displaying the analog clock.
- WiFi Time Sync - Automatic time synchronization via worldtimeapi.org (refreshed every 6 hours)
- Alarm System - Configurable alarm with sunrise light effect and audio playback
- Visual Display - 128×64 OLED display with custom Star Wars-inspired font
- Analog Clock - 16-LED NeoPixel ring showing hours (red), minutes (green), and seconds (blue)
- Battery Powered - 18650 Li-ion battery with USB charging and voltage monitoring
- Volume Control (0-30) - Adjustable alarm sound volume, persisted to flash
- LED Brightness (0-20) - Adjustable NeoPixel clock brightness with live preview
- Manual Time Setting - Set time via buttons when WiFi unavailable or for DST adjustments
- Multi-Mode Interface - Normal, Menu, Settings, Alarm, System Info, and Standby modes
- Smart Alarm - Sunrise light effect followed by audio, with randomized button sequence to dismiss
- Auto-Timeout Protection - All menus and settings automatically return to Normal mode after 10 seconds of inactivity (with auto-save)
- Power Management - Standby mode with task suspension for minimal power consumption
- Persistent Storage - All settings saved to flash memory and survive power cycles
This project uses probe-rs for debugging and flashing via a debug probe (e.g., Raspberry Pi Debug Probe, Picoprobe).
# Build and flash with probe-rs (recommended)
DEFMT_LOG=warn cargo run --releaseSee the Building the Project section for more details.
- Power on the device - it will initialize and sync time via WiFi
- Press Yellow to open the menu
- Press Green to access Settings
- Configure your preferences (volume, brightness, etc.)
- Press Blue in Normal mode to set your alarm time
- Press Green in Normal mode to toggle the alarm on/off
See the User Manual below for detailed instructions.
- Raspberry Pi Pico W (microcontroller with WiFi)
- SSD1306 OLED Display (128×64 pixels, I²C)
- DFPlayer Mini MP3 module (DFR0299)
- WS2812B NeoPixel Ring (16 RGB LEDs)
- 3W 8Ω Speaker
- 18650 Li-ion Battery (3350mAh)
- TC4056A Charger Module
- 5V Step-up Converter
- 3× LED Ring Push Buttons (12mm, Green/Blue/Yellow)
- Micro SD card (FAT32, for alarm audio)
Complete bill of materials available in the Components section below.
The project is written in Rust using the Embassy async framework:
src/state.rs- System state managementsrc/event.rs- Event channel and event typessrc/task/- Async tasks for each peripheral and system functionorchestrate.rs- Central orchestration and state transitionsdisplay.rs- OLED display managementlight_effects.rs- NeoPixel LED controlsound.rs- DFPlayer audio controlrtc_manager.rs- Real-time clock managementalarm_settings.rs- Flash persistence for settingstime_updater.rs- WiFi time synchronization- And more...
src/utility/- Helper functions (DateTime conversion, etc.)media/- Custom BMP graphics for displaycircuit/- KiCad schematic and PCB designenclosure/- FreeCAD 1.0 design files
# Generate and view code documentation
cargo doc --openThis project uses defmt for logging. Log levels are controlled by the DEFMT_LOG environment variable.
For development with full logging (requires debug probe):
cargo build
cargo runWarnings only (prevents RTT buffer overflow when running standalone):
DEFMT_LOG=warn cargo build --release
DEFMT_LOG=warn cargo run --releaseModerate logging (info and above):
DEFMT_LOG=info cargo build --release- Green Button (left)
- Blue Button (middle)
- Yellow Button (right)
This is the default mode showing the current time.
| Button | Action |
|---|---|
| Green | Toggle alarm ON/OFF |
| Blue | Enter alarm time setting |
| Yellow | Open menu |
Display shows:
- Current time (large Star Wars font digits)
- Date and day of week
- Lightsaber icon (if alarm is active)
- Battery level or USB charging indicator
NeoPixel ring:
- Red LED = hour hand
- Green LED = minute hand
- Blue LED = second hand
- Colors mix when hands overlap
| Button | Action |
|---|---|
| Green | Increase hours (+1, wraps 23→0) |
| Yellow | Increase minutes (+1, wraps 59→0) |
| Blue | Save and return to Normal mode |
Tips:
- Hold button for continuous increment
- Time wraps around for easy adjustment
- Alarm setting is saved to flash automatically
- Auto-timeout: Returns to Normal mode after 10 seconds of inactivity (changes are saved)
Press Yellow from Normal mode to access the menu.
| Button | Action |
|---|---|
| Green | Settings Menu |
| Blue | Standby Mode |
| Yellow | System Info |
Auto-timeout: Returns to Normal mode after 10 seconds of inactivity
Configure volume, brightness, and time.
| Button | Action |
|---|---|
| Green | Set Volume |
| Blue | Set LED Brightness |
| Yellow | Set Time Manually |
Auto-timeout: Returns to Normal mode after 10 seconds of inactivity
Adjust alarm sound volume (0-30).
| Button | Action |
|---|---|
| Green | Increase volume (+1, wraps 30→0) |
| Yellow | Decrease volume (-1, wraps 0→30) |
| Blue | Save to flash and return to Settings Menu |
Display shows: Volume: 13
Range: 0-30 (DFPlayer Mini volume range)
Auto-timeout: Auto-saves and returns to Normal mode after 10 seconds of inactivity
Adjust NeoPixel clock brightness (0-20).
| Button | Action |
|---|---|
| Green | Increase brightness (+1, wraps 20→0) |
| Yellow | Decrease brightness (-1, wraps 0→20) |
| Blue | Save to flash and return to Settings Menu |
Display shows: LED Clock: 1
Range: 0-20 (limited for battery safety)
Live Preview: Clock LEDs update immediately as you adjust!
Auto-timeout: Auto-saves and returns to Normal mode after 10 seconds of inactivity
Set time when WiFi is unavailable or for DST adjustments.
| Button | Action |
|---|---|
| Green | Increase hours (+1, wraps 23→0) |
| Yellow | Increase minutes (+1, wraps 59→0) |
| Blue | Set RTC and return to Settings Menu |
Display shows: Time in large digits (HH:MM)
Note: Seconds are reset to :00, date is preserved from current RTC
Auto-timeout: Auto-saves and returns to Normal mode after 10 seconds of inactivity
View power and alarm information (2 pages).
Page 1 - Power Info:
- Vsys voltage
- USB power status
- Firmware version
Page 2 - Alarm Info:
- Alarm time
- Next trigger date
- Enabled status
| Button | Action |
|---|---|
| Green | Next page (or exit if on last page) |
| Blue | Back to Normal mode |
| Yellow | Back to Normal mode |
Auto-timeout: Returns to Normal mode after 10 seconds of inactivity
When the alarm goes off:
- Sunrise Effect - NeoPixel ring gradually lights up (morning red → warm white)
- Audio Playback - Imperial March plays once
- Waker Effect - Rainbow whirl on NeoPixel ring
- Dismissal Challenge - Must press randomized button sequence
Display shows: Press Yellow! (or Green/Blue - changes each time)
| Button | Action |
|---|---|
| Any | Press the requested color in the correct sequence |
Must press all 3 colors in the correct order to stop the alarm!
Power-saving mode with display and LEDs off.
To enter: Menu → Blue button
Display shows: "Going to sleep..." (briefly)
What's suspended:
- Display updates
- NeoPixel ring
- Scheduler task
- Time updater task
- Voltage measurement task
| Button | Action |
|---|---|
| Any | Wake up device |
On wake: All tasks resume and time sync is performed.
Navigation:
- Yellow = "Back" or "Next option"
- Blue = "Confirm/Save"
- Green = "Primary action" or "Increase"
Adjusting Values:
- Green = Increment/Increase
- Yellow = Decrement/Decrease
- Blue = Save/Confirm
Wrapping Behavior: All numeric values wrap around for easy adjustment:
- Hours: 23 → 0
- Minutes: 59 → 0
- Volume: 30 → 0
- Brightness: 20 → 0
- Settings Persist - Volume and brightness are saved to flash and survive power cycles
- Live Preview - LED brightness changes are visible immediately while adjusting
- No Confirmation Prompts - Pressing Blue always saves/confirms
- Auto-Timeout Protection - All menus and settings automatically return to Normal mode after 10 seconds of inactivity (settings are auto-saved)
- WiFi Not Required - Manual time setting works even if WiFi is down
- Alarm Preserved - Setting time manually doesn't affect your alarm settings
- Hold for Speed - Hold Green or Yellow buttons for continuous increment
Circuit schematic available in KiCad format: circuit/pi-pico-alarmclock/
Design includes:
- Power management with battery charger
- Voltage level sensing
- MOSFET switching for display and audio control
- All peripheral connections
Designed in FreeCAD 1.0. Export files available: enclosure/
External view of the enclosure showing the front panel with OLED display cutout, NeoPixel ring hour markers, speaker grille, rotary encoder, and push button positions.
Interior assembly view with lid removed, showing PCB placement, speaker mounting, and 18650 battery holder positioning.
Early breadboard prototype used for development and testing of the circuit design before moving to the final PCB implementation.
The completed alarm clock showing the internal assembly with custom PCB, Raspberry Pi Pico W mounted on the controller board, DFPlayer Mini MP3 module, NeoPixel ring, speaker, 18650 battery, and wiring for the three LED ring push buttons.
| Component | Qty | Description |
|---|---|---|
| Raspberry Pi Pico W | 1 | Microcontroller with WiFi |
| OLED Display | 1 | SSD1306 compatible I²C OLED Display 128×64 pixels with two color yellow/blue. Input Voltage 3.3V |
| DFPlayer Mini | 1 | MP3 module (DFR0299) |
| TC4056A Charger Module | 1 | Li-ion battery charging module with protection |
| Step-up Converter | 1 | 5V boost converter (e.g., U3V16F5 or similar), 2.5-5.5V input, 5V/1A output |
| WS2812B NeoPixel Ring | 1 | 16 RGB LED ring (this is the limit the power supply can handle) |
| Speaker | 1 | 3W 8Ω speaker, 70×30×15mm (DFPlayer Mini compatible) |
| 18650 Li-ion Battery | 1 | 3350mAh or similar capacity |
| Battery Holder | 1 | For 18650 battery |
| Micro SD Card | 1 | Any capacity, formatted to FAT32 |
| Component | Qty | Description |
|---|---|---|
| LED Ring Push Buttons | 3 | 12mm LED ring illuminated push buttons with JST 4-pin connectors (one each: yellow, green, blue). LED rings are controlled via MOSFET Q4 (IRLZ44N) on GPIO 26 with 10-second auto-off timeout on button press (except during alarm mode) |
| Component | Qty | Type | Description |
|---|---|---|---|
| Q1, Q2, Q4 | 3 | IRLZ44N | N-channel MOSFET, logic-level (TO-220) |
| Q3 | 1 | IRF9540 | P-channel MOSFET, logic-level (TO-220) |
| D3 | 1 | 1N5819 | Schottky diode, 40V, 1A (DO-41) |
| Reference | Qty | Value |
|---|---|---|
| R1, R5, R14 | 3 | 10kΩ |
| R2, R13 | 2 | 100Ω |
| R3, R7 | 2 | 1kΩ |
| R6 | 1 | 2.2kΩ |
| R8 | 1 | 220Ω |
| R9, R11 | 2 | 100kΩ |
| R10, R12 | 2 | 220kΩ |
| Reference | Qty | Type | Value | Voltage | Description |
|---|---|---|---|---|---|
| C1, C3, C4, C5, C6 | 5 | Ceramic | 100nF | 50V | General decoupling |
| C2, C7 | 2 | Electrolytic | 470µF | 16V | Power supply filtering |
| C8, C9 | 2 | Ceramic | 100nF | 50V | ADC input filters for VSYS/VBUS voltage dividers |
| Reference | Qty | Type | Description |
|---|---|---|---|
| J1 | 1 | Screw Terminal 2P | 5mm pitch, for speaker connection |
| J2 | 1 | JST PH 4-pin | Button Green (2mm pitch) |
| J3 | 1 | JST PH 4-pin | Button Blue (2mm pitch) |
| J4 | 1 | JST PH 4-pin | Button Yellow (2mm pitch) |
| J5 | 1 | JST PH 3-pin | NeoPixel connection (2mm pitch) |
| J6 | 1 | JST PH 4-pin | OLED display (2mm pitch) |
| J7 | 1 | Screw Terminal 2P | 5mm pitch, for battery connection |
The USB power and battery voltage detection uses voltage dividers (220kΩ/100kΩ) with 100nF filter capacitors that help reduce electrical noise from NeoPixel switching and EMI in the enclosure. Software filtering further improves stability:
USB Detection (VBUS):
- Debouncing with 5 consecutive readings
- 10ms delay between samples
- Rejects transient noise spikes
Voltage Measurement (VSYS):
- Median filter for ADC samples (rejects impulse noise spikes)
- 5ms settling delay between samples
- 0.1V hysteresis to prevent display flicker
Settings are stored using sequential-storage with wear leveling:
| Key | Setting | Type | Range | Default |
|---|---|---|---|---|
| 0 | Alarm Hour | u8 | 0-23 | 0 |
| 1 | Alarm Minute | u8 | 0-59 | 0 |
| 2 | Alarm Enabled | u8 | 0-1 | 0 (false) |
| 3 | Volume | u8 | 0-30 | 13 |
| 4 | Clock Brightness | u8 | 0-20 | 1 |
Flash Range: 0x1F_9000..0x1FC_000 (12K allocated)
Memory Configuration:
- Total Flash: 2048K (2 MB)
- Code: 2020K
- Settings: 12K
- Reserved: 16K
This project worked well as a learning experience, but there are several hardware design decisions that should be improved in a future version:
1. No Hardware Power Switch
- The device lacks a physical power switch, relying entirely on software standby mode
- This means the device continuously draws power even when "off"
- Solution for next version: Add a physical power switch between the battery and the circuit OR even better add circuitry to power down and back up by push button, AFAIK that is possible but was out of my league for this iteration.
This is a hobby project and I have very little experience in electronics and had none before in Rust and also none before in CAD. All three things I taught myself along the way. While this was incredible fun, this project will be full of imperfections, literally everywhere. In case you happen across this repo and spots a thing to improve - if you find the time to let me know, I will be more than happy. After all, this was and is about learning things.
That being said: This device does work, at least as far as I did test it to this point.
Does the world need another alarm clock? Hell no, it does not. You can buy them in thousands of types for very little money and then most will have more functionality, better battery life, and whatnot. I was looking for a thing to do, had a joking conversation with my eldest daughter (who is in an age range where getting up in the morning appears to be terribly difficult) and that was that: I found myself building this thing.
It took me two years, much of which time this project sat in some state of being unfinished. Mostly it sat idle when I had hit a wall in Rust-skill, electronics skill or 3d-printing skill. I only very recently found a huge ground bounce that messed up the DFPlayer audio output.
When I started this, Coding AI was in relative infancy. I am glad it was, because that made me learn a lot the hard and memorable way. By today all that I do uses AI to some extent, it is amazing what kind of reach this bestows. So, in this iteration of the project I did use AI for almost all refactoring and - shame on me - also for some features. Would I dare to if this was a commercial project? No. But for a hobby thing? Why not?
- Embassy Framework - Rust async framework for embedded devices with excellent examples and HALs
- Embassy Community - Patient and helpful individuals who answered my Rust-rookie and Embassy-rookie questions. From the orchestrator pattern through a number of headscratchers with Cells, Mutexes and Sync Primitives.... I learned it all from them.
MIT License - See LICENSE file for details






