Files
2026-08-23 09:45:14 +02:00

23 KiB

ZephCore — Adding a New Board

Supported Boards

nRF52840

Board Build string Flash
RAK4631 west build -b rak4631 zephcore UF2 drag-drop or west flash
RAK3401 1W west build -b rak3401_1watt zephcore UF2 drag-drop or west flash
Wio Tracker L1 west build -b wio_tracker_l1 zephcore UF2 drag-drop or west flash
SenseCAP Solar west build -b sensecap_solar zephcore UF2 drag-drop or west flash
XIAO nRF52840 west build -b xiao_nrf52840 zephcore UF2 drag-drop or west flash
ProMicro SX1262 west build -b promicro_sx1262 zephcore UF2 drag-drop or west flash
T1000-E west build -b t1000_e zephcore UF2 drag-drop or west flash
SenseCAP MeshTracker X1 west build -b meshtracker_x1 zephcore UF2 drag-drop or west flash
ThinkNode M1 west build -b thinknode_m1 zephcore UF2 drag-drop or west flash
ThinkNode M3 west build -b thinknode_m3 zephcore UF2 drag-drop or west flash
ThinkNode M6 west build -b thinknode_m6 zephcore UF2 drag-drop or west flash
RAK WisMesh Tag west build -b rak_wismesh_tag zephcore UF2 drag-drop or west flash
Ikoka Nano 30dBm west build -b ikoka_nano_30dbm zephcore UF2 drag-drop
GAT562 30S Mesh Kit west build -b gat562_30s zephcore UF2 drag-drop or west flash
LilyGo T-Echo west build -b lilygo_techo zephcore UF2 drag-drop or west flash
LilyGo T-Impulse Plus west build -b lilygo_timpulse_plus zephcore UF2 drag-drop or west flash
Heltec T114 west build -b heltec_t114 zephcore UF2 drag-drop or west flash
Heltec Mesh Node T096 west build -b heltec_t096 zephcore UF2 drag-drop or west flash
muzi works R1 Neo west build -b muziworks_r1neo zephcore UF2 drag-drop or west flash

Heltec T114 screenless: append boards/nrf52840/heltec_t114/no_display.conf to EXTRA_CONF_FILE for units without the TFT module.

UF2 flash: Double-tap reset button, drag build/zephyr/zephyr.uf2 to the USB drive. SWD flash: west flash (requires J-Link, pyocd, or nrfjprog connected).

ESP32

Board Build string Flash
XIAO ESP32-C3 west build -b xiao_esp32c3 zephcore west flash
XIAO ESP32-C6 west build -b xiao_esp32c6/esp32c6/hpcore zephcore west flash
LilyGo TLoRa C6 west build -b lilygo_tlora_c6/esp32c6/hpcore zephcore west flash
LilyGo T3S3 (SX1262) west build -b lilygo_t3s3/esp32s3/procpu zephcore west flash
XIAO ESP32-S3 west build -b xiao_esp32s3/esp32s3/procpu zephcore west flash
Station G2 west build -b station_g2/esp32s3/procpu zephcore west flash
Heltec V3 west build -b heltec_wifi_lora32_v3/esp32s3/procpu zephcore west flash
Heltec V4.2 (GC1109 PA) west build -b heltec_wifi_lora32_v4/esp32s3/procpu zephcore west flash
Heltec V4.3 (KCT8103L PA) west build -b heltec_wifi_lora32_v43/esp32s3/procpu zephcore west flash
Heltec Wireless Tracker V1.1 west build -b heltec_wireless_tracker/esp32s3/procpu zephcore west flash
Heltec Wireless Tracker V2 west build -b heltec_wireless_tracker_v2/esp32s3/procpu zephcore west flash
LilyGo T-Beam v1.2 west build -b ttgo_tbeam/esp32/procpu zephcore west flash
ThinkNode M9 west build -b thinknode_m9/esp32s3/procpu zephcore west flash

