Initial bare-metal foundation for nRF52840 PTT-FHSS

- Fully open toolchain: arm-none-eabi-gcc inside Podman/Docker container
- Platform-agnostic build via justfile (auto-detects podman vs docker)
- CMake + Ninja build system with arm-none-eabi toolchain file
- nRF52840 linker script and startup with full 64-entry vector table
- Register access via typed bitfield unions (include/regs.h)
- FHSS channel sequencer: AES-128-ECB PRNG over 40 channels (2402-2480 MHz)
- Low-power sleep via SYSTEM_ON WFI + GPIOTE wakeup on button press
- Vendor deps as shallow git submodules: nrfx, CMSIS_5, tiny-AES-c
This commit is contained in:
Krzysztof Cieślik
2026-05-21 20:20:21 +02:00
commit 969be959f0
20 changed files with 745 additions and 0 deletions

123
README.md Normal file
View File

@@ -0,0 +1,123 @@
# ptt-fhss
Bare-metal PTT (Push-to-Talk) firmware for the **Seeed XIAO BLE (nRF52840)** with
frequency-hopping spread spectrum (FHSS) on the 2.4 GHz band.
Designed to be low-power, hard to detect, and built with a fully open toolchain -
no Zephyr, no Nordic SDK, no SoftDevice.
## Hardware
| Component | Part |
|-----------|------|
| Target | Seeed XIAO BLE (nRF52840) |
| Programmer | RP2040 running DAPLink |
## How it works
### FHSS
The radio hops across 40 channels (2402-2480 MHz, 2 MHz steps) every 2 ms.
The hopping sequence is generated by AES-128-ECB keyed with a shared secret -
both devices derive identical sequences independently without any synchronisation
traffic. To an observer with an SDR the transmissions appear as short, scattered
impulses with no identifiable pattern.
### Low power
The nRF52840 stays in SYSTEM_ON sleep (`WFI`) with the DC/DC converter active
(~1.5 uA quiescent). A GPIOTE edge event on the PTT button wakes the CPU;
the radio is active only during the burst.
### Register access
All hardware register writes use typed bitfield unions defined in `include/regs.h`
rather than raw bit-shift expressions. The union layout is guaranteed correct with
`arm-none-eabi-gcc` (LSB-first bitfields on Cortex-M).
## Toolchain
Everything that produces the binary runs inside a container. The host only needs
tools that talk to hardware (pyocd) or manage the build (just).
| Tool | Purpose | Install |
|------|---------|---------|
| podman or docker | Container runtime | distro package |
| just | Task runner | `cargo install just` or distro package |
| pyocd | Flash / debug via DAPLink | `pip install pyocd` |
| git | Source control + submodules | distro package |
Inside the container: `debian:bookworm-slim` + `gcc-arm-none-eabi` from apt +
`cmake` + `ninja`.
## Quick start
```sh
git clone <repo-url> ptt
cd ptt
git submodule update --init --depth=1
just build # build firmware.hex inside container
just flash # flash via DAPLink (host pyocd)
```
On first run `just build` pulls the container image and compiles. Subsequent
builds reuse the cached image and only recompile changed files.
## Tasks
```
just build compile firmware inside the container
just flash build + flash via DAPLink
just gdbserver start pyocd GDB server on port 3333
just clean remove build/
just clean-all remove build/ and the container image
```
## Project structure
```
.
|-- Dockerfile container image (debian + arm-gcc from apt)
|-- justfile build / flash / debug tasks
|-- CMakeLists.txt
|-- cmake/
| \-- arm-none-eabi.cmake CMake toolchain file
|-- link/
| \-- nrf52840.ld linker script (no SoftDevice, Flash @ 0x00000000)
|-- include/
| |-- regs.h hardware register bitfield unions
| |-- radio.h
| |-- fhss.h
| \-- power.h
|-- src/
| |-- startup.c vector table (64 entries) + Reset_Handler
| |-- main.c
| |-- radio.c RADIO peripheral driver (stub)
| |-- fhss.c AES-ECB hopping sequence generator
| \-- power.c DC/DC, GPIOTE wakeup, WFI sleep
\-- vendor/
|-- nrfx/ Nordic HAL headers, pinned to v2.9.0 (includes mdk/)
|-- CMSIS_5/ ARM core headers (core_cm4.h etc.)
\-- tiny-aes-c/ Public domain AES-128 implementation
```
## Before first flash
The XIAO BLE ships with a SoftDevice which occupies the start of Flash.
Erase it before flashing bare-metal firmware:
```sh
pyocd erase --target nrf52840 --chip
```
## Status
| Module | State |
|--------|-------|
| Startup / vector table | done |
| Linker script | done |
| Power management (sleep + wakeup) | done |
| FHSS sequence generator | done |
| RADIO driver | stub - TX loop not yet implemented |
| Sync protocol | not started |