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