93 lines
4.8 KiB
Markdown
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)
|