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.overlayoverrides 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.confforces 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.confshrinks 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=yto 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.confforcesCONFIG_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 viaSerialCompanionTransport.c(a drop-inzephcore_ble_*provider, auto-selected becauseCONFIG_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_ENABLEDauto-resolves on top;ZephyrRNGfurther 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 withwest 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:
- Create directory:
boards/<platform>/<board_name>/Platform folders: nrf52840, nrf54l, mg24, esp32, stm32wl - Copy board.conf and board.overlay from THIS directory
- Uncomment the sections matching your platform
- Fill in YOUR pin numbers and partition layout
- Add board detection to CMakeLists.txt (platform detection block, ~line 270):
Add
BOARD MATCHES "your_board"to the correct platform line - 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_LOWfor the two user LEDs (P1.3 / P1.4). On hardware here they are active-high — the overlay overridesgreen_led/blue_ledtoGPIO_ACTIVE_HIGH. - Aliases: Overlay sets
led0→green (heartbeat),led1→blue (unread + LoRa TX blink vialora-tx-led). On repeaters, firmware does not drive the second LED as “unread”; blue stays TX-only.