2FAC – Two Factor Alarm Clock

f2fac-first-sketch.jpg

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

In this week we played with Micro Controllers and I developed strong feedings for the RP2040. My final project will have a base station and the clock itself. One of them will probably run a RP2040 with Internet access to keep time using NTP.

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.

IMG_20260330_171448_DRO.jpg

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.

clock-on-dock.png

Other Ideas

Light sensor the dim time when dark.

Build

Clock

Electronics

screenshot-2026-05-07_10-55-04.png.jpg
screenshot-2026-05-07_10-55-57.png.jpg
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]);
    }
    
    led-diffuser.png
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

IMG_20260505_150027_DRO.jpg
Figure 1: Testing the DFPlayer Mini on a breadboard with a XIAO RP2350

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.

IMG_20260513_143315.jpg
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.

    IMG_20260507_144258_DRO.jpg
    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 += 1
    

    Unfortunately, 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 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

  • [X] DCF77 (week 9)
  • [X] Step Response Snooze (week 9)
  • [X] Rotary Encoder (week 9)
  • [X] DFR0299 (

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

IMG_20260521_122233.jpg
IMG_20260522_151912.jpg
IMG_20260522_151935.jpg
IMG_20260522_151853.jpg

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.

IMG_20260523_163801.jpg
IMG_20260523_163810.jpg
IMG_20260523_163839.jpg
IMG_20260523_163851.jpg

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.

IMG_20260525_163650.jpg
IMG_20260525_170457.jpg

While soldering I printed some diffusers for the display to try out.

IMG_20260525_210326.jpg

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.

IMG_20260528_131018.jpg
Figure 3: all 4 iterations (at this moment)

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.

IMG_20260529_132427.jpg
IMG_20260529_132722.jpg

Settings used:

  Power % min/max Speed
Cardboard 30/20 100
Wood 100/90 25
Acrylic 75/65 10
IMG_20260529_164026.jpg
IMG_20260529_164032.jpg

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.

IMG_20260529_164216.jpg

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.

IMG_20260530_104142.jpg
IMG_20260530_104152.jpg
IMG_20260530_104205.jpg

The rest of the day I spend on designing a dock. Extra point for being able to print it on my Pursa Mini (180x180x180mm).

screenshot-2026-05-31_10-15-52.png.jpg
dock-draft-top-20260530.png
dock-draft-bottom-20260530.png

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.

IMG_20260531_125213.jpg

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  

TODO Software Bill of Materials

Inspiration

Libraries

Copyright © 2026 Remco van 't Veer

Licensed under a
CC BY 4.0

Build using
GNU Emacs, Org Mode and GNU Guix

Source code hosted at
gitlab.fabcloud.org