2FAC – Two Factor Alarm Clock
Description
A wireless and sturdy alarm clock that can be snoozed by hitting (or throwing?) it but not turned off unless it is placed on its (bolted down?) docking station elsewhere in the house. The docking station also allows setting the clock and alarm time using a selector button/slide for the clock or alarm, and two rotating knobs to set the hours and minutes. The alarm clock itself only shows the current time (and the alarm time when shaken?).
Origin
A normal alarm clock doesn't work for all teenagers who need to get up early, and dads (like me) who want to be able to sleep in late.
Progress
Week 2 — CAD
In the week I played with 7-segment displays and a shape for the clock and base station. I did not deviate much from my initial sketch because pressure to learn FreeCAD and Blender to not leave any room for creativity.
Week 4 — Embedded Programming
Week 6 — Electronics Design
In this week I learned about Charlieplexing. If I go for 7-segment digits on the clock, I'll need it to drive so many LEDs. I still do not know if I want to go for OLED or super fancy e-ink instead.
Week 7 — Computer Controller Machining
Wood is very pretty and a good candidate for the base station or clock, or both.
Week 8 — Electronics Production
In this week I made a working 7-segment LED digit. I am still not sure if I am going this route but it is very cool!
Week 9 — Input Devices
I experimented with all the inputs for my project in this week.
- DCF77
- to get the current time
- Rotary Encoder
- to change the wake up time
- Step Response
- to detect a hand on the clock for snoozing
Week 10 — Output Devices
I experimented with making NeoPixel components for clock digits, and with a Piezo for sound. The latter was disappointing, I have decided not to use them.
Week 11 — Networking
More playing with NeoPixels, not applicable to my final projects but I learned a lot about them.
Week 16 — System Integration
I finally started to design the entire project in this week.
Other Ideas
Light sensor the dim time when dark.
Build
Clock
Electronics
Time keeping
To keep clock time accurate the DCF77 transmission will be used. Time will be sync on power on, and daily readings should be enough to keep the clock in sync for an alarm clock.
Display
The time display will be provided by pockets on the inside of the clock body to make 7-segment digits lighted by LEDs. The light will shine through the enclosure. Inspiration: Fab Foos score keeping.
The digits will be NeoPixel modules based on the design for week 10.
The improved design is 35x65mm board with a 25x45mm digit. It also has one 1uF capacitor between the VDD (5V) and VSS (ground) as instructed on the datasheet (100nF per LED). The "dots" board will not have any capacitors because (I hope) the capacitors on the digit boards will be more than enough.
- TODO dots segment?
- TODO Segment mask / diffusion to avoid light leakage?
seg_width = 18; seg_height = 7.5; seg_depth = 4; led_width = 5.5; led_height = 5.5; led_depth = 2.5; led_c_width = 7; led_c_height = 7; led_c_depth = 1; module seg() { linear_extrude(seg_depth) polygon(points = [[0, 0], [seg_height / 2, seg_height / 2], [seg_width - (seg_height / 2), seg_height / 2], [seg_width, 0], [seg_width - (seg_height / 2), seg_height / -2], [seg_height / 2, seg_height / -2]]); } // segment (print 28 times) difference() { seg(); translate([(seg_width - led_width) / 2, led_height / -2, 0]) cube([led_width, led_height, led_depth]); translate([(seg_width - led_c_width) / 2, led_c_height / -2, 0]) cube([led_c_width, led_c_height, led_c_depth]); } // dot (print 2 times) translate([0, 20, 0]) difference() { cube([seg_height, seg_height, seg_depth]); translate([(seg_height - led_width) / 2, (seg_height - led_height) / 2, 0]) cube([led_width, led_height, led_depth]); translate([(seg_height - led_c_width) / 2, (seg_height - led_c_height) / 2, 0]) cube([led_c_width, led_c_height, led_c_depth]); }
Wake up Sound
Alternative: https://www.amazon.nl/-/en/MAX98357A-amplifier-filterless-breakout-application/dp/B0F21T7Q3P see also https://github.com/adafruit/Adafruit_CAD_Parts/tree/main/3006%20MAX98357
For sound, I decided to go for the DFPlayer Mini. It can play MP3 files, has a build in amplifier, and supports TF cards (aka mini SD) for storage. For output, I got a 57mm, 8Ω, 0.5W speaker.
https://www.digikey.nl/en/products/detail/dfrobot/DFR0299/6588463 https://wiki.dfrobot.com/dfr0299#tech_specs
I added a 1K resistor for stability when I read the below Q/A in the FAQ.
Q: Why is serial communication unstable or noisy, and how can Arduino control issues be resolved?
A: Add a 1kΩ resistor between Arduino TX and DFPlayer RX for signal conditioning.
DFPlayer Mini operates at ~3.3V while many MCUs use 5V; the resistor helps with level compatibility and reduces noise.
I found a MicroPython library for this component but, unfortunately, it did not work out of the box. The problems were actually easy to fix, I also added a small enhancement (ability to pass the UART id), and made a merge request to the author.
To make it available in KiCad, I downloaded the symbol from SnapMagic (need to make an account). The footprint was unusable for my PCB because it has stacking pins, so I'll need headers on my board.
TODO Snooze
Step response.
TODO Setting wake up time
TODO Power
The XIAO RP2350 has battery management on board (XIAO RP2040 does not) can be connected to a Lithium battery and charge it. When USB is disconnected it will automatically switch to battery power. The battery level can be read from GPIO29 (see also pin map). The battery does need to have power protection (PCM) to avoid over(dis)charging.
The V5/VUSB/VBUS pin can be used to power the XIAO without using the USB port. To protect the VBUS it is smart to add a diode (see also XIAO with Lipo Battery Charging Circuit).
The 5V power will from the docking station.
- Connect/disconnect stability
I used the code below to test power breaks. This code was installed as
main.py, so it starts up after a reset.import machine, time, ws2812 machine.Pin(23, machine.Pin.OUT).value(1) led = ws2812.WS2812(22, 1) led.pixels_fill((128, 0, 0)) led.pixels_show() time.sleep_ms(2000) led.pixels_fill((0, 0, 128)) led.pixels_show()
For testing I connected 5V through a PD Board (B0DPHHK5ZV) with a Lithium battery (Makerfocus 3.7V 3000mAh Lithium) attached. Running the code above it was easy to detect whether the RF2350 resets because the LED will turn red for 2 seconds and then back to blue.
Figure 2: XIAO RP2350 on 5V and battery Findings:
- (dis)connecting USB power causes no breaks
- (dis)connecting 5V power causes no breaks
- disconnecting battery on USB/5V power only breaks power on first disconnect
- Detecting on USB power
The GPIO29 pin can be used to gauge the power of the battery (don't forget to set GPIO19 to high). Below is the code I used to see if it is possible to detect if the board is powered by battery or from an external source.
import machine, time # turn on battery power reading machine.Pin(19, machine.Pin.OUT).value(1) # pin to read battery power values adc = machine.ADC(29) n = 0 while True: v = adc.read_u16() print(n, v) time.sleep_ms(1000) n += 1Unfortunately, it seems impossible to reliably detect, with the code above, to detect whether the external power source is attached. I was expecting to see a big power drop when the external source is detached but the value just keeps fluctuating as it did before.
Interestingly, the code example in the Seeed OSHW repository does not return realistic voltage values.
from machine import Pin, ADC import time # Function to initialize the GPIO pin for enabling battery voltage reading def init_gpio(): enable_pin = Pin(19, Pin.OUT) enable_pin.value(1) # Set the pin to high to enable battery voltage reading def main(): print("ADC Battery Example - GPIO29 (A3)") init_gpio() # Initialize the enable pin adc = ADC(Pin(29)) # Initialize the ADC on GPIO29 conversion_factor = 3.3 / (1 << 12) # Conversion factor for 12-bit ADC and 3.3V reference while True: result = adc.read_u16() # Read the ADC value voltage = result * conversion_factor * 2 # Calculate the voltage, considering the voltage divider (factor of 2) print("Raw value: 0x{:03x}, voltage: {:.2f} V".format(result, voltage)) time.sleep(0.5) # Delay for 500 milliseconds if __name__ == '__main__': main()The above outputs:
ADC Battery Example - GPIO29 (A3) Raw value: 0x8b98, voltage: 57.58 V Raw value: 0x8ab8, voltage: 57.22 V Raw value: 0x8aa8, voltage: 57.20 V ...
Weird? Bugs?
- TODO Annoying blinking PWR LED
When the XIAO is attached to a live battery, the PWR LED blinks. I did not yet find a way to turn it off. 😢
Docking station
TODO Connector
Pogo pins for 5V power en the rotary encoder to set the time.
https://www.digikey.nl/nl/products/filter/contacten/contacten-veerbelast-pogo-pins-en-druk/311
Parts:
TODO Power
I am considering using a PD Board (B0DPHHK5ZV) to use an USB-C cable to provide 5V to the clock.
Planning
DONE Collect relevant learnings from documentation
DONE Finalize list electronic components
BUSY Electronic components tests individually
TODO Test battery charging
DONE Main PCB designed
DONE Digit PCB designed
TODO Dots PCB designed
TODO Dock PCB designed
TODO Main board milled, soldered, and tested
TODO Digit and dots boards milled, soldered, and tested
TODO Application programming
TODO Enclosure and dock designed
TODO Enclosure and dock fabricated
Log
2026-05-21 Milled PCBs for the clock/dock connectors
2026-05-23 Clock connector added to the main interior
I printed a stand which will be slotted into the housing which holds the clock connector to connect to the dock.
2026-05-25 Broke connector of the main board
While connecting the bottom part with the clock connector to the main board, I ripped off one of the connectors on the main board. I immediately remilled the board and resoldered it.
While soldering I printed some diffusers for the display to try out.
2026-05-26 Audio not working on 5V
Lasts night I found out the DFP mini does not work when on battery power. The 5V pin does not supply 5V when the battery is attached. I destroyed the RP2350 while testing the voltage with a multimeter.
I also spend some time with V-Carve and OpenScad to create tool paths for milling the housing. I am planning on doing that next Friday.
2026-05-27 Switching to MAX98357A
I got a MAX98357A in the DFRobot MAX98357 I2S Amplifier Module - 2.5W in the mail today and immediately started testing it.
The pin names on the board do not correspond to the I2S names in the MicroPython library. Here's the translation:
| Pin | MicroPython |
|---|---|
| BCLK | sck |
| LRCLK | ws |
| DIN | sd |
Also interesting: not all pin combinations are allowed. I got a
ValueError: invalid ws (must be sck+1) error on the first try.
Eventually I was able to produce a tone and play a WAV file I uploaded
to the RP2350 using mpremote. After confirming it works, I spend (a
lot of) time making a footprint and adjusting the symbol in KiCad to
change the main PCB.
During class I decided to drop using the Shopbot for the housing because the multiplex I have is too uneven to get reliable pockets for the acrylic windows and PCB frame. I discussed it with Henk and will go fully laser cut acrylic.
2026-05-28 Milling another main board
I milled a new main board with the MAX98357 on it today but alas, the footprint I made yesterday is 2.54mm too wide. Next time, I'll print the PCB on paper first when including self made footprints to see if it actually fit. So, I corrected the footprint and milled yet another board.
2026-05-28 Laser cutting the housing
Today was LightBurn and burn smell day! It was more fun than I imagined. I did a cardboard test first, then the faces in wood (very pretty!), then the windows in clear acrylic and finally the rest of the shell in white because the cardboard is not study enough.
The first cardboard test failed. Somehow deepnest messed up the scale of the SVGs, so I dropped deepnest and nested myself in LightBurn.
Settings used:
| Power % min/max | Speed | |
|---|---|---|
| Cardboard | 30/20 | 100 |
| Wood | 100/90 | 25 |
| Acrylic | 75/65 | 10 |
I mismeasured the thickness of the acrylic so the in between slice are too fat. I can probably fix the interior to make it fit (new frame and base), so I'll look into that this weekend.
2026-05-30 Acrylic housing and dock drafting
Today I cleaned up the acrylic slices for the body (remove protective foil and clean them with soap to get rid of the smell left by laser cutting). Although the slices are too thick, the slices are already usable without reprinting the internals. I was afraid the white of these rings would not be as pretty but it looks very nice.
The rest of the day I spend on designing a dock. Extra point for being able to print it on my Pursa Mini (180x180x180mm).
2026-05-31 Printing the dock and changing the connect pin order
Printed the model I designed yesterday and updated the connector boards to have a more logical pin order, and make it harder to flip power and ground when docking it the wrong way.
2026-06-01 Creating new connectors, shopping for parts and DCF77 trouble
Got some thread 5mm rod to bolt the housing slices (unfortunately the was no bolt in the store long enough). I also worked on the 3D models of the clock and dock base because I need USB access without taking it apart all the time. I milled the dock connectors to get a better pin layout and created new wires to match.
During the long 3D print, I looked at why the DCF77 does not work on RP2350 (it does on the RP2040!). I found some hints on an old blog post (hurrah for the wayback machine!). But, after adding a (real) pull up resistor and a capacitor as in the example, still nothing. I tried switching to 5V, nothing. More pull up by enabling the onboard pull too, nothing. I'm giving up for now, printing a new clock base with USB access should allow me to set the time (many with Web Serial page to do so?).
Annoying, low progress day..
2026-06-02 Finishing the hardware and starting video editing
It's been a long and busy day. I printed (and reprinted) the dock base for the new pin layout and connector position. In the second print I made the footprint slightly narrower to avoid pins brunching each other when connecting (I probably need diodes on the V and GND pins).
I sawed the thread rod to match the clock thickness and filmed putting the clock together in a couple of takes. Kdenlive is a bit harder to use than I imagined but I can probably make a sort video.
For music I found Ambient Cinematic by AtlasAudio on pixabay.
TODO Bill of Materials
| Amount | Description | Price | Sum | Function |
|---|---|---|---|---|
| 1 | SEEED STUDIO XIAO | 5.75 | 5.75 | Main / RP2350 |
| 1 | DCF77 | 2.69 | 2.69 | Main / Radio Clock Receiver |
| 1 | DFR0954 | 7.02 | 7.02 | Main / Audio source |
| 1 | Speaker | 1.50 | 1.50 | Main / Audio output |
| 1 | RES 0 OHM JUMPER 1/4W 1206 | 0.10 | 0.10 | Main / Electrical |
| 1 | RES 1K OHM 1% 1/4W 1206 | 0.10 | 0.10 | Main / Electrical |
| 1 | Makerfocus 3.7V 3000mAh Lithium | 15.00 | 15.00 | Main / Battery |
| 30 | ADDRESS LED SERIAL RGB | 0.25 | 7.50 | Display / Light |
| 4 | CAP CER 1UF 50V X7R 1206 | 0.25 | 1.00 | Display / Electrical |
| 1 | KY-040 Rotary Encoder | 2.44 | 2.44 | Docking / Time selection |
| 1 | CONN SPRING MOD MALE 6POS SMD | 1.95 | 1.95 | Docking / Connector dock |
| 1 | CONN SPRING MOD FEMALE 6POS SMD | 1.92 | 1.92 | Docking / Connector clock |
| 4 | Magnets | 0.00 | Docking / Placement | |
| 1 | USB-C PD Trigger Board | 1.67 | 1.67 | Docking / Power |
| Total ex. VAT | €48.64 |