Heltec V3 console: ZephCore routes console/shell to uart0 on V3. Use the UART serial port for boot logs and CLI.

Heltec V4.2 vs V4.3: The hardware revision is printed on the PCB silkscreen. If unclear, check GPIO2's default pull: the V4.2 GC1109 PA has an internal pull-down (GPIO2 reads LOW at boot), while the V4.3 KCT8103L PA has an internal pull-up (GPIO2 reads HIGH). Only difference in firmware: TX control pin GPIO46→GPIO5.

Heltec Wireless Tracker V1.1 vs V2: use heltec_wireless_tracker for the upstream Zephyr V1.1 target and heltec_wireless_tracker_v2 for the ESP32-S3FN8 V2 board with KCT8103L PA/FEM. The V2 pin map is documented in boards/esp32/heltec_wireless_tracker_v2/README.md.

LilyGo T-Beam v1.2: Classic ESP32 (PICO-D4) board — several caveats apply:

  • Upstream Zephyr DTS models the SX1276 variant; board.overlay overrides the radio node to SX1262 on the same SPI3 wiring.
  • PICO-D4 rev 1.0 bootloops when the bootloader tries to enable QIO flash mode. board.conf forces DIO (CONFIG_ESPTOOLPY_FLASHMODE_DIO=y). Any new classic ESP32 board with PICO-D4 needs this.
  • Classic ESP32 DRAM is much smaller than ESP32-S3. board.conf shrinks contacts/channels/queue (MAX_CONTACTS=160, MAX_CHANNELS=8, OFFLINE_QUEUE_SIZE=128) to fit.
  • AXP2101 PMU manages LoRa and GPS power rails via Zephyr regulator + fuel-gauge drivers (auto-enabled from the upstream DTS PMU nodes). Add CONFIG_FUEL_GAUGE=y to boards that expose battery state-of-charge over I2C.
  • Console/CLI are on uart0 (onboard USB-UART). No native USB on classic ESP32.

ESP32 flash uses esptool over USB via west flash. Hold BOOT button if the device doesn't enter download mode automatically.

Zephyr's CONFIG_ESP_SIMPLE_BOOT is active by default (no MCUBoot). The build produces a self-contained zephyr.bin that the ESP32 ROM bootloader loads directly from 0x0 — no special first-flash procedure, works on a bare chip.

Exception — WiFi AP OTA (boards/common/wifi_ota.conf): the HTTP OTA updater writes firmware to MCUBoot slot1 and requires MCUBoot. Add --sysbuild when building with wifi_ota.conf, and run west flash once to seat MCUBoot before OTA works:

west build -b <board>/esp32s3/procpu zephcore --pristine --sysbuild -- \
  -DEXTRA_CONF_FILE="boards/common/wifi_ota.conf"
west flash --esp-device COMX

GitHub Release downloads for S3/C-series boards are always MCUboot-based, unlike the plain-build default above: build.sh builds those boards with --sysbuild unconditionally, so the release always has MCUboot @ 0x0 + signed app @ 0x20000. Only -merged.bin is published for them — flash that one, at offset 0x0, for both first flash and updates. It does not touch storage_partition/lfs_partition (identity, prefs, contacts, BLE bonds), so routine updates preserve device state. Classic ESP32 boards (T-Beam, PICO-D4) still publish a self-contained plain .bin for 0x1000, since they use simple-boot in the release build too.

SX127x Boards (loramac-node backend)

ZephCore can drive SX1272/SX1276/SX1278 via the loramac-node backend — a separate radio path from the native SX126x driver used by all other boards. The TTGO LoRa32 is the only board exercising it.

This path is source-only and unsupported. No release publishes firmware for it, it is not in build.sh or the Mesh America catalog, and it has no RX duty cycle and no RX gain boost. Treat it as a starting point to maintain yourself, not as a supported target.

