Files
nrfwatch/developmentlog.md
T
2026-05-01 11:53:56 +02:00

93 lines
4.8 KiB
Markdown

# Development Log
## 2026-04-25: Initial Setup
### Project: nrfwarch
Smartwatch firmware for Seeed Xiao nRF54L15 Sense with GDEY0154D61LT 1.54" e-ink display (SSD1680 controller).
### Board setup
- Ported SSD1680 e-ink driver from jcontrerasf/E-ink-display-Zephyr (nRF52840/PlatformIO) to Xiao nRF54L15 with modern Zephyr APIs
- Created custom devicetree binding (`nrfwarch,epd-ctrl`) for EPD GPIO pins
- VCOM command (0x2C, value 0xA5) added to init sequence
- Partial update uses `0xCF` control byte (clock + analog + temperature, no LUT reload)
### Framebuffer renderer
- 200x200 monochrome framebuffer, 5000 bytes
- Y-flip in `wf_set_pixel` compensates for SSD1680 data entry mode 0x01 (Y decrement): `ram_row = (199 - y)`
- `wf_draw_glyph` draws a glyph bitmap onto the framebuffer at (dst_x, dst_y). Font data uses bit=1 for foreground (draw black), bit=0 for background (skip). Background pixels are NOT written so the framebuffer underneath is preserved.
- Stack overflow in `epd_clear` fixed by using static buffer instead of stack allocation
### Fonts
- `tools/gen_fonts.py` generates `src/fonts.h` and `src/fonts.c` programmatically
- Digits are 24x40px (120 bytes each), colon is 8x40px (40 bytes)
- Digits are drawn as stroked outlines: horizontal/vertical lines + filled rectangles
- Each digit is a seven-segment-style rendering with 2px stroke thickness and 3px margins
- To regenerate: `python3 tools/gen_fonts.py`
### EPD wiring (Xiao nRF54L15 Sense)
| Signal | Pin | GPIO |
|--------|-----|------|
| MOSI | D10 | SPI MOSI |
| CLK | D8 | SPI SCK |
| CS | D1 | xiao_d 1 |
| DC | D3 | xiao_d 3 |
| RST | D0 | xiao_d 0 |
| BUSY | D2 | xiao_d 2 |
## 2026-04-25: Display Simulator
### What was done
- `sim/` directory with display simulator for watchface development without hardware
- `sim/src/epd_sim.c` implements the same API as `src/epd_drv.c`, dumps framebuffer as hex over stdout with `!F!`/`!E!` framing
- `sim/view.py` reads hex frames from stdin, inverts polarity (framebuffer bit=1 is white, PBM bit=1 is black), writes PBM to `/tmp/nrfwarch.pbm`
- feh auto-launches with `--auto-reload` for live display
- Sim runs on Zephyr `native_sim` target (native Linux process, not QEMU). Time advances 1 min/sec.
### Sim architecture
```
Zephyr native_sim binary (zephyr.exe)
-> watchface.c renders to framebuffer (same code as hardware)
-> epd_sim.c dumps hex frames to stdout
-> view.py reads stdin, converts to PBM
-> feh displays the image with auto-reload
```
### Frame protocol
- `!F!` = frame start, then 5000 bytes as hex (32 bytes/line), `!E!` = frame end
- view.py ignores everything outside markers (handles Zephyr log output mixed in)
## 2026-04-25: Flake, Tests, and Font Fixes
### Nix flake
- Converted from shell.nix to flake.nix with three apps:
- `nix run .#setup-sdk` - downloads Zephyr v4.0 + SDK 0.17.0, patches all ELF binaries for NixOS
- `nix run .#test` - builds and runs ztest suite on QEMU x86
- `nix run .#sim` - builds and runs display simulator with feh viewer
- Dev shell includes: cmake, ninja, west, qemu_full, feh, dtc, patchelf
### NixOS SDK patching
- Zephyr SDK binaries are dynamically linked with `/lib64/ld-linux-x86-64.so.2` interpreter
- Must patchelf every ELF binary (bin/, libexec/, lib/) to point at NixOS's ld-linux
- The setup-sdk app handles this automatically
### Test suite (17 tests, all passing)
- **Framebuffer**: clear sets all 0xFF, pixel set/get roundtrip, bounds checking, Y-flip at corners
- **Fonts**: digit/colon array sizes match compile-time constants, all digits contain non-trivial data
- **Rendering**: draw_time modifies buffer, different times produce different buffers, battery indicator renders, 0% vs 100% battery differs
- **Glyph**: all-ones glyph sets pixel to black
### Bugs fixed this session
- **Inverted fonts**: `wf_draw_glyph` had `black = !(val & bit)` which inverted foreground/background. Fixed to only draw when bit=1 (foreground), skip bit=0 (background).
- **Leading bar artifact**: Background pixels were being explicitly written as white on every glyph byte, overwriting adjacent content. Fixed by skipping background pixels entirely (`continue` on bit=0).
- **Font data mismatch**: Original hand-written font data had wrong byte counts (112 bytes declared as 64). Replaced with programmatic generator.
- **epd_clear stack overflow**: 5000-byte array on stack blew past 4KB main stack. Changed to static buffer.
- **Devicetree binding**: `compatible = "simple-bus"` rejected custom GPIO properties. Created proper `nrfwarch,epd-ctrl.yaml` binding.
### Next steps
- BLE CTS client for time sync from phone
- GRTC-based RTC for persistent timekeeping across system-off
- Battery monitoring via ADC channel 7 with VBAT regulator
- LSM6DSO IMU wrist-tilt wake detection
- System-off power management with regulator control
- Partial refresh for minute updates (avoid full refresh flicker)