2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00
2026-05-01 11:53:56 +02:00

nrfwarch

Smartwatch firmware for the Seeed Xiao nRF54L15 Sense with a GDEY0154D61LT 1.54" e-ink display.

Built on Zephyr RTOS v4.0. Targets the xiao_nrf54l15/nrf54l15/cpuapp board. Includes a display simulator that renders the watchface on your desktop using feh.

Hardware

  • MCU: Nordic nRF54L15 (ARM Cortex-M33, 128KB RAM, 1MB Flash)
  • Board: Seeed Xiao nRF54L15 Sense (includes LSM6DSO IMU, PDM mic)
  • Display: Good Display GDEY0154D61LT 1.54" e-ink, 200x200px, SSD1680 controller, SPI

E-ink wiring

Signal Xiao Pin Function
MOSI D10 SPI data out
CLK D8 SPI clock
CS D1 Chip select
DC D3 Data/command
RST D0 Hardware reset
BUSY D2 Busy status

Quick start

1. Enter dev shell

nix develop

2. Set up Zephyr SDK (first time only)

Downloads Zephyr v4.0 workspace and SDK 0.17.0, patches binaries for NixOS:

nix run .#setup-sdk

3. Run the display simulator

Builds and runs the watchface simulator. Time advances 1 minute per second. A feh window opens showing the e-ink output in real time. The simulator uses the same rendering code that runs on the real hardware.

nix run .#sim

This pipes the native_sim binary output through sim/view.py, which converts the framebuffer to PBM and auto-launches feh with --auto-reload. The output file is /tmp/nrfwarch.pbm.

4. Run tests

17 ztest cases covering framebuffer operations, font data, and rendering:

nix run .#test

5. Build for hardware

Open the project in nRF Connect for VS Code and add the application with board target xiao_nrf54l15/nrf54l15/cpuapp. Or build from the command line:

west build -b xiao_nrf54l15/nrf54l15/cpuapp .

Display simulation

The simulator lets you develop the watchface visually without any hardware. It runs the actual Zephyr kernel and the real watchface.c / fonts.c code -- the only difference is the EPD driver writes to a file instead of SPI.

How it works:

  1. sim/src/epd_sim.c implements the same API as src/epd_drv.c, but instead of sending SPI commands it dumps the raw framebuffer over stdout as hex, framed with !F! / !E! markers
  2. sim/view.py reads the hex frames from stdin, inverts the polarity (framebuffer uses 1=white, PBM uses 1=black), and writes a PBM image to /tmp/nrfwarch.pbm
  3. feh watches the file with --auto-reload and updates the display window

The simulated time starts at 12:00 and advances 1 minute every 0.8 seconds. Battery decreases by 2% per simulated hour. A full 24-hour cycle runs in about 19 minutes.

Manual usage

If you want to run the sim without feh or pipe the output yourself:

# Run the binary directly (outputs hex frames to stdout)
./build-sim/zephyr/zephyr.exe

# Convert a single frame to PBM manually
./build-sim/zephyr/zephyr.exe | python3 sim/view.py

# Just view the current frame
feh /tmp/nrfwarch.pbm

# Convert to PNG for sharing
pnmtopng /tmp/nrfwarch.pbm > watchface.png

Iterating on the watchface

  1. Edit src/watchface.c or modify digit shapes in tools/gen_fonts.py
  2. If fonts changed: python3 tools/gen_fonts.py
  3. Rebuild sim: cmake -B build-sim -GNinja -DBOARD=qemu_x86 -S sim && ninja -C build-sim
  4. Run: ./build-sim/zephyr/zephyr.exe | python3 sim/view.py
  5. feh auto-reloads when the PBM changes

Project layout

src/
  main.c           Entry point, minute-by-minute watch face loop
  epd_drv.c/h      SSD1680 e-ink driver (SPI, full/partial refresh, deep sleep)
  watchface.c/h    Framebuffer renderer (200x200 monochrome)
  fonts.c/h        Bitmap fonts (24x40 digits, 8x40 colon)

sim/
  src/main.c       Simulated main loop (accelerated time, no hardware)
  src/epd_sim.c    EPD mock - dumps framebuffer as hex over stdout
  view.py          Reads hex frames, writes PBM, launches feh
  CMakeLists.txt   Sim build config
  prj.conf         Sim Kconfig

boards/
  xiao_nrf54l15_nrf54l15_cpuapp.overlay   Devicetree overlay (SPI, GPIO, I2C, BT)
  xiao_nrf54l15_nrf54l15_cpuapp.conf      Board-specific Kconfig

dts/bindings/
  nrfwarch,epd-ctrl.yaml                  Custom binding for EPD GPIO control node

tests/
  src/main.c       17 ztest cases (framebuffer, pixels, fonts, rendering)
  prj.conf         Test build config

prj.conf           Main application config
CMakeLists.txt     Build definition
flake.nix          Nix flake (dev shell, setup-sdk, test, sim)

tools/
  gen_fonts.py     Font generator (run to regenerate src/fonts.*)

Flake commands

Command Description
nix develop Enter dev shell with all tools
nix run .#setup-sdk Download and set up Zephyr SDK + workspace
nix run .#test Build and run ztest suite on QEMU
nix run .#sim Run display simulator with feh viewer

What works

  • SSD1680 e-ink driver with full refresh and partial refresh
  • Framebuffer-based watch face with time rendering (HH:MM) and battery indicator
  • Deep sleep / wake for the display
  • Display simulator with real-time feh output
  • 17 passing tests covering framebuffer operations, font data integrity, and rendering

In progress / planned

  • BLE Current Time Service (CTS) client for phone time sync
  • GRTC-based RTC for persistent timekeeping across deep sleep
  • Battery monitoring via ADC (channel 7, VBAT regulator)
  • LSM6DSO IMU wrist-tilt wake detection
  • System-off power management with regulator control
  • Partial refresh for minute updates (avoid full refresh flicker)
  • BLE Battery Service (BAS) and notifications

References

License

Apache 2.0

S
Description
No description provided
Readme 61 KiB