Board Build string Flash
TTGO LoRa32 west build -b ttgo_lora32/esp32/procpu zephcore west flash

SX127x boards require these board.conf overrides (the zephcore_common.conf default enables the native SX126x path):

CONFIG_LORA_MODULE_BACKEND_NATIVE=n
CONFIG_LORA_MODULE_BACKEND_LORAMAC_NODE=y
CONFIG_ZEPHCORE_RADIO_SX127X=y
CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE=n   # lora_recv_duty_cycle not implemented for SX127x
CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=17 # PA_BOOST max without external PA

Classic ESP32 + PICO-D4 also need the DIO flash mode fix (see T-Beam note above).

One-time setup required (all ESP32 boards):

# Download Espressif BLE controller blobs (closed-source, required for BLE)
west blobs fetch hal_espressif

Run once after west init/west update. Without it, CMake aborts with a blob validation error. Re-run after any west update that bumps the hal_espressif revision.

nRF54L15

Board Build string Flash
XIAO nRF54L15 west build -b xiao_nrf54l15/nrf54l15/cpuapp zephcore --no-sysbuild west flash
MinewSemi ME25LS02 west build -b me25ls02/nrf54l15/cpuapp zephcore --no-sysbuild SWD (west flash)

Requires J-Link or CMSIS-DAP (built into XIAO board via SAMD11 bridge). The --no-sysbuild flag is required (no MCUboot support yet).

The SoC has no USB peripheral at all, so neither board has a UF2 or DFU path — zephyr.hex links at RRAM base 0x0 and is the complete image, written over SWD. On the ME25LS02's MX25LE02 carrier the USB-C port is a CH340x UART bridge (console only), so it needs an external probe.

MG24 (Silicon Labs)

Board Build string Flash
XIAO MG24 west build -b xiao_mg24 zephcore west flash

First-time setup required:

# Download Silicon Labs BLE controller blob
west blobs fetch hal_silabs

# Install pyocd + Silicon Labs device pack (if using pyocd)
pip install pyocd
pyocd pack install EFR32MG24B220F1536IM48

Flash with: west flash --runner pyocd

STM32WL (Seeed LoRa-E5)

Board Build string Flash
LoRa-E5 mini west build -b lora_e5_mini zephcore west flash

The STM32WLE5JC integrates an SX1262-class sub-GHz radio (driven by the native SX126x driver via its STM32WL HAL over the internal subghzspi bus). RF front-end, RF switch (PA4/PA5), TCXO and the +22 dBm RFO_HP PA are described in upstream zephyr/dts/arm/seeed_studio/lora-e5.dtsi.

STM32WL caveats — different from every other ZephCore platform:

  • No Bluetooth and no USB device. boards/common/stm32wl_common.conf forces CONFIG_BT=n. The console/CLI and the companion protocol both run over USART1, bridged to USB-C by the onboard USB-UART chip.
  • Repeater uses the USART CLI (add repeater.conf). The companion speaks MeshCore serial framing over the same UART via SerialCompanionTransport.c (a drop-in zephcore_ble_* provider, auto-selected because CONFIG_BT=n) — no BLE pairing, the official serial client connects directly.
  • RAM-bound, not flash-bound: 64KB SRAM. The companion's contact/queue arrays are capped hard in board.conf (MAX_CONTACTS=24, OFFLINE_QUEUE_SIZE=8). AES tables live in ROM (MBEDTLS_AES_ROM_TABLES) to reclaim ~8KB SRAM.
  • TRNG only (no HW CSPRNG): the STM32 TRNG is enabled as the entropy source and CSPRNG_ENABLED auto-resolves on top; ZephyrRNG further conditions identity seeds with AES-CTR (the timing stages are skipped — SysTick has no independent slow clock — so the TRNG-fed CSPRNG stages carry the seed).
  • No MCUboot / UF2: single app partition at flash origin + a LittleFS volume (see board.overlay). Flash over SWD/ST-Link with west flash (OpenOCD).

