update example board and porting guide

This commit is contained in:
liquidraver
2026-10-05 10:24:36 +02:00
parent 158df925ff
commit d5bb275e01
8 changed files with 1523 additions and 1341 deletions
+2 -1
View File
@@ -1024,7 +1024,8 @@ New drivers in `patches/zephyr-new/` (LR11xx, LR20xx, native-Linux SPI/GPIO, DTS
## 10. Board Matrix
Build strings and flash methods: `boards/supported_boards.md` and `boards/example_board/README.md`.
Build strings: `docs/supported_boards.md`. Flash methods: `docs/BUILDING.md`. Adding a board:
`zephcore/boards/example_board/README.md`.
| Board | SoC | Radio | GPS | Display | Notable extras |
|-------|-----|-------|-----|---------|----------------|
+2 -2
View File
@@ -708,8 +708,8 @@ config ZEPHCORE_SD_FWID
Only applicable to nRF52 boards with Adafruit bootloader.
Common values:
0x00B6 - S140 v6.1.1 (RAK4631, Wio Tracker, T1000-E)
0x0123 - S140 v7.3.0 (XIAO nRF52840, Ikoka Nano)
0x00B6 - S140 v6.1.1 (RAK4631, ThinkNode M1, Heltec T114)
0x0123 - S140 v7.3.0 (XIAO nRF52840, Wio Tracker L1, T1000-E)
config ZEPHCORE_VBAT_MV_MULTIPLIER
int "Battery voltage ADC multiplier (fallback)"
+4 -3
View File
@@ -4,8 +4,9 @@
* NOTE: On nRF52, this property is currently inert — the nRF GPIO driver
* does not implement GPIO_INT_WAKEUP, so wakeup-source alone does not set
* the GPIO SENSE bits required for System OFF wakeup. Actual SENSE
* configuration is done explicitly in action_deep_sleep() (ui_task.c)
* via nrf_gpio_cfg_sense_input() right before sys_poweroff().
* configuration is done explicitly for the sw0 alias in
* adapters/board/zephyr_poweroff.c via nrf_gpio_cfg_sense_input() right
* before sys_poweroff().
*
* Kept here so that:
* 1. If a future Zephyr nRF GPIO driver gains GPIO_INT_WAKEUP support,
@@ -15,7 +16,7 @@
* on other platforms where the driver does honour GPIO_INT_WAKEUP.
*
* Include this in any nRF52 board DTS that defines a 'buttons' gpio-keys node.
* Boards without buttons (e.g. ikoka_nano, rak4631) should NOT include this.
* Boards without buttons (e.g. ikoka_nano, rak3401_1watt) should NOT include this.
*
* SPDX-License-Identifier: MIT
*/
+247 -449
View File
@@ -1,476 +1,274 @@
ZephCore — Adding a New Board
=============================
# ZephCore: Adding a Board
This directory is the template for a new board. It is not a buildable board.
Supported Boards
----------------
| File | Copy it when | What it is |
|---|---|---|
| [`board.conf`](board.conf) | always | Kconfig that cannot be derived from devicetree: name, SoftDevice ID, radio type |
| [`board.overlay`](board.overlay) | always, unless a custom `.dts` carries everything | Devicetree: radio, flash layout, console, peripherals. Every example is an `#if 0` block taken from a real board |
| [`partitions.overlay`](partitions.overlay) | ESP32-S3, ESP32-C3, ESP32-C6 | The flash map, shared by the app and MCUboot |
| [`zephcore.yml`](zephcore.yml) | always | The board manifest: registers the board for releases, the catalog and the docs |
Looking for something else?
- **Which boards exist, and their build strings:** [docs/supported_boards.md](../../../docs/supported_boards.md)
(generated from the manifests).
- **Building, roles, flashing, the config chain:** [docs/BUILDING.md](../../../docs/BUILDING.md).
- **Native Linux presets** (not boards): [docs/LINUX_NATIVE.md](../../../docs/LINUX_NATIVE.md).
## 1. Two patterns
**Pattern 1: the board already exists in Zephyr.** You add three or four files and no board definition.
```
zephcore/boards/<platform>/<board>/
board.conf
board.overlay
partitions.overlay ESP32-S3 / C3 / C6 only
zephcore.yml
```
References: `nrf52840/rak4631`, `nrf52840/wio_tracker_l1`, `nrf54l/xiao_nrf54l15`, `mg24/xiao_mg24`,
`esp32/xiao_esp32c3`, `esp32/xiao_esp32s3`, `esp32/heltec_wifi_lora32_v3`, `esp32/ttgo_tbeam` (classic ESP32),
`stm32wl/lora_e5_mini`.
**Pattern 2: a custom board.** The same directory also holds a complete Zephyr board definition:
| File | Purpose |
|---|---|
| `board.yml` | Board name, vendor, SoC |
| `Kconfig.<board>` | Selects the SoC |
| `<board>[_<soc>[_<cluster>]].dts` | The devicetree. Named after the build target, as Zephyr expects; copy a sibling's naming |
| `<board>-pinctrl.dtsi` | Pin control |
| `<board>_defconfig` | Minimal defconfig |
| `board.cmake` | Flash runners (optional: a UF2-only board needs none) |
| `pre_dt_board.cmake`, `Kconfig.defconfig`, `Kconfig` | Only when needed: DTC warning flags, SoC defaults, extra heap |
| `<board>.yaml` | Twister metadata, optional |
| `battery_curve.c` | Optional per-board battery discharge curve, compiled automatically when present |
With a custom `.dts` the hardware can be described there entirely, and `board.overlay` is then optional.
References: `nrf52840/ikoka_nano_30dbm` (minimal, radio only), `nrf52840/thinknode_m1` (e-paper, GPS, QSPI,
buzzer, two buttons), `nrf52840/t1000_e` (LR1110), `esp32/lilygo_t3s3` (ESP32-S3), `nrf54l/me25ls02` (nRF54L15).
Two rules hold for both patterns:
- **The directory name is the board name**, the first component of the `west build -b` string.
- **The parent directory is the platform.** It selects `<platform>_common.conf`; there is nothing to register in
`CMakeLists.txt`. A board name may exist under one platform directory only (the build stops otherwise).
| Directory | Platform conf | SoCs |
|---|---|---|
| `boards/nrf52840/` | `nrf52_common.conf` | nRF52840 with the Adafruit UF2 bootloader |
| `boards/nrf54l/` | `nrf54l_common.conf` | nRF54L15, nRF54LM20A |
| `boards/esp32/` | `esp32_common.conf` (+ `esp32c3s3_common.conf` on S3 and C3) | ESP32, ESP32-S3, ESP32-C3, ESP32-C6 |
| `boards/mg24/` | `mg24_common.conf` | EFR32MG24 |
| `boards/stm32wl/` | `stm32wl_common.conf` | STM32WL |
## 2. Steps
1. **Collect the facts first:** the schematic, and the board's Arduino MeshCore variant (`variants/<board>/`)
if there is one. The variant gives the pin map, the SoftDevice version (nRF52) and the name MeshCore uses.
Where a vendor annotation and the pin map disagree, trust the pin map.
2. **Create `boards/<platform>/<board>/`** and copy the template files into it.
3. **Edit `board.overlay`:** keep the examples for your platform, remove the `#if 0` / `#endif` lines around
them, delete the rest, and change every pin. The minimum is a radio node, a flash layout and a console.
4. **Edit `board.conf`:** the two name strings, plus `CONFIG_ZEPHCORE_SD_FWID` on nRF52 and the radio type if
the radio is not an SX126x.
5. **Edit `zephcore.yml`:** set `target`, and leave `release:` out for now.
6. **Build** the companion, then the repeater. Always `--pristine` when changing board or role.
```bash
west build -b <board> zephcore --pristine
```
```bash
west build -b <board> zephcore --pristine -- -DEXTRA_CONF_FILE="boards/common/repeater.conf"
```
nRF54L adds `--no-sysbuild`. An ESP32-S3 / C3 / C6 **repeater** adds `--sysbuild` (section 5). ESP32 and
MG24 need their radio blobs once: `west blobs fetch hal_espressif` / `west blobs fetch hal_silabs`.
7. **Read back what was built.** CMake prints a `ZephCore config hierarchy` block with the board directory,
platform, and the final conf and overlay lists: check that your files are in it. Then look at
`build/zephyr/zephyr.dts` for the radio node, the `chosen` entries and the partition map.
8. **Validate on hardware**, then register the board for release (section 6).
## 3. `board.conf`
Required on every board:
| Symbol | Meaning |
|---|---|
| `CONFIG_ZEPHCORE_BOARD_NAME` | Name shown in the app and by the `board` command; 39 characters at most |
| `CONFIG_BT_DIS_MODEL_NUMBER_STR` | BLE Device Information model string |
| `CONFIG_ZEPHCORE_SD_FWID` | nRF52 only: `0x00B6` (s140 v6) or `0x0123` (s140 v7), matching the partition include |
Set only when the hardware calls for it (details and the values used in the tree are in the template):
| Symbol | When |
|---|---|
| `CONFIG_ZEPHCORE_RADIO_LR1110` / `_LR2021` | LR11xx or LR2021 radio. SX126x, LLCC68 and STM32WL need nothing |
| `CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM`, `_MAX_TX_POWER_DBM` | External PA: keep the chip below the PA's maximum input |
| `CONFIG_ZEPHCORE_MAX_CONTACTS`, `_MAX_CHANNELS`, `_OFFLINE_QUEUE_SIZE` | RAM-bound boards (ESP32 without PSRAM, classic ESP32, STM32WL) |
| `CONFIG_HEAP_MEM_POOL_SIZE` | nRF with a panel larger than 128x64 |
| `CONFIG_ESPTOOLPY_FLASHSIZE_8MB` / `_16MB` | ESP32 with more than 4 MB flash |
| `CONFIG_SPIRAM_MODE_OCT` | ESP32-S3 with 8 MB octal PSRAM (R8) |
| `CONFIG_ESPTOOLPY_FLASHMODE_DIO` (+ `_QIO=n`) | Classic ESP32 PICO-D4 |
Do not set what devicetree already decides (`ZEPHCORE_UI_DISPLAY`, `ZEPHCORE_UI_BUZZER`, `PWM`, `SPI`,
`NORDIC_QSPI_NOR`, `ESP_SPIRAM`), and do not put role or debug policy in a board: roles, logging and `BT=n`
come from `boards/common/*.conf`. `CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE` is off by default for every role and
is a user setting (`set rxduty`), not a board property.
The chain, later wins:
```
prj.conf → zephcore_common.conf → <platform>_common.conf → esp32c3s3_common.conf (S3/C3)
→ board.conf → auto-included feature confs → your EXTRA_CONF_FILE
```
Overlays follow the same order: `board.overlay` first, overlays paired with a conf after it, `partitions.overlay`
last ([ADR 0003](../../../docs/adr/0003-devicetree-overlay-precedence.md)). A `foo.overlay` next to a `foo.conf`
is paired automatically, which is how a board variant changes Kconfig and devicetree together
(`heltec_t114/no_display.conf` + `no_display.overlay`).
## 4. What the firmware reads from devicetree
Only `lora0` is required. Everything else enables its feature by being present.
| Devicetree | Effect |
|---|---|
| alias `lora0` | The radio, for example `semtech,sx1262`, `semtech,lr1110` or `semtech,lr2021` |
| `lfs_partition` + `filesystem.dtsi` | `/lfs`: identity, prefs, contacts, channels |
| chosen `zephyr,settings-partition` | NVS for BLE bonds (nRF52, ESP32). nRF54L and MG24 keep bonds in a file on `/lfs` |
| aliases `led0`, `led1`, `lora-tx-led` | Heartbeat, unread, TX blink |
| alias `sw0` | User button; wake source from nRF System OFF and ESP32 light sleep |
| chosen `zephyr,display` | Display UI |
| node labelled `buzzer` + alias `buzzer` | PWM buzzer |
| node labelled `gnss` | GPS |
| aliases `gps-enable`, `gps-wakeup`, `gps-reset`, `gps-resetb`, `gps-vrtc-enable`, `gps-sleep-int`, `gps-rtc-int`; chosen `zephcore,gps-power` | GPS power control |
| `zephyr,user { io-channels; vbat-mv-multiplier; }` | Battery voltage; a regulator labelled `vbat_enable` is switched per read |
| `sensors-i2c.dtsi`, `rtc-i2c.dtsi` | I2C sensors (probed at runtime) and a battery-backed RTC (opt-in) |
| `nordic,qspi-nor` + `qspi-ext.dtsi` | `/ext` on external flash for contacts, channels and blobs |
| chosen `zephcore,companion-uart` | Companion protocol on a UART, for boards without Bluetooth |
| `zephcore,poweroff-gpios` | Pins released at shutdown on boards with a latched supply rail |
`board.overlay` has a worked example for each row.
## 5. Platform notes and traps
### 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` |
| Wio Tracker L1 Pro 1W | `west build -b wio_tracker_l1_1w 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` |
| Heltec Mesh Node T1 | `west build -b heltec_t1 zephcore` | UF2 drag-drop or `west flash` |
| muzi works R1 Neo | `west build -b muziworks_r1neo zephcore` | UF2 drag-drop or `west flash` |
- **SoftDevice version decides two things together:** the partition include (`nrf52_partitions_sdv6.dtsi` or
`_sdv7.dtsi`) and `CONFIG_ZEPHCORE_SD_FWID`. The Arduino variant's linker script tells you which. The wrong
pair puts the app at the wrong address and the board bootloops with no USB.
- **Include the shared partition file; do not write the map by hand.** It keeps `/lfs` and the bond store at
the offsets every shipped ZephCore board uses, and it sets `zephyr,sram`, which the nRF52840 SoC file does
not (without it the linker reports `region 'RAM' overflowed` on every build).
- **No 32.768 kHz crystal:** set `&lfclk { k32src = "rc"; k32src-accuracy-ppm = <250>; };` in devicetree. The
old `CONFIG_CLOCK_CONTROL_NRF_K32SRC_*` choices are silently ignored
([ADR 0005](../../../docs/adr/0005-zephyr-main-pin.md)).
- **Wake from power-off** uses the `sw0` alias: `adapters/board/zephyr_poweroff.c` arms GPIO SENSE on that pin
before System OFF. `nrf52_wakeup.dtsi` documents the intent in devicetree; include it only when a node
labelled `buttons` exists, since a missing label is a build error.
- **QSPI flash:** declare it only if the chip is fitted, and disable an upstream `&qspi` the variant lacks.
- **SPIM0 / SPIM1 share their instance with TWIM0 / TWIM1.** Put the radio on `&spi2` or `&spi3`.
- **SSD1306 / SH1106 on a `nordic,nrf-twim` bus** needs `zephyr,concat-buf-size`, or the panel stays blank.
**Heltec T114 screenless:** append `boards/nrf52840/heltec_t114/no_display.conf` to `EXTRA_CONF_FILE` for units without the TFT module.
### nRF54L
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).
- Build with `--no-sysbuild`. The SoC has no USB peripheral: no UF2 and no DFU, `zephyr.hex` over SWD.
- Enable `&xo` and `&lfclk` if the board DTS does not; the BLE controller does not build without them.
- **Port P2 has no GPIOTE.** No GPIO interrupt works there: the radio IRQ cannot be on P2, and a button on P2
needs `polling-mode` on its `gpio-keys` node (`seeed_lr2021_evk`).
### 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 V4-R8 (KCT8103L PA, 8MB octal PSRAM) | `west build -b heltec_wifi_lora32_v4_r8/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` |
| Meshnology W12 (LR2021) | `west build -b meshnology_w12/esp32s3/procpu zephcore` | `west flash` |
- **Boot and flash layout** ([ADR 0001](../../../docs/adr/0001-esp32-boot-and-flash-layout.md)): a plain
companion build is simple boot at `0x0`. A repeater build on S3 / C3 / C6 pulls in WiFi OTA automatically
and **must be built with `--sysbuild`**; without it the board stops enumerating USB. Releases are always the
MCUboot `-merged.bin`. Classic ESP32 (`/esp32/` targets) is simple boot in every build and has no
`partitions.overlay`.
- **`partitions.overlay` is shared with MCUboot.** Read the template before changing a layout, including the
`_default_` versus `_amp_` base-table trap.
- **Flash size** defaults to 4 MB; state the real one in `board.conf` (it is copied to MCUboot).
- **ESP32-S3 native USB** ([ADR 0002](../../../docs/adr/0002-esp32s3-native-usb-companion.md)): including
`esp32s3_usb_otg.dtsi` from `board.overlay` gives companion builds a USB transport. esptool then cannot
auto-reset the board; use `start dfu` or a 1200-baud touch.
- **PSRAM** is enabled from devicetree when the board DTS uses an R-suffix module file. Only the mode needs
stating, because the upstream default cannot be overridden conditionally:
**Heltec V3 console:** ZephCore routes console/shell to `uart0` on V3. Use the UART serial port for boot logs and CLI.
| Part | PSRAM | `board.conf` |
|---|---|---|
| `R2` | 2 MB quad | nothing |
| `R8` | 8 MB octal | `CONFIG_SPIRAM_MODE_OCT=y` |
**Meshnology W12:** the only board here with an LR2021 *and* an external PA, and
the only ESP32 board whose TX power is capped well below the chip maximum —
4 dBm at the chip is ~30 dBm at the antenna through the GC1109 front end. It has
no USB-UART bridge, so esptool cannot auto-reset a companion build into download
mode; use `start dfu`, a 1200-baud touch, or hold BOOT. Full port notes and the
vendor schematic live in `devdocs/w12/`.
- **Manifest capabilities:** `wifi: true` gives the companion a WiFi transport and belongs only on boards with
the RAM for WiFi and BLE together ([ADR 0009](../../../docs/adr/0009-wifi-companion-boards.md)).
`light_sleep: true` makes repeaters light-sleep; the requirements are in `boards/common/pm_esp32.conf`, and
it is declared only after it was validated on the hardware. Either way, give NSS, RESET and every MCU-driven
RF switch or FEM line `ESP32_GPIO_SLEEP_HOLD_EN` from the start.
- **RAM, not flash, limits the companion.** Boards without PSRAM lower `MAX_CONTACTS`; the classic ESP32 also
trims channels and the offline queue.
- **Classic ESP32 PICO-D4** bootloops in QIO flash mode: `CONFIG_ESPTOOLPY_FLASHMODE_DIO=y`. Console and CLI
are on `uart0`.
- GPIO32 and up are on `&gpio1`: GPIO41 is `<&gpio1 9 ...>`.
**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.
### MG24
**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`.
- Erase blocks are 8 KB: partition sizes are multiples of 8 KB.
- Enable `&se` (secure element: crypto and the TRNG).
- On the XIAO the default I2C pins are the radio's NSS / RXEN; disable `&i2c0`.
**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.
### STM32WL
ESP32 flash uses esptool over USB via `west flash`. Hold BOOT button if the device
doesn't enter download mode automatically.
- No Bluetooth and no USB device. The companion protocol and the CLI both run on USART1; the board selects it
with the `zephcore,companion-uart` chosen node.
- 64 KB SRAM: the companion tables are cut hard in `board.conf`.
- `boards/stm32wl/lora_e5_mini/` is the only reference.
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.
### SX127x
Source-only and unsupported: a separate path through Zephyr's loramac-node backend with no RX duty cycle and
no RX boost. `boards/esp32/ttgo_lora32/` is the one board on it, and its `board.conf` shows the overrides.
## 6. Registering the board
`zephcore.yml` is the only registry. `build.sh` (and so CI and the release), the Mesh America catalog,
[docs/supported_boards.md](../../../docs/supported_boards.md) and the board count in the root README are all
generated from it. The schema is documented at the top of `zephcore/scripts/board_manifest.py`.
- **Bring-up:** `target` only. The board builds locally and is listed as source-only.
- **Validated:** add `release:` with the roles to publish.
- **In the configurator:** add `catalog:`. `device` must be MeshCore's exact device name for the board to fold
into their tile ([docs/PROVIDER_CATALOG.md](../../../docs/PROVIDER_CATALOG.md)).
After every manifest change:
**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:
```bash
west build -b <board>/esp32s3/procpu zephcore --pristine --sysbuild -- \
-DEXTRA_CONF_FILE="boards/common/wifi_ota.conf"
west flash --esp-device COMX
python zephcore/scripts/board_manifest.py check
```
**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):
```kconfig
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_async not implemented for SX127x
CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=17 # PA_BOOST max without external PA
```bash
python zephcore/scripts/board_manifest.py docs
```
Classic ESP32 + PICO-D4 also need the DIO flash mode fix (see T-Beam note above).
The notes under each list in `docs/supported_boards.md` are written by hand; add one if the board has
something a user must know before flashing.
**One-time setup required (all ESP32 boards):**
## Quick reference: Wio-SX1262 on a XIAO
```
# Download Espressif BLE controller blobs (closed-source, required for BLE)
west blobs fetch hal_espressif
```
Every XIAO carries the Wio-SX1262 on the same D pins:
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.
| 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 |
### 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`) |
| Semtech LR2021 LoRa Plus EVK | `west build -b seeed_lr2021_evk/nrf54l15/cpuapp zephcore --no-sysbuild` | `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 LR2021 EVK is the Seeed/Semtech kit (SKU 100039980): a XIAO nRF54L15 plus the
LoRa Plus Expansion Board plus a Wio-LR2021 module, all in XIAO sockets sharing one
set of D0..D10 nets. It is a distinct board from `xiao_nrf54l15` above, which is the
same MCU on a Wio-SX1262 carrier with incompatible wiring. Two hardware gotchas
before first boot: the **IDCC jumper** must be fitted or the radio has no power, and
the **LF U.FL pigtail** must be connected before transmitting. The board's `.dts`
header carries the full pin map and the reasoning behind each choice.
The SoC has no USB peripheral at all, so none of these boards 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 through the wired companion
transport (`ZephyrCompanionUSB.cpp`, UART backend: the board's
`zephcore,companion-uart` chosen node selects `CONFIG_ZEPHCORE_COMPANION_SERIAL`
when there is no Bluetooth) — no BLE pairing, the official serial client
connects directly, and a terminal gets the text CLI.
- **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 a zephcore.yml manifest (copy one from a similar board; schema in
zephcore/scripts/board_manifest.py). No CMake edit is needed: the
boards/<platform>/ directory the board lives in selects its platform.
Leave out `release:` until the board is validated on hardware, then run
`python zephcore/scripts/board_manifest.py check`.
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](../../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:
```kconfig
# 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.
RXEN and DIO2 together drive the module's RF switch, and DIO3 powers its TCXO at 1.8 V.
+112 -81
View File
@@ -1,110 +1,141 @@
# ============================================================================
# ZephCore — Universal board.conf Template
# ZephCore — board.conf template
# ============================================================================
#
# This file is ONLY for settings that cannot be auto-detected from devicetree
# or Kconfig conditionals. Most hardware features are auto-enabled:
# Only for settings that cannot be derived from devicetree. Most hardware
# features switch themselves on:
#
# Feature Auto-detected from
# Feature Enabled by
# ─────────────── ──────────────────────────────────────────────────
# Display DT chosen node: zephyr,display
# Buzzer + PWM DT nodelabel: buzzer (compatible "pwm-leds")
# Display DT chosen: zephyr,display
# Buzzer + PWM DT nodelabel: buzzer (child of a "pwm-leds" node)
# QSPI flash DT node: compatible "nordic,qspi-nor"
# SPI (LR1110) Kconfig: ZEPHCORE_RADIO_LR1110 selects SPI
# RX duty cycle Kconfig: auto-enabled for companion + SX1262/LLCC68
# Sensors DT: I2C scan at runtime
# GPS DT: GNSS compatible string on UART
# GPS DT nodelabel: gnss (a GNSS driver on a UART)
# I2C sensors DT: boards/common/sensors-i2c.dtsi, probed at runtime
# Hardware RTC DT: boards/common/rtc-i2c.dtsi, probed at boot
# ESP32 PSRAM DT: psram0 size > 0 (zephcore/Kconfig.psram)
#
# Config chain (later files override earlier):
# prj.conf → zephcore_common.conf → <platform>_common.conf → THIS FILE
# Config chain (later files override earlier ones):
# prj.conf → zephcore_common.conf → <platform>_common.conf
# → esp32c3s3_common.conf (ESP32-S3/C3 only) → THIS FILE
# → auto-included feature confs → your -DEXTRA_CONF_FILE
#
# Platform configs are auto-selected in CMakeLists.txt based on BOARD name:
# nrf52 → nrf52_common.conf (UF2, USB CDC, DLE, RTT)
# nrf54l → nrf54l_common.conf (DLE, RTT)
# mg24 → mg24_common.conf (SiLabs blob stacks, larger heap)
# esp32 → esp32_common.conf (Espressif blob stacks, larger heap)
# The platform conf is chosen by the directory this board lives in
# (boards/nrf52840/, nrf54l/, esp32/, mg24/, stm32wl/). There is nothing to
# register in CMakeLists.txt.
#
# DO NOT duplicate settings from the common configs!
#
# IMPORTANT: Register your board in CMakeLists.txt!
# Add "OR BOARD MATCHES \"your_board\"" to the correct platform detection
# line (~line 60-75) so the right <platform>_common.conf is auto-included.
# DO NOT duplicate settings from the common confs.
#
# ============================================================================
# ========== REQUIRED: Board Identity ==========
# Name shown on phone app and OLED top bar
# Name reported to the phone app and by the `board` CLI command. 39 characters
# at most (static_assert in app/CompanionMesh.cpp). Match the Arduino MeshCore
# variant's name where one exists.
CONFIG_ZEPHCORE_BOARD_NAME="My Board"
# BLE Device Information Service model name (shown in phone settings)
CONFIG_BT_DIS_MODEL_NUMBER_STR="My Board v1"
# ========== REQUIRED (nRF52 only): SoftDevice Firmware ID ==========
# Determines the app start address — MUST match your bootloader's SoftDevice.
# Wrong value = bootloop / bricked device with no USB/BLE/logs.
# Written into the DFU .zip (adafruit-nrfutil --sd-req). It MUST match the
# SoftDevice the board's Adafruit bootloader was flashed with, and the
# partition include in the DTS must be the matching one:
#
# Check the Arduino variant's platformio.ini for which linker script it uses:
# nrf52840_s140_v6*.ld → 0x00B6 (app@0x26000)
# nrf52840_s140_v7*.ld → 0x0123 (app@0x27000)
# Arduino variant linker script FWID DTS include
# nrf52840_s140_v6*.ld (app@0x26000) 0x00B6 nrf52_partitions_sdv6.dtsi
# nrf52840_s140_v7*.ld (app@0x27000) 0x0123 nrf52_partitions_sdv7.dtsi
#
# CONFIG_ZEPHCORE_SD_FWID=0x00B6 # s140 v6.1.1 (RAK4631, ThinkNode M1/M3)
# CONFIG_ZEPHCORE_SD_FWID=0x0123 # s140 v7.3.0 (XIAO nRF52, Wio Tracker, T1000-E)
# The wrong pair puts the app at the wrong address: bootloop, no USB, no logs.
#
# CONFIG_ZEPHCORE_SD_FWID=0x00B6 # s140 v6.1.1 (RAK4631, ThinkNode M1/M3/M6, Heltec T114)
# CONFIG_ZEPHCORE_SD_FWID=0x0123 # s140 v7.3.0 (XIAO nRF52840, Wio Tracker L1, T1000-E)
# ========== OPTIONAL: Radio Type ==========
# Default: RADIO_NATIVE (SX126x, SX127x, LLCC68, STM32WL) — no setting needed.
# Override ONLY for LR11xx boards (SPI is auto-selected):
# CONFIG_ZEPHCORE_RADIO_LR1110=y
# ========== OPTIONAL: Max Contacts Override ==========
# Default is 350 (Kconfig). Override for RAM-constrained boards:
# nRF52840 + QSPI: 510 (ample storage)
# nRF52840: 350 (default, no override needed)
# ESP32-C6: 350 (default, no override needed)
# nRF54L15: 450 (FLPR core reserves 68KB SRAM)
# ESP32-C3: 300 (limited DRAM)
# MG24: 350 (default, Silabs blob uses 32KB heap)
# CONFIG_ZEPHCORE_MAX_CONTACTS=450
# ========== OPTIONAL: Display Heap Override ==========
# The CFB framebuffer is allocated via k_malloc at boot.
# Default heap (2048 in zephcore_common.conf) fits 128x64 OLEDs.
# Larger displays WILL fail silently (-ENOMEM, blank screen).
# Default: ZEPHCORE_RADIO_NATIVE (Zephyr's SX126x driver: SX1261/62/68, LLCC68,
# STM32WL). Nothing to set for those. Otherwise pick the one that matches the
# lora node's compatible:
# CONFIG_ZEPHCORE_RADIO_LR1110=y # semtech,lr1110 (LR1110/LR1120/LR1121)
# CONFIG_ZEPHCORE_RADIO_LR2021=y # semtech,lr2021
#
# Display Resolution Framebuffer Heap needed
# SSD1306/SH1106 128x64 1024 bytes 2048 (default)
# SSD1681 200x200 5000 bytes 6144+
# SSD1680 296x128 4736 bytes 6144+
# SX127x goes through the loramac-node backend and needs the whole group
# (source-only path, see boards/esp32/ttgo_lora32/):
# CONFIG_LORA_MODULE_BACKEND_NATIVE=n
# CONFIG_LORA_MODULE_BACKEND_LORAMAC_NODE=y
# CONFIG_ZEPHCORE_RADIO_SX127X=y
# CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=17
# ========== OPTIONAL: TX Power (boards with an external PA / FEM) ==========
# Both default to 22 dBm, the SX1262 maximum. With a PA after the chip, the
# chip must be held below the PA's maximum input:
# CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=14
# CONFIG_ZEPHCORE_MAX_TX_POWER_DBM=19 # hard cap, enforced by the radio adapter
# ========== OPTIONAL: Companion table sizes (RAM-bound boards) ==========
# Defaults: 350 contacts, 40 channels, 256 queued offline messages. The
# companion is limited by RAM, not flash, so the boards that override these
# lower them. Values in the tree today:
# nRF52840 350 (default)
# nRF54L15 450 (nRF54LM20A: 510)
# ESP32-C3 300
# ESP32-C6 280
# ESP32-S3 without PSRAM 130-140 (Heltec V3, Wireless Tracker)
# Classic ESP32 50-140, with MAX_CHANNELS / OFFLINE_QUEUE_SIZE trimmed too
# STM32WL (64 KB SRAM) 12
# Raise it only after reading the RAM figure in the build's memory report.
# CONFIG_ZEPHCORE_MAX_CONTACTS=300
# CONFIG_ZEPHCORE_MAX_CHANNELS=8
# CONFIG_ZEPHCORE_OFFLINE_QUEUE_SIZE=128
# ========== OPTIONAL: Display heap (nRF52 / nRF54L with a large panel) ==========
# The CFB framebuffer is k_malloc'd at boot. The 2048-byte default in
# zephcore_common.conf fits a 128x64 OLED (1024 bytes). A larger panel fails
# with -ENOMEM and stays blank, with no other symptom.
#
# Panel Resolution Framebuffer Heap in the tree
# SSD1306/SH1106 128x64 1024 bytes 2048 (default)
# ST7735 mono TFT 160x80 1600 bytes 4096
# SSD1681 e-paper 200x200 5000 bytes 6144
#
# ESP32 and MG24 already have a heap of tens of KB and need no override.
# CONFIG_HEAP_MEM_POOL_SIZE=6144
#
# On RAM-constrained MCUs (nRF52840 + large display), you may also need:
# CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=1024 # Shrink RTT buffer (saves 3KB)
# ========== OPTIONAL: ESP32 Flash Size Override ==========
# esp32_common.conf defaults to 4MB flash. Override for boards with more:
# ========== OPTIONAL: UI ==========
# Boards whose long press emits KEY_ENTER: two confirms do not fit in the
# default 500 ms window.
# CONFIG_ZEPHCORE_UI_CONFIRM_WINDOW_MS=3000
# Larger built-in font for high-resolution panels (e-paper).
# CONFIG_ZEPHCORE_DISPLAY_LARGE_FONT=y
# ========== OPTIONAL: ESP32 ==========
# esp32_common.conf assumes 4 MB flash. Set the real size; sysbuild copies it
# to the MCUboot image (sysbuild/CMakeLists.txt). A mismatch hangs MCUboot at
# "spi_flash: detected chip:".
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
#
# PSRAM is enabled from devicetree. Only the mode of an 8 MB octal part
# (ESP32-S3R8) has to be stated; 2 MB quad (R2) is the upstream default.
# CONFIG_SPIRAM_MODE_OCT=y
#
# Classic ESP32 PICO-D4 bootloops when the bootloader enables QIO flash mode.
# CONFIG_ESPTOOLPY_FLASHMODE_DIO=y
# CONFIG_ESPTOOLPY_FLASHMODE_QIO=n
#
# Boards with an I2C fuel gauge / PMU that reports state of charge (AXP2101).
# CONFIG_FUEL_GAUGE=y
# ========== OPTIONAL: TX Power Override (boards with external PA) ==========
# Default: 22 dBm (SX1262 max). Override for boards with external PA
# to prevent overdriving:
# CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=15 # e.g., Station G2 (20dB PA)
# CONFIG_ZEPHCORE_MAX_TX_POWER_DBM=19 # Hard cap
# ========== OPTIONAL: UART Async API (nRF52840 GPS boards) ==========
# Enable if your board has a GPS module on UART with deferred-init
# (i.e., GPS power is controlled by external GPIOs, not the GNSS driver).
#
# The nRF52840 UARTE is DMA-based. The default ISR modem backend
# emulates byte-by-byte interrupts on top of DMA, which breaks when GPS
# data arrives before the ISR handler is enabled.
#
# WHEN TO ENABLE:
# - Your board uses quectel,lc76g with zephyr,deferred-init
# - GPS module starts transmitting before modem_pipe_open() is called
#
# WHEN NOT NEEDED:
# - luatos,air530z driver (manages power internally via on-off-gpios)
# - GPS at 9600 baud (slow enough for ISR backend)
# - Boards where the GNSS driver controls power (no deferred-init)
#
# CONFIG_UART_ASYNC_API=y
# ========== DO NOT SET HERE ==========
# CONFIG_ZEPHCORE_UI_DISPLAY / _UI_BUZZER / CONFIG_PWM / CONFIG_DISPLAY
# follow devicetree (exception: a board whose panel is an optional
# plug-in forces UI_DISPLAY=y, see boards/nrf52840/rak4631/)
# CONFIG_SPI, CONFIG_LORA selected by the radio type
# CONFIG_NORDIC_QSPI_NOR follows the nordic,qspi-nor node
# CONFIG_ESP_SPIRAM / _SIZE follow the psram0 node
# CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE
# boot default is off for every role; users switch it with `set rxduty`
# CONFIG_ZEPHCORE_ROLE_*, CONFIG_LOG*, CONFIG_BT=n
# role and debug policy come from boards/common/*.conf, never from a board
# CONFIG_CLOCK_CONTROL_NRF_K32SRC_*
# ignored since the Zephyr main pin; the 32.768 kHz source is devicetree
# (&lfclk { k32src = "rc"; }, see board.overlay and docs/adr/0005)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,154 @@
/*
* ============================================================================
* ZephCore — partitions.overlay template (ESP32-S3 and ESP32-C3/C6 boards)
* ============================================================================
*
* The flash map of an ESP32-S3 / C-series board lives in this file, not in
* board.overlay, because it is fed to BOTH images of a sysbuild build: the app
* (last entry of EXTRA_DTC_OVERLAY_FILE, zephcore/CMakeLists.txt) and MCUboot
* (mcuboot_EXTRA_DTC_OVERLAY_FILE, zephcore/sysbuild/CMakeLists.txt). If the two
* disagree, MCUboot looks for the OTA image at a different address than the app
* wrote it to and every WiFi OTA "succeeds", then boots the old firmware.
* Background: docs/adr/0001-esp32-boot-and-flash-layout.md.
*
* Who needs this file:
* ESP32-S3, ESP32-C3, ESP32-C6 yes (repeater and release builds use MCUboot)
* Classic ESP32 no: simple boot only; split the stock
* storage_partition in board.overlay instead
* (boards/esp32/ttgo_tbeam/board.overlay)
* nRF52 / nRF54L / MG24 / STM32WL no: see board.overlay
*
* Rules every layout here follows:
* - slot0 at 0x10000, right after the 64 KB MCUboot region. This is also the
* Arduino / ESP-IDF app offset, which keeps the release image compatible
* with the Mesh America "flash-update" flow.
* - slot0 and slot1 the same size (MCUboot swap).
* - `storage` (16 KB NVS, BLE bonds) and `lfs` (LittleFS: identity, prefs,
* contacts) at the top of flash. Once a board has shipped, never move
* them: a reflash that moves them wipes every user's identity and bonds.
* - every partition carries compatible = "zephyr,mapped-partition" (Zephyr
* main); without it the code that opens the partition fails to compile
* with "..._PARTITION_ID undeclared".
*
* THE BASE-TABLE TRAP. Espressif ships two stock tables per flash size,
* partitions_0x0_default_<size>.dtsi and partitions_0x0_amp_<size>.dtsi. The amp
* one also defines slot0_appcpu_partition and slot1_appcpu_partition. The
* /delete-node/ lists below match the DEFAULT table: on top of an amp table the
* two appcpu nodes survive and overlap the new slot1 and storage. Nothing checks
* partition overlap at build time, so it builds, boots and is silently wrong.
* - Custom board DTS: include partitions_0x0_default_<size>.dtsi
* (boards/esp32/lilygo_t3s3/lilygo_t3s3_procpu.dts).
* - Upstream board whose DTS includes the amp table: add the two appcpu
* labels to the delete list.
*
* After the first build, read the map back instead of trusting this file:
* build/zephyr/zephyr.dts for a plain build; for --sysbuild compare
* build/zephcore/zephyr/zephyr.dts with build/mcuboot/zephyr/zephyr.dts. A
* correct map is contiguous, free of overlaps and ends at the flash size.
*
* Layouts in the tree, by chip and flash size (copy the nearest sibling):
*
* Chip / flash storage (NVS) lfs Sibling board
* ─────────────── ────────────────── ─────────────────── ──────────────────────
* S3, 4 MB 0x39C000 16 KB 0x3A0000 384 KB xiao_esp32s3, lilygo_t3s3
* S3, 8 MB 0x79C000 16 KB 0x7A0000 384 KB heltec_wifi_lora32_v3
* S3, 16 MB 0xF9C000 16 KB 0xFA0000 384 KB heltec_wifi_lora32_v4, station_g2
* C3/C6, 4 MB 0x3E0000 16 KB 0x3B0000 192 KB xiao_esp32c3, xiao_esp32c6
*
* Remove the #if 0 / #endif around the one layout that fits and delete the
* other. board.overlay must still end with
* #include "../../common/filesystem.dtsi"
* which mounts &lfs_partition at /lfs.
*
* SPDX-License-Identifier: MIT
*/
#if 0 /* ---------- LAYOUT A: ESP32-S3, 4 MB flash (xiao_esp32s3) ---------- */
/delete-node/ &sys_partition;
/delete-node/ &slot0_lpcore_partition;
/delete-node/ &slot1_lpcore_partition;
/delete-node/ &storage_partition;
/delete-node/ &coredump_partition;
/delete-node/ &slot0_partition;
/delete-node/ &slot1_partition;
/ {
chosen {
zephyr,settings-partition = &storage_partition;
};
};
&flash0 {
partitions {
/* App slots — equal size, slot0 at 0x10000. */
slot0_partition: partition@10000 {
compatible = "zephyr,mapped-partition";
label = "image-0";
reg = <0x10000 0x1C6000>;
};
slot1_partition: partition@1D6000 {
compatible = "zephyr,mapped-partition";
label = "image-1";
reg = <0x1D6000 0x1C6000>;
};
/* BLE bond NVS, 16 KB. */
storage_partition: partition@39C000 {
compatible = "zephyr,mapped-partition";
label = "storage";
reg = <0x39C000 0x4000>;
};
/* LittleFS, 384 KB (identity / prefs / contacts). */
lfs_partition: partition@3A0000 {
compatible = "zephyr,mapped-partition";
label = "lfs";
reg = <0x3A0000 0x60000>;
};
};
};
#endif /* LAYOUT A */
#if 0 /* ---------- LAYOUT B: ESP32-C3 / C6, 4 MB flash (xiao_esp32c3) ---------- */
/* The stock LP-core slots at 0x3A0000 are kept, so the app slots stop there. */
/delete-node/ &sys_partition;
/delete-node/ &slot0_partition;
/delete-node/ &slot1_partition;
/delete-node/ &storage_partition;
/ {
chosen {
zephyr,settings-partition = &storage_partition;
};
};
&flash0 {
partitions {
/* App slots — equal size, slot0 at 0x10000. */
slot0_partition: partition@10000 {
compatible = "zephyr,mapped-partition";
label = "image-0";
reg = <0x10000 0x1C8000>;
};
slot1_partition: partition@1d8000 {
compatible = "zephyr,mapped-partition";
label = "image-1";
reg = <0x1D8000 0x1C8000>;
};
/* LittleFS, 192 KB (identity / prefs / contacts). */
lfs_partition: partition@3b0000 {
compatible = "zephyr,mapped-partition";
label = "lfs";
reg = <0x3B0000 0x30000>;
};
/* BLE bond NVS, 16 KB. */
storage_partition: partition@3e0000 {
compatible = "zephyr,mapped-partition";
label = "storage";
reg = <0x3E0000 0x4000>;
};
};
};
#endif /* LAYOUT B */
@@ -0,0 +1,50 @@
# ZephCore board manifest — schema in zephcore/scripts/board_manifest.py
#
# TEMPLATE. Copy to boards/<platform>/<board>/zephcore.yml. This copy is not
# read by anything: the manifest loader only scans the platform directories.
#
# The manifest is the one place a board is registered. build.sh (the release
# matrix), gen_provider_catalog.py, docs/supported_boards.md and the board
# count in the root README are all generated from it. After editing:
#
# python zephcore/scripts/board_manifest.py check
# python zephcore/scripts/board_manifest.py docs
# REQUIRED. The `west build -b` string, qualifiers included. It must start with
# the directory name:
# nRF52840 / MG24 / STM32WL / ESP32-C3 my_board
# ESP32-S3 my_board/esp32s3/procpu
# ESP32-C6 my_board/esp32c6/hpcore
# Classic ESP32 my_board/esp32/procpu
# nRF54L15 my_board/nrf54l15/cpuapp
target: my_board
# OPTIONAL, ESP32 only. CMake reads these by line match, so keep the exact
# two-space-indented ` name: true` form.
# capabilities:
# wifi: true # companion also gets the WiFi (TCP) transport: the
# # board has the RAM for WiFi and BLE together (ADR 0009)
# light_sleep: true # repeater light-sleeps between events. Declare it only
# # after boards/common/pm_esp32.conf's checks pass on
# # hardware; qualifying wiring alone is not enough
# Leave `release:` out while the board is in bring-up: it then builds locally
# and is listed as "source-only", but no release carries firmware for it.
# release:
# roles: [companion, repeater] # also: room_server, observer
# variants: # extra published builds of this board
# - id: noscreen # artifact name infix
# conf: no_display.conf # in this directory; a no_display.overlay
# # beside it is paired automatically
# subtitle: "No screen"
# Mesh America configurator tile. Needs `release:`. `device` must be MeshCore's
# exact name for the board, or it will not fold into their tile; take it from
# https://raw.githubusercontent.com/meshcore-dev/flasher.meshcore.io/main/config.json
# (docs/PROVIDER_CATALOG.md). `maker` is a key of MAKERS in gen_provider_catalog.py.
# catalog:
# device: "Vendor My Board"
# maker: vendor
# new: true # MeshCore has no tile for it: publish our own
# img: lora.svg # with `new`: stock art, or own_img: my_board.jpg
# subtitle: "30 dBm" # tells two builds in one tile apart