# 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 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 |