Build the repeater:

west build -b lora_e5_mini zephcore -- -DEXTRA_CONF_FILE="boards/common/repeater.conf"

Building for Repeater Role

Append -- -DEXTRA_CONF_FILE="boards/common/repeater.conf" to any build command:

west build -b rak4631 zephcore -- -DEXTRA_CONF_FILE="boards/common/repeater.conf"

Building for Room Server Role

Append -- -DEXTRA_CONF_FILE="boards/common/room_server.conf" to any build command:

west build -b rak4631 zephcore -- -DEXTRA_CONF_FILE="boards/common/room_server.conf"

Production vs Debug Builds

Production (no logging, no asserts, reboot-on-fatal) is the default — no extra conf needed. To enable logging, add the debug overlay:

west build -b rak4631 zephcore -- -DEXTRA_CONF_FILE="boards/common/debug.conf"

Repeater + Debug

west build -b rak4631 zephcore -- -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/debug.conf"

All build commands should include --pristine when switching between roles or boards.

Adding a New Board

There are TWO ways to add a board, depending on whether Zephyr already has a board definition for your hardware.

PATTERN 1: Existing Zephyr Board (overlay only)

Use this when your board already exists in Zephyr's tree. You only need TWO files: board.conf + board.overlay

Examples: XIAO nRF54L15, XIAO MG24, XIAO ESP32-C3, RAK4631

Directory structure:

zephcore/boards/<platform>/<board_name>/
  board.conf       — Kconfig (name, radio type)
  board.overlay    — DT overlay (LoRa SPI, partitions, peripherals)

Steps:

  1. Create directory: boards/<platform>/<board_name>/ Platform folders: nrf52840, nrf54l, mg24, esp32, stm32wl
  2. Copy board.conf and board.overlay from THIS directory
  3. Uncomment the sections matching your platform
  4. Fill in YOUR pin numbers and partition layout
  5. Add board detection to CMakeLists.txt (platform detection block, ~line 270): Add BOARD MATCHES "your_board" to the correct platform line
  6. Build and iterate!

PATTERN 2: Fully Custom Board (new DTS)

Use this when your board does NOT exist in Zephyr's tree. You need a full board definition: .dts, pinctrl, Kconfig, etc.

Examples: Ikoka Nano 30dBm, ThinkNode M1 (custom nRF52840 designs)

Look at existing custom boards as templates:

zephcore/boards/nrf52840/ikoka_nano_30dbm/   — minimal (LoRa only)
zephcore/boards/nrf52840/thinknode_m1/       — full-featured (EPD, GPS, QSPI, buzzer)

A full custom board includes:

board.conf                           — Kconfig (name, radio, overrides)
board.overlay                        — DT overlay (usually empty if DTS is complete)
<board>_<soc>.dts                    — Full device tree
<board>_<soc>-pinctrl.dtsi           — Pin control definitions
board.yml                            — Board metadata (name, arch, SoC)
Kconfig.<board>                      — SoC selection
<board>_<soc>_defconfig              — Minimal defconfig
board.cmake                          — Flash runner config

Critical: zephyr,sram in the chosen node (nRF52840 only)

The nRF52840 SoC DTSI defines sram0 but does NOT set zephyr,sram in the chosen node. Without it, the linker gets RAM size = 0 and every build fails with "region 'RAM' overflowed" regardless of actual RAM use.

Boards that include <nordic/nrf52840_partition.dtsi> get this for free. Boards that use nrf52_partitions_sdv6.dtsi or nrf52_partitions_sdv7.dtsi directly (without the upstream include) also get it now — those DTSIs set zephyr,sram = &sram0 themselves.

If you write a fully custom DTS that includes neither, add it yourself:

chosen {
    zephyr,sram = &sram0;
    zephyr,code-partition = &code_partition;
    ...
};

