Krzysztof Cieślik c9320eb9db
All checks were successful
CI / Build firmware (push) Successful in 36s
CI / Check formatting (push) Successful in 12s
CI / Static analysis (push) Successful in 12s
CI / Build documentation (push) Successful in 15s
Implement full FHSS TX/RX, real AES key, button read, slot sync
radio_tx_burst(): captures current slot, calls fhss_next_channel() to
advance the sequence, sets channel, transmits ptt_frame_t, then holds
the dwell window via TIMER0 so the receiver has the full 2 ms to listen.

radio_rx_burst(): advances to the same channel, enables RX, waits for
EVENTS_END or TIMER0 COMPARE0 expiry. On a valid CRC the ptt_frame_t
is returned; caller syncs FHSS with fhss_set_slot(frame.slot + 1).

TIMER0: configured once in radio_init() (MODE=Timer, BITMODE=16-bit,
PRESCALER=4 -> 1 MHz tick, CC[0]=2000 -> 2 ms, COMPARE0_STOP shortcut).

fhss.c: replace zero key with a non-trivial 128-bit value; add
fhss_set_slot() and fhss_get_slot() for RX synchronisation.

power.c: add BUTTON_ACTIVE_LOW flag (default 1 = pull-up + GND button);
configure PIN_CNF accordingly; implement power_button_pressed() via
NRF_P0->IN.

regs.h: add gpio_pin_cnf_t with GPIO_PULL_* constants.

main.c: tight PTT loop -- radio_tx_burst() while button held, otherwise
radio_rx_burst() with slot sync on reception. No WFI in RX mode.
2026-05-22 00:40:49 +02:00
2026-05-21 23:15:56 +02:00

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

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:

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
Description
No description provided
Readme 412 KiB
Languages
C 83.6%
Just 8.5%
CMake 6.8%
Dockerfile 1.1%