|
| 1 | +# Board Configuration |
| 2 | + |
| 3 | +Each board requires several files to integrate with the CircuitPython Zephyr |
| 4 | +build system. This document describes the file layout, partitioning scheme, and |
| 5 | +MCUboot (Adaboot) bootloader requirements. |
| 6 | + |
| 7 | +## File Layout |
| 8 | + |
| 9 | +For a board named `<board>` with Zephyr qualifier `<board>_<soc>_<cpu>`: |
| 10 | + |
| 11 | +``` |
| 12 | +boards/ |
| 13 | +├── <vendor>/<board>/ |
| 14 | +│ ├── circuitpython.toml # Build config (extensions, USB IDs, flash overrides) |
| 15 | +│ └── autogen_board_info.toml # Auto-generated module info (committed, not edited) |
| 16 | +├── <board>_<soc>_<cpu>.conf # Kconfig settings (BLE, WiFi, etc.) |
| 17 | +├── <board>_<soc>_<cpu>.overlay # Device tree overlay (partitions, code-partition, USB) |
| 18 | +├── mcuboot_<board>.conf # Board-specific MCUboot config (optional) |
| 19 | +├── partitions/ |
| 20 | +│ ├── <board>.dtsi # Hand-written partition overlay (see below) |
| 21 | +│ └── generated/ |
| 22 | +│ └── <board>.dtsi # Auto-generated partition layout |
| 23 | +└── board_aliases.cmake # Maps friendly names to Zephyr board specs |
| 24 | +``` |
| 25 | + |
| 26 | +## Partition Structure |
| 27 | + |
| 28 | +Boards use a two-layer partition scheme: |
| 29 | + |
| 30 | +### Hand-written layer (`partitions/<board>.dtsi`) |
| 31 | + |
| 32 | +This file is written once per board and handles board-specific setup: |
| 33 | + |
| 34 | +1. **Delete Zephyr-default partitions** so they can be replaced: |
| 35 | + ```dts |
| 36 | + &flash0 { /delete-node/ partitions; }; |
| 37 | + ``` |
| 38 | + |
| 39 | +2. **Enable external flash** if it's disabled by default: |
| 40 | + ```dts |
| 41 | + &mx25r64 { status = "okay"; }; |
| 42 | + ``` |
| 43 | + |
| 44 | +3. **Set up retention memory** for MCUboot double-tap reset (if the SoC has |
| 45 | + `gpregret`): |
| 46 | + ```dts |
| 47 | + &gpregret1 { |
| 48 | + status = "okay"; |
| 49 | + boot_mode0: boot_mode@0 { |
| 50 | + compatible = "zephyr,retention"; |
| 51 | + status = "okay"; |
| 52 | + reg = <0x0 0x1>; |
| 53 | + }; |
| 54 | + }; |
| 55 | + / { |
| 56 | + chosen { zephyr,boot-mode = &boot_mode0; }; |
| 57 | + }; |
| 58 | + ``` |
| 59 | + |
| 60 | +4. **Define MCUboot button alias**: |
| 61 | + ```dts |
| 62 | + / { aliases { mcuboot-button0 = &button0; }; }; |
| 63 | + ``` |
| 64 | + |
| 65 | +5. **Define additional flash controllers** for multi-image boards (e.g. |
| 66 | + nRF5340 net core flash): |
| 67 | + ```dts |
| 68 | + / { soc { |
| 69 | + netcore_flash_controller: flash-controller@41080000 { |
| 70 | + compatible = "nordic,nrf53-flash-controller"; |
| 71 | + /* ... flash1 with netcore_partition ... */ |
| 72 | + }; |
| 73 | + }; }; |
| 74 | + ``` |
| 75 | + |
| 76 | +6. **Include the generated layout**: |
| 77 | + ```dts |
| 78 | + #include "generated/<board>.dtsi" |
| 79 | + ``` |
| 80 | + |
| 81 | +### Generated layer (`partitions/generated/<board>.dtsi`) |
| 82 | + |
| 83 | +Auto-generated by `cptools/bootloaderify.py --fix <board_id>` for most boards. |
| 84 | +For boards with multi-core partitioning (nRF54H20), the generated file is |
| 85 | +hand-written since the tool doesn't know about multi-domain layouts. |
| 86 | + |
| 87 | +Contains the actual partition table with sizes and offsets aligned to erase |
| 88 | +block boundaries. |
| 89 | + |
| 90 | +Typical layout with internal + external flash: |
| 91 | + |
| 92 | +| Partition | Location | Purpose | |
| 93 | +|------------|----------|---------| |
| 94 | +| `mcuboot` | Internal | Adaboot bootloader (128 KB) | |
| 95 | +| `image-0` | Internal | Application slot (fills remaining internal flash) | |
| 96 | +| `storage` | Internal | Zephyr settings storage | |
| 97 | +| `nvm` | Internal or External | Non-volatile memory for CircuitPython | |
| 98 | +| `image-1` | External | OTA update slot (same size as image-0 + 1 erase page) | |
| 99 | +| `image-2` | Internal or flash1 | Net/radio core firmware (multi-core boards only) | |
| 100 | +| `circuitpy`| External | CircuitPython filesystem (remainder of external flash) | |
| 101 | + |
| 102 | +For internal-only boards, all partitions share the single flash device. |
| 103 | + |
| 104 | +## Board Overlay (`<board>_<soc>_<cpu>.overlay`) |
| 105 | + |
| 106 | +The overlay ties partitions to the build and adds USB: |
| 107 | + |
| 108 | +```dts |
| 109 | +#include "partitions/<board>.dtsi" |
| 110 | +
|
| 111 | +/ { |
| 112 | + chosen { |
| 113 | + zephyr,code-partition = &slot0_partition; |
| 114 | + }; |
| 115 | +}; |
| 116 | +
|
| 117 | +#include "../app.overlay" /* Only if the board has USB */ |
| 118 | +``` |
| 119 | + |
| 120 | +## MCUboot (Adaboot) Integration |
| 121 | + |
| 122 | +The `sysbuild/CMakeLists.txt` automatically enables MCUboot for any board that |
| 123 | +has a `boards/partitions/<board>.dtsi` file. It combines: |
| 124 | + |
| 125 | +- `sysbuild/adaboot.conf` — enables MCUboot in single-app mode, no signatures |
| 126 | +- `sysbuild/mcuboot.conf` — UF2 drag-and-drop, GPIO button entrance, double-tap reset |
| 127 | +- `boards/mcuboot_<board>.conf` — optional board-specific overrides |
| 128 | + |
| 129 | +### Board-specific MCUboot config |
| 130 | + |
| 131 | +Create `boards/mcuboot_<board>.conf` when the board needs: |
| 132 | + |
| 133 | +- **Retention subsystem** (`CONFIG_RETAINED_MEM=y`, `CONFIG_RETENTION=y`, |
| 134 | + `CONFIG_RETENTION_BOOT_MODE=y`) — required for double-tap reset on boards |
| 135 | + with `gpregret` |
| 136 | +- **USB driver quirks** (e.g. `CONFIG_MULTITHREADING=y` for DWC2 USB controllers) |
| 137 | +- **UF2 disabled** (`CONFIG_MCUBOOT_UF2=n`) — for boards without USB |
| 138 | +- **Double-tap disabled** (`CONFIG_MCUBOOT_UF2_ENTRANCE_DOUBLE_TAP=n`) — for |
| 139 | + boards without `gpregret` retention (e.g. nRF54H20) |
| 140 | + |
| 141 | +## Multi-Image Updates |
| 142 | + |
| 143 | +Boards with multiple cores can update all core firmware from a single UF2 |
| 144 | +drag-and-drop operation. The UF2 handler in MCUboot (`uf2_disk.c`) supports |
| 145 | +multiple flash targets, routing UF2 blocks by `target_addr` to the correct |
| 146 | +flash region. |
| 147 | + |
| 148 | +### nRF5340 (cpuapp + cpunet, separate flash per core) |
| 149 | + |
| 150 | +The nRF5340 has a dedicated 256 KB flash for the network core (`flash1` at |
| 151 | +0x01000000), separate from the 1 MB app core flash (`flash0`). |
| 152 | + |
| 153 | +The partition dtsi defines the net core flash controller |
| 154 | +(`nordic,nrf53-flash-controller` at 0x41080000) so MCUboot running on the app |
| 155 | +core can write to it. A `netcore_partition` covering the full 256 KB is |
| 156 | +registered as a secondary UF2 target. |
| 157 | + |
| 158 | +UF2 blocks targeting 0x00000000-range go to flash0 (app core), blocks |
| 159 | +targeting 0x01000000-range go to flash1 (net core). The build system produces |
| 160 | +separate UF2 files per core, concatenated into one file for the user. |
| 161 | + |
| 162 | +| Device | Partition | Size | Purpose | |
| 163 | +|--------|-----------|------|---------| |
| 164 | +| flash0 | mcuboot | 128 KB | Bootloader | |
| 165 | +| flash0 | image-0 | ~864 KB | App core firmware | |
| 166 | +| flash0 | storage | 32 KB | Settings | |
| 167 | +| flash1 | image-2 | 256 KB | Net core firmware (entire flash1) | |
| 168 | +| mx25r64 | nvm | 4 KB | Non-volatile memory | |
| 169 | +| mx25r64 | image-1 | ~868 KB | App core update slot | |
| 170 | +| mx25r64 | circuitpy | ~7 MB | CircuitPython filesystem | |
| 171 | + |
| 172 | +Applies to: **nrf5340dk**, **nrf7002dk** |
| 173 | + |
| 174 | +### nRF54H20 (cpuapp + cpurad, shared MRAM behind IronSide SE) |
| 175 | + |
| 176 | +The nRF54H20 has a single 2 MB MRAM shared by all cores (cpuapp, cpurad, |
| 177 | +cpuppr, cpuflpr). Nordic's IronSide SE secure element occupies the first |
| 178 | +192 KB and acts as the first-stage bootloader — it validates and boots MCUboot |
| 179 | +(Adaboot) at offset 0x30000. |
| 180 | + |
| 181 | +IronSide SE's update service is only for updating IronSide SE itself. |
| 182 | +Application firmware is managed entirely by MCUboot. The app core boots the |
| 183 | +radio core at runtime via `ironside_se_cpuconf()`. |
| 184 | + |
| 185 | +The `netcore_partition` (labeled `image-2`) holds the cpurad firmware. UF2 |
| 186 | +blocks are routed by address: cpuapp blocks target the `image-0` range, |
| 187 | +cpurad blocks target the `image-2` range — both within MRAM. |
| 188 | + |
| 189 | +No `gpregret` is available on nRF54H20, so double-tap reset is disabled. |
| 190 | +UF2 entrance is via GPIO button only. |
| 191 | + |
| 192 | +| Device | Partition | Size | Purpose | |
| 193 | +|--------|-----------|------|---------| |
| 194 | +| mram1x | *(IronSide SE)* | 192 KB | Secure boot (reserved, not touched) | |
| 195 | +| mram1x | mcuboot | 128 KB | Adaboot bootloader | |
| 196 | +| mram1x | image-0 | 1344 KB | App core firmware | |
| 197 | +| mram1x | image-2 | 352 KB | Radio core firmware (cpurad) | |
| 198 | +| mram1x | nvm | 16 KB | Non-volatile memory | |
| 199 | +| mram1x | storage | 16 KB | Settings | |
| 200 | +| mx25uw63 | image-1 | ~1348 KB | App core update slot | |
| 201 | +| mx25uw63 | circuitpy | ~6.7 MB | CircuitPython filesystem | |
| 202 | + |
| 203 | +### nRF54LM20 / nRF54L15 (single core with external flash) |
| 204 | + |
| 205 | +Standard single-core layout. Internal RRAM holds the bootloader, app, and |
| 206 | +settings. External SPI flash holds the update slot and CircuitPython |
| 207 | +filesystem. RRAM's `gpregret` provides double-tap reset support. |
| 208 | + |
| 209 | +The nRF54L15 has no USB, so UF2 is disabled in its MCUboot config. Firmware |
| 210 | +is flashed via J-Link. |
| 211 | + |
| 212 | +## Adding a New Board |
| 213 | + |
| 214 | +1. Create `boards/<vendor>/<board>/circuitpython.toml` with build extensions |
| 215 | + and USB VID/PID. Add `[external_flash]` if the board has SPI/QSPI flash |
| 216 | + without `erase-block-size` in the device tree: |
| 217 | + ```toml |
| 218 | + CIRCUITPY_BUILD_EXTENSIONS = ["elf"] |
| 219 | + |
| 220 | + [external_flash] |
| 221 | + label = "mx25r64" |
| 222 | + erase_block_size = 4096 |
| 223 | + ``` |
| 224 | + |
| 225 | +2. Add a board alias to `board_aliases.cmake`: |
| 226 | + ```cmake |
| 227 | + cp_board_alias(vendor_board board/soc/cpu) |
| 228 | + ``` |
| 229 | + |
| 230 | +3. Generate partitions: |
| 231 | + ```sh |
| 232 | + python3 cptools/bootloaderify.py --fix vendor_board |
| 233 | + ``` |
| 234 | + For multi-core boards where bootloaderify can't handle the layout, create |
| 235 | + `boards/partitions/generated/<board>.dtsi` manually. |
| 236 | + |
| 237 | +4. Create `boards/partitions/<board>.dtsi` — delete default partitions, |
| 238 | + enable flash/retention, add mcuboot button alias, define additional flash |
| 239 | + controllers for multi-core, include generated dtsi. |
| 240 | + |
| 241 | +5. Create `boards/<board>_<soc>_<cpu>.overlay` — include partition dtsi, |
| 242 | + set `zephyr,code-partition`, include `app.overlay` if USB is available. |
| 243 | + |
| 244 | +6. Create `boards/mcuboot_<board>.conf` if needed for retention, USB quirks, |
| 245 | + or disabling features (UF2, double-tap). |
| 246 | + |
| 247 | +7. Create `boards/<board>_<soc>_<cpu>.conf` for any Kconfig settings |
| 248 | + (BLE, WiFi, logging, etc.). |
| 249 | + |
| 250 | +## Notes |
| 251 | + |
| 252 | +- The `bootloaderify.py` tool detects predefined mcuboot partitions in the |
| 253 | + upstream Zephyr DTS. When external flash is available, it moves slot1 to |
| 254 | + external flash and grows slot0 to fill internal flash. |
| 255 | +- Boards without USB (e.g. nrf54l15dk) get partitions for circuitpy/nvm/storage |
| 256 | + but disable UF2 in their mcuboot config. Firmware is flashed via J-Link or |
| 257 | + serial recovery. |
0 commit comments