nRF52 boards with a user button: button wakeup from System OFF

sys_poweroff() on nRF52840 enters System OFF (~1µA). To allow waking via button press (instead of only via USB/charger), GPIO SENSE bits must be set before poweroff. This is done via the wakeup-source property on the gpio-keys node, which the nRF52 GPIO driver handles automatically.

Boards that define a buttons: gpio-keys node must add at the END of their DTS:

#include "../../common/nrf52_wakeup.dtsi"

Boards WITHOUT a buttons label (ikoka_nano) must NOT include it — referencing an undefined &buttons label is a hard build error.

RAK4631 user button: gpio-keys on P0.09 (NFC1). Short → next page (after tap window), double-tap → previous page, long press (≥1s) → enter / activate.

What Goes in board.conf

Most hardware features are auto-detected from devicetree. board.conf should ONLY contain settings that can't be inferred from hardware:

REQUIRED (all boards): CONFIG_ZEPHCORE_BOARD_NAME Human-readable name CONFIG_BT_DIS_MODEL_NUMBER_STR BLE Device Information model

REQUIRED (nRF52 only): CONFIG_ZEPHCORE_SD_FWID SoftDevice firmware ID (0x00B6 or 0x0123)

OPTIONAL (only if needed): CONFIG_ZEPHCORE_RADIO_LR1110 LR1110 radio (auto-selects SPI) CONFIG_ZEPHCORE_MAX_CONTACTS Override for RAM-limited boards CONFIG_HEAP_MEM_POOL_SIZE Override for large displays (>128x64) CONFIG_SEGGER_RTT_BUFFER_SIZE_UP Shrink RTT on RAM-tight boards CONFIG_ESPTOOLPY_FLASHSIZE_16MB ESP32 boards with 16MB flash CONFIG_ESPTOOLPY_FLASHMODE_DIO=y Classic ESP32 PICO-D4 boards (bootloops otherwise) CONFIG_ESPTOOLPY_FLASHMODE_QIO=n Companion to the DIO override above CONFIG_FUEL_GAUGE=y Boards with AXP2101 or other I2C fuel gauge CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM Boards with external PA CONFIG_ZEPHCORE_MAX_TX_POWER_DBM Boards with external PA

AUTO-DETECTED (do NOT set in board.conf): CONFIG_PWM Auto from DT buzzer nodelabel CONFIG_ZEPHCORE_UI_BUZZER Auto from DT buzzer nodelabel CONFIG_ZEPHCORE_UI_DISPLAY Auto from DT zephyr,display chosen CONFIG_SPI Auto from ZEPHCORE_RADIO_LR1110 CONFIG_NORDIC_QSPI_NOR Auto from DT nordic,qspi-nor node CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE OFF by default for all roles (boot default only; runtime toggle via CLI "set rxduty on/off") CONFIG_ESP_SPIRAM Auto: ON when DT psram0 size > 0 (ESP32-S/C5) CONFIG_ESP_SPIRAM_SIZE Auto: read from DT psram0 size by upstream Kconfig

NOT auto-detected (must set in board.conf for ESP32-S3 R8 boards): CONFIG_SPIRAM_MODE_OCT R8 chips (8 MB OPI octal) need this set to "y" in board.conf. R2 chips (2 MB QSPI quad) need nothing — QUAD is the upstream default. See "ESP32 PSRAM" section below.

ESP32 PSRAM (auto-enable, per-board mode on S3 R8)

CONFIG_ESP_SPIRAM=y is enabled automatically from devicetree by zephcore/Kconfig.psram. As long as a board's DTS includes a Zephyr WROOM dtsi with an R-suffix (e.g. esp32s3_wroom_n16r2.dtsi, esp32s3_wroom_n8r8.dtsi), PSRAM lights up — no board.conf entry needed for the basics. Boards without PSRAM use a non-R-suffix dtsi (e.g. esp32s3_wroom_n8), which leaves psram0 at size = 0, so PSRAM stays off. ESP32-C3/C6 are outside Espressif's PSRAM Kconfig gate entirely — no effect.

