https://github.com/libmcu/template-nrf52
https://github.com/libmcu/template-nrf52
Last synced: 3 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/libmcu/template-nrf52
- Owner: libmcu
- License: mit
- Created: 2024-02-08T01:13:34.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2026-03-30T01:00:09.000Z (3 months ago)
- Last Synced: 2026-03-30T04:28:49.862Z (3 months ago)
- Language: C
- Size: 20.9 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# madi
Firmware for MADI devices across multiple MCU targets and platform stacks.
This repository supports nRF52 (Zephyr/nRF5 SDK) and ESP32-class targets (ESP-IDF) with a shared, port-based architecture.
## Project Structure
```
.
├── CMakeLists.txt # Top-level entry (Zephyr or nRF5 SDK)
├── include/ # Public headers (version.h, metrics.def …)
├── src/ # Application sources
├── ports/
│ ├── nrf52/ # nRF5 SDK port (legacy)
│ └── zephyr/
│ ├── boards/nordic/madi_nrf52840/ # Board definition (DTS, Kconfig)
│ ├── prj.conf # Application Kconfig
│ └── mcuboot.conf # MCUboot Kconfig overlay
├── projects/
│ └── platforms/
│ ├── zephyr.cmake # Zephyr build wiring (signing key, modules)
│ └── madi_nrf52840.cmake # nRF5 SDK CMake platform config
├── secrets/
│ └── dfu_signing_dev.key # Dev signing key — never commit to VCS
├── tests/ # Unit tests (Make-based)
└── external/ # libmcu and other third-party sources
```
Key files:
| File | Purpose |
|------|---------|
| `ports/zephyr/boards/nordic/madi_nrf52840/madi_nrf52840.dts` | Flash partitions, QSPI, GPIO |
| `ports/zephyr/prj.conf` | Application Kconfig |
| `ports/zephyr/mcuboot.conf` | MCUboot Kconfig (signature algorithm, swap, QSPI) |
| `projects/platforms/zephyr.cmake` | Signing key path, Zephyr module list |
---
## Prerequisites
### Zephyr / nRF52
#### 1. Zephyr workspace
Install the Zephyr SDK outside this repo using
[west](https://docs.zephyrproject.org/latest/develop/getting_started/index.html):
```bash
west init $HOME/zephyr
cd $HOME/zephyr
west update
```
Expected layout:
```
$HOME/zephyr/
├── zephyr/ # Zephyr kernel ($ZEPHYR_BASE)
├── modules/ # hal_nordic, mbedtls, littlefs, segger …
└── bootloader/ # mcuboot
```
#### 2. ARM GNU Toolchain (GCC 12+)
Download **arm-none-eabi** from
[developer.arm.com](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads)
and extract, e.g. to `~/.local/gcc-arm-none-eabi/`.
#### 3. Build tools
```bash
brew install ninja # macOS
sudo apt install ninja-build # Ubuntu / Debian
```
#### 4. Flash tools
| Tool | Use |
|------|-----|
| `nrfjprog` | J-Link / nRF5 — initial flash and app updates |
| `pyocd` | OpenOCD-based alternative to nrfjprog |
| `mcumgr` | OTA / DFU over serial or BLE |
#### 5. imgtool
```bash
python3 -m venv .venv
source .venv/bin/activate
export PATH=.venv/bin:$PATH
export ZEPHYR_BASE=$HOME/zephyr/zephyr
export ZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb
export GNUARMEMB_TOOLCHAIN_PATH=~/.local/gcc-arm-none-eabi
```
```bash
pip install -r $ZEPHYR_BASE/../bootloader/mcuboot/scripts/requirements.txt
```
#### 6. Zephyr Python requirements (required for `west build`)
Install Zephyr script dependencies in the active Python environment:
```bash
pip install -r $ZEPHYR_BASE/scripts/requirements.txt
```
> Missing this step commonly fails with `ModuleNotFoundError` (for example
> `pykwalify`) during board discovery.
### ESP-IDF
#### 1. ESP-IDF checkout and tools path
- IDF root (example): `$HOME/esp/esp-idf`
- tools path (default `IDF_TOOLS_PATH`): `$HOME/.espressif`
#### 2. Build tools
```bash
brew install ninja # macOS
sudo apt install ninja-build # Ubuntu / Debian
```
---
## Environment Setup
### Zephyr
Add to your shell profile (`.bashrc` / `.zshrc`) and reload:
```bash
source .venv/bin/activate
export PATH=.venv/bin:$PATH
export ZEPHYR_BASE=$HOME/zephyr/zephyr
export ZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb
export GNUARMEMB_TOOLCHAIN_PATH=~/.local/gcc-arm-none-eabi
# Install the official mcumgr CLI (Go required)
go install github.com/apache/mynewt-mcumgr-cli/mcumgr@latest
# Add Go bin to PATH if needed
export PATH="$(go env GOPATH)/bin:$PATH"
```
### ESP-IDF
Run before ESP32 build/flash commands:
```bash
export IDF_TOOLS_PATH=$HOME/.espressif
source $HOME/esp/esp-idf/export.sh
```
---
## Initial Setup
### Zephyr
Build MCUboot and the app, then flash everything to a blank device.
#### 1. Build MCUboot
#### west
```bash
west build -b madi_nrf52840 -d build/mcuboot \
$ZEPHYR_BASE/../bootloader/mcuboot/boot/zephyr \
-- -DBOARD_ROOT=$(pwd)/ports/zephyr \
"-DEXTRA_CONF_FILE=$(pwd)/ports/zephyr/mcuboot.conf" \
"-DCONFIG_BOOT_SIGNATURE_KEY_FILE=\"$(pwd)/secrets/dfu_signing_dev.key\""
```
#### CMake
```bash
cmake -B build/mcuboot \
-S $ZEPHYR_BASE/../bootloader/mcuboot/boot/zephyr \
-DBOARD=madi_nrf52840 \
-DBOARD_ROOT=$(pwd)/ports/zephyr \
"-DEXTRA_CONF_FILE=$(pwd)/ports/zephyr/mcuboot.conf" \
"-DCONFIG_BOOT_SIGNATURE_KEY_FILE=$(pwd)/secrets/dfu_signing_dev.key" \
-G Ninja
cmake --build build/mcuboot
```
Output: `build/mcuboot/zephyr/zephyr.hex`
#### 2. Build App
#### NCS sysbuild
```bash
west build \
-b madi_nrf52840 \
-d build \
--sysbuild \
-- \
-DCONF_FILE=ports/zephyr/prj.conf \
-DBOARD_ROOT=ports/zephyr
```
#### west
```bash
# First time — configure and build
west build -b madi_nrf52840 -d build
# or
west build -b madi_nrf52840 -d build -- -DBOARD_ROOT=$(pwd)/ports/zephyr
# Subsequent builds — configuration is cached
west build -d build
```
#### CMake
```bash
cmake -B build -DTARGET_PLATFORM=madi_nrf52840 -G Ninja
cmake --build build
```
Signed outputs in `build/zephyr/`:
| File | Use |
|------|-----|
| `zephyr.signed.hex` | Test image — reverts on next reset if not confirmed |
| `zephyr.signed.bin` | Signed binary for OTA upload |
| `zephyr.signed.confirmed.hex` | Pre-confirmed — no runtime confirmation needed |
#### 3. Flash (first-time)
#### nrfjprog
```bash
# Erase chip
nrfjprog --family NRF52 --eraseall
# Flash MCUboot
nrfjprog --family NRF52 --program build/mcuboot/zephyr/zephyr.hex --verify
# Flash confirmed app to slot0 (--sectorerase preserves MCUboot at 0x0)
nrfjprog --family NRF52 \
--program build/zephyr/zephyr.signed.confirmed.hex \
--verify --sectorerase
nrfjprog --family NRF52 --reset
```
#### west
```bash
nrfjprog --family NRF52 --eraseall
west flash -d build/mcuboot
west flash -d build
```
#### pyocd
```bash
pyocd flash -t nrf52840 --erase chip build/mcuboot/zephyr/zephyr.hex
pyocd flash -t nrf52840 --erase sector \
build/zephyr/zephyr.signed.confirmed.hex
```
### ESP-IDF
Build (choose one):
```bash
idf.py -B build -DTARGET_PLATFORM=jc8012wp4a1 build
```
```bash
cmake -B build -DTARGET_PLATFORM=jc8012wp4a1 -G Ninja && cmake --build build
```
Flash and monitor:
```bash
idf.py -B build -DTARGET_PLATFORM=jc8012wp4a1 flash monitor
```
---
## Development Workflow
MCUboot stays on the device. Only rebuild and reflash the app.
### Build App
#### west
```bash
west build -d build
```
#### CMake
```bash
cmake --build build
```
#### Make (nRF5 SDK — no MCUboot)
```bash
make
```
> The Make build targets the nRF5 SDK directly (no Zephyr, no MCUboot).
> See [Legacy Build (nRF5 SDK)](#legacy-build-nrf5-sdk) for details.
### Flash App
#### nrfjprog
```bash
nrfjprog --family NRF52 \
--program build/zephyr/zephyr.signed.confirmed.hex \
--verify --sectorerase
nrfjprog --family NRF52 --reset
```
#### west
```bash
west flash -d build
```
#### pyocd
```bash
pyocd flash -t nrf52840 --erase sector \
build/zephyr/zephyr.signed.confirmed.hex
```
### OTA / DFU
The board exposes a USB CDC ACM port for mcumgr SMP. Connect the USB cable and
wait for the RTT log to show `USB CDC ACM ready` before running commands.
> **Port name**: `/dev/ttyACM0` on Linux, `/dev/tty.usbmodem*` on macOS.
```bash
# Upload image to slot 1
mcumgr --conntype serial --connstring /dev/ttyACM0,baud=115200 \
image upload build/zephyr/zephyr.signed.bin
# List images — copy the hash of the uploaded image
mcumgr --conntype serial --connstring /dev/ttyACM0,baud=115200 \
image list
# Mark as pending (test mode — reverts if not confirmed before next reset)
mcumgr --conntype serial --connstring /dev/ttyACM0,baud=115200 \
image test
# Trigger swap
mcumgr --conntype serial --connstring /dev/ttyACM0,baud=115200 reset
# After the new image boots successfully, confirm it permanently
mcumgr --conntype serial --connstring /dev/ttyACM0,baud=115200 \
image confirm
```
Or confirm from application code:
```c
boot_write_img_confirmed(); /* requires CONFIG_MCUBOOT_IMG_MANAGER=y */
```
> **Rollback**: If confirmation is not received before the next reset,
> MCUboot automatically reverts to the previous image in slot 0.
---
## Flash Partition Layout
| Flash | Partition | Label | Offset | Size |
|-------|-----------|-------|--------|------|
| Internal — nRF52840 (1 MiB) | `boot_partition` | `mcuboot` | `0x000000` | 48 KiB |
| Internal | `slot0_partition` | `image-0` | `0x00C000` | 976 KiB |
| External — MX25R1635F (2 MiB) | `slot1_partition` | `image-1` | `0x000000` | 976 KiB |
| External | `storage_partition` | `storage` | `0x0F4000` | 1 MiB |
MCUboot uses **swap-using-move**: slot 0 and slot 1 must be identical in size
(976 KiB). No scratch partition is required.
---
## Signing Key
The build uses `secrets/dfu_signing_dev.key` (ECDSA P-256) by default,
configured in `projects/platforms/zephyr.cmake`.
### Check an existing key
```bash
openssl pkey -in secrets/dfu_signing_dev.key -text -noout | head -5
# ECDSA P-256: "Public Key Algorithm: id-ecPublicKey" + "ASN1 OID: prime256v1"
# RSA-2048: "Public-Key: (2048 bit)"
# Ed25519: "Public Key Algorithm: ED25519"
```
### Generate a new key
```bash
# ECDSA P-256 — recommended (fast on Cortex-M4, matches mcuboot.conf default)
imgtool keygen -k secrets/dfu_signing_prod.pem -t ecdsa-p256
# Ed25519 — smallest MCUboot footprint
imgtool keygen -k secrets/dfu_signing_prod.pem -t ed25519
# RSA-2048 — most conservative choice
imgtool keygen -k secrets/dfu_signing_prod.pem -t rsa-2048
```
> ⚠️ Never commit private keys. Ensure `secrets/*.pem` and `secrets/*.key`
> are in `.gitignore`.
### Switch to a different key
Update two places:
1. **`projects/platforms/zephyr.cmake`** — app signing key:
```cmake
set(MCUBOOT_SIGNATURE_KEY_FILE
"${CMAKE_CURRENT_LIST_DIR}/../../secrets/dfu_signing_prod.pem"
CACHE STRING "MCUboot signing key" FORCE)
```
2. **MCUboot build** — pass the new key via `-DCONFIG_BOOT_SIGNATURE_KEY_FILE`
and update `CONFIG_BOOT_SIGNATURE_TYPE_*` in `ports/zephyr/mcuboot.conf`
if the algorithm changes:
| Option | Algorithm |
|--------|-----------|
| `CONFIG_BOOT_SIGNATURE_TYPE_ECDSA_P256=y` | ECDSA P-256 (default) |
| `CONFIG_BOOT_SIGNATURE_TYPE_ED25519=y` | Ed25519 |
| `CONFIG_BOOT_SIGNATURE_TYPE_RSA=y` | RSA-2048 |
Rebuild and reflash MCUboot after any key or algorithm change — the old
bootloader rejects images signed with a different key.
---
## Legacy Build (nRF5 SDK)
Standalone build without Zephyr or MCUboot, targeting the nRF5 SDK directly.
### Build
#### CMake
```bash
cmake -S . -B build -DTARGET_PLATFORM=madi_nrf52840
cmake --build build
```
Build outputs: `build/madi.elf`, `build/madi.hex`, `build/madi.bin`
#### Make
```bash
make
```
### Flash
#### CMake targets
```bash
cmake --build build --target flash # nrfjprog
cmake --build build --target flash_usb # dfu-util
cmake --build build --target flash_softdevice
```
### Tests
#### Make
```bash
make test
make coverage
```
---
## Board Notes
- **Board name**: `madi_nrf52840`
- **Console**: RTT (Segger J-Link) — logging and shell via RTT
- **USB**: USB CDC ACM — mcumgr SMP transport (`/dev/ttyACM0` on Linux, `/dev/tty.usbmodem*` on macOS)
- **UART0**: TX=P0.23, RX=P0.25 — disabled (pinctrl defined but not in use)
- **LED**: P0.20 (active-low, alias `led0`)
- **QSPI flash**: CS=P0.07, SCK=P0.08, IO0=P1.09, IO1=P1.08, IO2=P0.12, IO3=P0.06