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