Mode selection is silicon-strapped, but not auto-pickable here: upstream Kconfig.spiram sets an unconditional default SPIRAM_MODE_QUAD on the choice, which wins over any conditional override we'd add downstream. So:

Chip suffix PSRAM size Mode board.conf needs
R2 2 MB QSPI quad nothing — upstream default
R8 8 MB OPI octal CONFIG_SPIRAM_MODE_OCT=y

If you add a new ESP32-S3 R8 board (e.g. esp32s3_wroom_n*r8.dtsi), add a one-liner to its board.conf:

# PSRAM mode — ESP32-S3R8 is silicon-strapped OPI octal.
# Enable is auto-detected from DTS by Kconfig.psram; only the mode is set here.
CONFIG_SPIRAM_MODE_OCT=y

For aggressive DRAM relief beyond the heap rebalance, additional Kconfigs CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y and CONFIG_SPIRAM_RODATA=y move text/rodata into PSRAM at boot, but introduce PSRAM-cache-miss timing variability — only enable per-board after verifying radio and BLE paths still meet their timing budgets.

Config Inheritance

prj.conf                           Always loaded first
  |
zephcore_common.conf               BLE, storage, input, LoRa, crypto, sensors
  |
<platform>_common.conf             Platform-specific overrides only
  |                                  nrf52_common.conf   — UF2, USB CDC, DLE, RTT
  |                                  nrf54l_common.conf  — DLE, RTT
  |                                  mg24_common.conf    — SiLabs blob stacks, heap
  |                                  esp32_common.conf   — Espressif blob stacks, heap
  |                                  stm32wl_common.conf — BT off, UART companion/CLI
  |
board.conf                         Board name, radio type, board-specific

DO NOT duplicate settings from parent configs in board.conf!

Quick Reference: Wio-SX1262 XIAO Pin Mapping

All XIAO boards use the same D-pin assignment for Wio-SX1262:

Signal | XIAO Pin | nRF52840  | nRF54L15  | MG24      | ESP32-C3  | ESP32-C6  | ESP32-S3
-------|----------|-----------|-----------|-----------|-----------|-----------|----------
DIO1   | D1       | P0.03     | P1.05     | PC01      | GPIO3     | GPIO1     | GPIO39
RESET  | D2       | P0.28     | P1.06     | PC02      | GPIO4     | GPIO2     | GPIO42
BUSY   | D3       | P0.05     | P1.07     | PC03      | GPIO5     | GPIO21    | GPIO40
NSS    | D4       | P0.04     | P1.10     | PC04      | GPIO6     | GPIO22    | GPIO41
RXEN   | D5       | P0.29     | P1.11     | PC05      | GPIO7     | GPIO23    | GPIO38
SCK    | D8       | P1.13     | P2.01     | PA03      | GPIO8     | GPIO19    | GPIO7
MISO   | D9       | P1.14     | P2.04     | PA04      | GPIO9     | GPIO20    | GPIO8
MOSI   | D10      | P1.15     | P2.02     | PA05      | GPIO10    | GPIO18    | GPIO9

Note: nRF52840 D-pin mapping varies by board (XIAO nRF52840 shown). RAK4631 has SX1262 integrated — different pinout entirely.

RAK4631 LEDs and overlay (RAK4631)

  • Polarity: Stock Zephyr RAK4631 DTS uses GPIO_ACTIVE_LOW for the two user LEDs (P1.3 / P1.4). On hardware here they are active-high — the overlay overrides green_led / blue_led to GPIO_ACTIVE_HIGH.
  • Aliases: Overlay sets led0→green (heartbeat), led1→blue (unread + LoRa TX blink via lora-tx-led). On repeaters, firmware does not drive the second LED as “unread”; blue stays TX-only.