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:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
build/
|
||||||
9
.gitmodules
vendored
Normal file
9
.gitmodules
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[submodule "vendor/nrfx"]
|
||||||
|
path = vendor/nrfx
|
||||||
|
url = https://github.com/NordicSemiconductor/nrfx.git
|
||||||
|
[submodule "vendor/CMSIS_5"]
|
||||||
|
path = vendor/CMSIS_5
|
||||||
|
url = https://github.com/ARM-software/CMSIS_5.git
|
||||||
|
[submodule "vendor/tiny-aes-c"]
|
||||||
|
path = vendor/tiny-aes-c
|
||||||
|
url = https://github.com/kokke/tiny-AES-c.git
|
||||||
77
CMakeLists.txt
Normal file
77
CMakeLists.txt
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.22)
|
||||||
|
|
||||||
|
# must be set before project()
|
||||||
|
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/cmake/arm-none-eabi.cmake")
|
||||||
|
|
||||||
|
project(ptt_fhss C ASM)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# ── sources ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
add_executable(firmware
|
||||||
|
src/startup.c
|
||||||
|
src/main.c
|
||||||
|
src/radio.c
|
||||||
|
src/fhss.c
|
||||||
|
src/power.c
|
||||||
|
vendor/tiny-aes-c/aes.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── include paths ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
target_include_directories(firmware PRIVATE
|
||||||
|
include
|
||||||
|
vendor/nrfx/mdk # nRF52840 device headers (nrf52840.h etc.)
|
||||||
|
vendor/CMSIS_5/CMSIS/Core/Include # core_cm4.h, cmsis_gcc.h etc.
|
||||||
|
vendor/tiny-aes-c
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── preprocessor defines ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
target_compile_definitions(firmware PRIVATE
|
||||||
|
NRF52840_XXAA # device variant required by nrfx/mdk headers
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── compiler flags ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
set(CPU_FLAGS
|
||||||
|
-mcpu=cortex-m4
|
||||||
|
-mthumb
|
||||||
|
-mfpu=fpv4-sp-d16
|
||||||
|
-mfloat-abi=hard
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_options(firmware PRIVATE
|
||||||
|
${CPU_FLAGS}
|
||||||
|
-Os
|
||||||
|
-ffunction-sections
|
||||||
|
-fdata-sections
|
||||||
|
-fno-exceptions
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-Werror
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── linker ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
target_link_options(firmware PRIVATE
|
||||||
|
${CPU_FLAGS}
|
||||||
|
-T ${CMAKE_SOURCE_DIR}/link/nrf52840.ld
|
||||||
|
-Wl,--gc-sections
|
||||||
|
-Wl,--print-memory-usage
|
||||||
|
-nostartfiles
|
||||||
|
-specs=nano.specs
|
||||||
|
-specs=nosys.specs
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── post-build: .hex + size report ────────────────────────────────────────
|
||||||
|
|
||||||
|
add_custom_command(TARGET firmware POST_BUILD
|
||||||
|
COMMAND arm-none-eabi-objcopy
|
||||||
|
-O ihex $<TARGET_FILE:firmware>
|
||||||
|
${CMAKE_BINARY_DIR}/firmware.hex
|
||||||
|
COMMAND arm-none-eabi-size $<TARGET_FILE:firmware>
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
gcc-arm-none-eabi \
|
||||||
|
binutils-arm-none-eabi \
|
||||||
|
libnewlib-arm-none-eabi \
|
||||||
|
cmake \
|
||||||
|
ninja-build \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
123
README.md
Normal file
123
README.md
Normal 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 |
|
||||||
16
cmake/arm-none-eabi.cmake
Normal file
16
cmake/arm-none-eabi.cmake
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
set(CMAKE_SYSTEM_NAME Generic)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
|
||||||
|
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
|
||||||
|
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
|
||||||
|
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
|
||||||
|
set(CMAKE_SIZE arm-none-eabi-size)
|
||||||
|
|
||||||
|
# prevent cmake from link-testing the compiler against the host
|
||||||
|
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||||
|
|
||||||
|
# do not search host paths for libraries or headers
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
6
include/fhss.h
Normal file
6
include/fhss.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void fhss_init(void);
|
||||||
|
uint8_t fhss_next_channel(void); /* next channel from AES-ECB PRNG sequence */
|
||||||
|
void fhss_sync_tick(void); /* call every FHSS_DWELL_MS ms */
|
||||||
4
include/power.h
Normal file
4
include/power.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void power_init(void);
|
||||||
|
void power_sleep_until_button(void); /* SYSTEM_ON WFI, woken by GPIOTE event */
|
||||||
7
include/radio.h
Normal file
7
include/radio.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void radio_init(void);
|
||||||
|
void radio_set_channel(uint8_t ch); /* 0-39, maps to 2402-2480 MHz */
|
||||||
|
void radio_tx(const uint8_t *data, uint8_t len);
|
||||||
|
void radio_tx_burst(void); /* TX with FHSS hopping */
|
||||||
90
include/regs.h
Normal file
90
include/regs.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hardware register bitfield unions for nRF52840 peripherals.
|
||||||
|
* Layout is guaranteed correct only with arm-none-eabi-gcc (LSB-first bitfields).
|
||||||
|
* Bit ranges match nRF52840 Product Specification v1.7.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* GPIOTE */
|
||||||
|
|
||||||
|
/* CONFIG[n]: channel configuration */
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint32_t MODE : 2; /* [1:0] 0=Disabled 1=Event 3=Task */
|
||||||
|
uint32_t : 6; /* [7:2] reserved */
|
||||||
|
uint32_t PSEL : 5; /* [12:8] pin number within port */
|
||||||
|
uint32_t PORT : 1; /* [13] 0=Port0 1=Port1 */
|
||||||
|
uint32_t : 2; /* [15:14] reserved */
|
||||||
|
uint32_t POLARITY : 2; /* [17:16] 0=None 1=LoToHi 2=HiToLo 3=Toggle */
|
||||||
|
uint32_t : 2; /* [19:18] reserved */
|
||||||
|
uint32_t OUTINIT : 1; /* [20] initial output value for Task mode */
|
||||||
|
uint32_t : 11; /* [31:21] reserved */
|
||||||
|
} bit;
|
||||||
|
uint32_t reg;
|
||||||
|
} gpiote_config_t;
|
||||||
|
|
||||||
|
#define GPIOTE_MODE_DISABLED 0u
|
||||||
|
#define GPIOTE_MODE_EVENT 1u
|
||||||
|
#define GPIOTE_MODE_TASK 3u
|
||||||
|
#define GPIOTE_POL_NONE 0u
|
||||||
|
#define GPIOTE_POL_LOTOHI 1u
|
||||||
|
#define GPIOTE_POL_HITOLO 2u
|
||||||
|
#define GPIOTE_POL_TOGGLE 3u
|
||||||
|
|
||||||
|
/* INTENSET / INTENCLR: interrupt enable */
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint32_t IN0 : 1; /* [0] channel 0 input event */
|
||||||
|
uint32_t IN1 : 1; /* [1] channel 1 input event */
|
||||||
|
uint32_t IN2 : 1; /* [2] */
|
||||||
|
uint32_t IN3 : 1; /* [3] */
|
||||||
|
uint32_t IN4 : 1; /* [4] */
|
||||||
|
uint32_t IN5 : 1; /* [5] */
|
||||||
|
uint32_t IN6 : 1; /* [6] */
|
||||||
|
uint32_t IN7 : 1; /* [7] */
|
||||||
|
uint32_t : 23; /* [30:8] reserved */
|
||||||
|
uint32_t PORT : 1; /* [31] PORT event */
|
||||||
|
} bit;
|
||||||
|
uint32_t reg;
|
||||||
|
} gpiote_inten_t;
|
||||||
|
|
||||||
|
/* RADIO */
|
||||||
|
|
||||||
|
/* FREQUENCY: radio channel */
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint32_t FREQUENCY : 7; /* [6:0] offset from base frequency in MHz */
|
||||||
|
uint32_t : 1; /* [7] reserved */
|
||||||
|
uint32_t MAP : 1; /* [8] 0: base=2400 MHz 1: base=2360 MHz */
|
||||||
|
uint32_t : 23; /* [31:9] reserved */
|
||||||
|
} bit;
|
||||||
|
uint32_t reg;
|
||||||
|
} radio_frequency_t;
|
||||||
|
|
||||||
|
#define RADIO_MAP_DEFAULT 0u /* channel n -> 2400+n MHz */
|
||||||
|
#define RADIO_MAP_BLE 1u /* channel n -> 2360+n MHz */
|
||||||
|
|
||||||
|
/* TXPOWER: transmit power */
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
int32_t TXPOWER : 8; /* [7:0] signed dBm: +8, +7, +6, +5, +4, +3, +2,
|
||||||
|
0, -4, -8, -12, -16, -20, -40 */
|
||||||
|
uint32_t : 24; /* [31:8] reserved */
|
||||||
|
} bit;
|
||||||
|
uint32_t reg;
|
||||||
|
} radio_txpower_t;
|
||||||
|
|
||||||
|
/* MODE: radio data rate and modulation */
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint32_t MODE : 4; /* [3:0] 0=NRF_1Mbit 1=NRF_2Mbit 4=BLE_1Mbit ... */
|
||||||
|
uint32_t : 28; /* [31:4] reserved */
|
||||||
|
} bit;
|
||||||
|
uint32_t reg;
|
||||||
|
} radio_mode_t;
|
||||||
|
|
||||||
|
#define RADIO_MODE_NRF_1MBIT 0u
|
||||||
|
#define RADIO_MODE_NRF_2MBIT 1u
|
||||||
|
#define RADIO_MODE_BLE_1MBIT 4u
|
||||||
44
justfile
Normal file
44
justfile
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# variables
|
||||||
|
image := "ptt-builder"
|
||||||
|
build_dir := "build"
|
||||||
|
|
||||||
|
# podman takes priority over docker
|
||||||
|
engine := `command -v podman 2>/dev/null \
|
||||||
|
|| command -v docker 2>/dev/null \
|
||||||
|
|| { echo "error: podman or docker required" >&2; exit 1; }`
|
||||||
|
|
||||||
|
# user-namespace mapping: podman rootless vs docker
|
||||||
|
user_ns := `command -v podman >/dev/null 2>&1 \
|
||||||
|
&& echo "--userns=keep-id" \
|
||||||
|
|| echo "--user $(id -u):$(id -g)"`
|
||||||
|
|
||||||
|
# recipes
|
||||||
|
# build container image (only rebuilt when Dockerfile changes)
|
||||||
|
image-build:
|
||||||
|
{{engine}} build -t {{image}} .
|
||||||
|
|
||||||
|
# compile firmware inside the container
|
||||||
|
build: image-build
|
||||||
|
{{engine}} run --rm \
|
||||||
|
{{user_ns}} \
|
||||||
|
-v "{{justfile_directory()}}:/src:z" \
|
||||||
|
{{image}} \
|
||||||
|
sh -c "cmake -B /src/{{build_dir}} -G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=MinSizeRel /src \
|
||||||
|
&& ninja -C /src/{{build_dir}}"
|
||||||
|
|
||||||
|
# flash firmware via pyocd on the host (requires USB / DAPLink)
|
||||||
|
flash: build
|
||||||
|
pyocd flash --target nrf52840 {{build_dir}}/firmware.hex
|
||||||
|
|
||||||
|
# start GDB server for debugging
|
||||||
|
gdbserver:
|
||||||
|
pyocd gdbserver --target nrf52840 --port 3333
|
||||||
|
|
||||||
|
# remove build artifacts
|
||||||
|
clean:
|
||||||
|
rm -rf {{build_dir}}
|
||||||
|
|
||||||
|
# remove build artifacts AND the container image
|
||||||
|
clean-all: clean
|
||||||
|
{{engine}} rmi {{image}} 2>/dev/null || true
|
||||||
66
link/nrf52840.ld
Normal file
66
link/nrf52840.ld
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* nRF52840 - no SoftDevice
|
||||||
|
* Flash: 1 MB @ 0x00000000
|
||||||
|
* RAM: 256 KB @ 0x20000000
|
||||||
|
*
|
||||||
|
* Before first flash, erase the entire chip to remove any SoftDevice:
|
||||||
|
* pyocd erase --target nrf52840 --chip
|
||||||
|
*/
|
||||||
|
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 1024K
|
||||||
|
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stack grows downward from the top of RAM */
|
||||||
|
_estack = ORIGIN(RAM) + LENGTH(RAM);
|
||||||
|
|
||||||
|
ENTRY(Reset_Handler)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
/* vector table must be at the start of Flash */
|
||||||
|
.isr_vector :
|
||||||
|
{
|
||||||
|
KEEP(*(.isr_vector))
|
||||||
|
. = ALIGN(4);
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
/* code and read-only data */
|
||||||
|
.text :
|
||||||
|
{
|
||||||
|
*(.text .text.*)
|
||||||
|
*(.rodata .rodata.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_etext = .;
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
/* load address of .data in Flash - Reset_Handler copies from here */
|
||||||
|
_sidata = LOADADDR(.data);
|
||||||
|
|
||||||
|
/* initialized variables: stored in Flash, run from RAM */
|
||||||
|
.data :
|
||||||
|
{
|
||||||
|
_sdata = .;
|
||||||
|
*(.data .data.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_edata = .;
|
||||||
|
} > RAM AT > FLASH
|
||||||
|
|
||||||
|
/* zero-initialized variables: Reset_Handler clears this region */
|
||||||
|
.bss (NOLOAD) :
|
||||||
|
{
|
||||||
|
_sbss = .;
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_ebss = .;
|
||||||
|
} > RAM
|
||||||
|
|
||||||
|
/DISCARD/ :
|
||||||
|
{
|
||||||
|
*(.ARM.exidx*)
|
||||||
|
*(.gnu.linkonce.armexidx.*)
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/fhss.c
Normal file
41
src/fhss.c
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include "fhss.h"
|
||||||
|
#include <aes.h>
|
||||||
|
|
||||||
|
#define FHSS_CHANNELS 40u
|
||||||
|
#define FHSS_DWELL_MS 2u
|
||||||
|
|
||||||
|
/* TODO: replace with a real shared secret before deployment */
|
||||||
|
static const uint8_t shared_key[16] = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t slot;
|
||||||
|
|
||||||
|
void fhss_init(void)
|
||||||
|
{
|
||||||
|
slot = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t fhss_next_channel(void)
|
||||||
|
{
|
||||||
|
uint8_t block[16] = {0};
|
||||||
|
struct AES_ctx ctx;
|
||||||
|
|
||||||
|
/* encode slot counter big-endian into the AES input block */
|
||||||
|
block[0] = (uint8_t)(slot >> 24);
|
||||||
|
block[1] = (uint8_t)(slot >> 16);
|
||||||
|
block[2] = (uint8_t)(slot >> 8);
|
||||||
|
block[3] = (uint8_t)(slot );
|
||||||
|
|
||||||
|
AES_init_ctx(&ctx, shared_key);
|
||||||
|
AES_ECB_encrypt(&ctx, block); /* encrypts block in-place */
|
||||||
|
|
||||||
|
slot++;
|
||||||
|
return block[0] % FHSS_CHANNELS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fhss_sync_tick(void)
|
||||||
|
{
|
||||||
|
slot++;
|
||||||
|
}
|
||||||
15
src/main.c
Normal file
15
src/main.c
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#include "radio.h"
|
||||||
|
#include "fhss.h"
|
||||||
|
#include "power.h"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
power_init();
|
||||||
|
radio_init();
|
||||||
|
fhss_init();
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
power_sleep_until_button();
|
||||||
|
radio_tx_burst();
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/power.c
Normal file
37
src/power.c
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include "power.h"
|
||||||
|
#include "regs.h"
|
||||||
|
#include <nrf52840.h>
|
||||||
|
#include <cmsis_gcc.h>
|
||||||
|
|
||||||
|
/* P0.02 on XIAO BLE - adjust to match your schematic */
|
||||||
|
#define BUTTON_PIN 2u
|
||||||
|
|
||||||
|
void power_init(void)
|
||||||
|
{
|
||||||
|
/* DC/DC converter has lower quiescent current than the LDO */
|
||||||
|
NRF_POWER->DCDCEN = 1u;
|
||||||
|
|
||||||
|
NRF_GPIOTE->CONFIG[0] = (gpiote_config_t){
|
||||||
|
.bit = {
|
||||||
|
.MODE = GPIOTE_MODE_EVENT,
|
||||||
|
.PSEL = BUTTON_PIN,
|
||||||
|
.PORT = 0u,
|
||||||
|
.POLARITY = GPIOTE_POL_LOTOHI,
|
||||||
|
}
|
||||||
|
}.reg;
|
||||||
|
|
||||||
|
NRF_GPIOTE->INTENSET = (gpiote_inten_t){ .bit.IN0 = 1u }.reg;
|
||||||
|
|
||||||
|
NVIC_EnableIRQ(GPIOTE_IRQn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void power_sleep_until_button(void)
|
||||||
|
{
|
||||||
|
NRF_POWER->TASKS_LOWPWR = 1u;
|
||||||
|
__WFI();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPIOTE_IRQHandler(void)
|
||||||
|
{
|
||||||
|
NRF_GPIOTE->EVENTS_IN[0] = 0u;
|
||||||
|
}
|
||||||
27
src/radio.c
Normal file
27
src/radio.c
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#include "radio.h"
|
||||||
|
#include "fhss.h"
|
||||||
|
#include "regs.h"
|
||||||
|
#include <nrf52840.h>
|
||||||
|
|
||||||
|
void radio_init(void)
|
||||||
|
{
|
||||||
|
/* TODO: configure RADIO peripheral (MODE, PCNF0/1, BASE/PREFIX, CRC) */
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_set_channel(uint8_t ch)
|
||||||
|
{
|
||||||
|
NRF_RADIO->FREQUENCY = (radio_frequency_t){
|
||||||
|
.bit = { .FREQUENCY = ch, .MAP = RADIO_MAP_DEFAULT }
|
||||||
|
}.reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_tx(const uint8_t *data, uint8_t len)
|
||||||
|
{
|
||||||
|
(void)data; (void)len;
|
||||||
|
/* TODO: load packet, enable TX, wait for END event, disable */
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_tx_burst(void)
|
||||||
|
{
|
||||||
|
/* TODO: hop + TX loop driven by fhss_next_channel() */
|
||||||
|
}
|
||||||
168
src/startup.c
Normal file
168
src/startup.c
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* linker script symbols */
|
||||||
|
extern uint32_t _sidata; /* load address of .data in Flash */
|
||||||
|
extern uint32_t _sdata; /* start of .data in RAM */
|
||||||
|
extern uint32_t _edata; /* end of .data in RAM */
|
||||||
|
extern uint32_t _sbss;
|
||||||
|
extern uint32_t _ebss;
|
||||||
|
extern uint32_t _estack; /* address equals initial SP value */
|
||||||
|
|
||||||
|
extern int main(void);
|
||||||
|
|
||||||
|
void Reset_Handler(void);
|
||||||
|
|
||||||
|
static void __attribute__((used)) Default_Handler(void)
|
||||||
|
{
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ARM Cortex-M4 core exceptions */
|
||||||
|
void NMI_Handler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void HardFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void MemManage_Handler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void BusFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void UsageFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SVC_Handler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void DebugMon_Handler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void PendSV_Handler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SysTick_Handler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
|
||||||
|
/* nRF52840 peripheral IRQs (IRQ0-IRQ47) */
|
||||||
|
void POWER_CLOCK_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void RADIO_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void UARTE0_UART0_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void NFCT_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void GPIOTE_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SAADC_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void TIMER0_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void TIMER1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void TIMER2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void RTC0_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void TEMP_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void RNG_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void ECB_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void CCM_AAR_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void WDT_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void RTC1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void QDEC_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void COMP_LPCOMP_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SWI0_EGU0_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SWI1_EGU1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SWI2_EGU2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SWI3_EGU3_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SWI4_EGU4_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SWI5_EGU5_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void TIMER3_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void TIMER4_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void PWM0_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void PDM_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void MWU_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void PWM1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void PWM2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SPIM2_SPIS2_SPI2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void RTC2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void I2S_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void FPU_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void USBD_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void UARTE1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void QSPI_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void CRYPTOCELL_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void PWM3_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
void SPIM3_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
|
||||||
|
|
||||||
|
/* vector table: 64 entries (16 core + 48 IRQs) */
|
||||||
|
|
||||||
|
typedef void (*vector_fn)(void);
|
||||||
|
|
||||||
|
__attribute__((section(".isr_vector"), used))
|
||||||
|
const vector_fn vectors[64] = {
|
||||||
|
/* 0 */ (vector_fn)&_estack,
|
||||||
|
/* 1 */ Reset_Handler,
|
||||||
|
/* 2 */ NMI_Handler,
|
||||||
|
/* 3 */ HardFault_Handler,
|
||||||
|
/* 4 */ MemManage_Handler,
|
||||||
|
/* 5 */ BusFault_Handler,
|
||||||
|
/* 6 */ UsageFault_Handler,
|
||||||
|
/* 7 */ 0,
|
||||||
|
/* 8 */ 0,
|
||||||
|
/* 9 */ 0,
|
||||||
|
/* 10 */ 0,
|
||||||
|
/* 11 */ SVC_Handler,
|
||||||
|
/* 12 */ DebugMon_Handler,
|
||||||
|
/* 13 */ 0,
|
||||||
|
/* 14 */ PendSV_Handler,
|
||||||
|
/* 15 */ SysTick_Handler,
|
||||||
|
|
||||||
|
/* IRQ0 */ POWER_CLOCK_IRQHandler,
|
||||||
|
/* IRQ1 */ RADIO_IRQHandler,
|
||||||
|
/* IRQ2 */ UARTE0_UART0_IRQHandler,
|
||||||
|
/* IRQ3 */ SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler,
|
||||||
|
/* IRQ4 */ SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler,
|
||||||
|
/* IRQ5 */ NFCT_IRQHandler,
|
||||||
|
/* IRQ6 */ GPIOTE_IRQHandler,
|
||||||
|
/* IRQ7 */ SAADC_IRQHandler,
|
||||||
|
/* IRQ8 */ TIMER0_IRQHandler,
|
||||||
|
/* IRQ9 */ TIMER1_IRQHandler,
|
||||||
|
/* IRQ10 */ TIMER2_IRQHandler,
|
||||||
|
/* IRQ11 */ RTC0_IRQHandler,
|
||||||
|
/* IRQ12 */ TEMP_IRQHandler,
|
||||||
|
/* IRQ13 */ RNG_IRQHandler,
|
||||||
|
/* IRQ14 */ ECB_IRQHandler,
|
||||||
|
/* IRQ15 */ CCM_AAR_IRQHandler,
|
||||||
|
/* IRQ16 */ WDT_IRQHandler,
|
||||||
|
/* IRQ17 */ RTC1_IRQHandler,
|
||||||
|
/* IRQ18 */ QDEC_IRQHandler,
|
||||||
|
/* IRQ19 */ COMP_LPCOMP_IRQHandler,
|
||||||
|
/* IRQ20 */ SWI0_EGU0_IRQHandler,
|
||||||
|
/* IRQ21 */ SWI1_EGU1_IRQHandler,
|
||||||
|
/* IRQ22 */ SWI2_EGU2_IRQHandler,
|
||||||
|
/* IRQ23 */ SWI3_EGU3_IRQHandler,
|
||||||
|
/* IRQ24 */ SWI4_EGU4_IRQHandler,
|
||||||
|
/* IRQ25 */ SWI5_EGU5_IRQHandler,
|
||||||
|
/* IRQ26 */ TIMER3_IRQHandler,
|
||||||
|
/* IRQ27 */ TIMER4_IRQHandler,
|
||||||
|
/* IRQ28 */ PWM0_IRQHandler,
|
||||||
|
/* IRQ29 */ PDM_IRQHandler,
|
||||||
|
/* IRQ30 */ 0, /* reserved */
|
||||||
|
/* IRQ31 */ 0, /* reserved */
|
||||||
|
/* IRQ32 */ MWU_IRQHandler,
|
||||||
|
/* IRQ33 */ PWM1_IRQHandler,
|
||||||
|
/* IRQ34 */ PWM2_IRQHandler,
|
||||||
|
/* IRQ35 */ SPIM2_SPIS2_SPI2_IRQHandler,
|
||||||
|
/* IRQ36 */ RTC2_IRQHandler,
|
||||||
|
/* IRQ37 */ I2S_IRQHandler,
|
||||||
|
/* IRQ38 */ FPU_IRQHandler,
|
||||||
|
/* IRQ39 */ USBD_IRQHandler,
|
||||||
|
/* IRQ40 */ UARTE1_IRQHandler,
|
||||||
|
/* IRQ41 */ QSPI_IRQHandler,
|
||||||
|
/* IRQ42 */ CRYPTOCELL_IRQHandler,
|
||||||
|
/* IRQ43 */ 0, /* reserved */
|
||||||
|
/* IRQ44 */ 0, /* reserved */
|
||||||
|
/* IRQ45 */ PWM3_IRQHandler,
|
||||||
|
/* IRQ46 */ 0, /* reserved */
|
||||||
|
/* IRQ47 */ SPIM3_IRQHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Reset_Handler */
|
||||||
|
|
||||||
|
void Reset_Handler(void)
|
||||||
|
{
|
||||||
|
/* copy .data initializers from Flash to RAM */
|
||||||
|
uint32_t *src = &_sidata;
|
||||||
|
uint32_t *dst = &_sdata;
|
||||||
|
while (dst < &_edata) {
|
||||||
|
*dst++ = *src++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* zero .bss */
|
||||||
|
dst = &_sbss;
|
||||||
|
while (dst < &_ebss) {
|
||||||
|
*dst++ = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
1
vendor/CMSIS_5
vendored
Submodule
1
vendor/CMSIS_5
vendored
Submodule
Submodule vendor/CMSIS_5 added at 55b19837f5
1
vendor/nrfx
vendored
Submodule
1
vendor/nrfx
vendored
Submodule
Submodule vendor/nrfx added at 16756cadac
1
vendor/tiny-aes-c
vendored
Submodule
1
vendor/tiny-aes-c
vendored
Submodule
Submodule vendor/tiny-aes-c added at 23856752fb
Reference in New Issue
Block a user