Skip to content

Commit fe53127

Browse files
committed
article: MCUboot Getting Start Guide for ESP32
Article translated and updated from my original from Embarcados portal. Signed-off-by: Almir Okato <[email protected]>
1 parent 1ee8cff commit fe53127

File tree

4 files changed

+386
-0
lines changed

4 files changed

+386
-0
lines changed
15.5 KB
Loading
37.1 KB
Loading
39.5 KB
Loading
Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
---
2+
title: "MCUboot - Getting Started Guide for ESP32"
3+
date: 2025-12-16
4+
showAuthor: false
5+
authors:
6+
- "almir-okato"
7+
tags: ["Bootloader", "MCUboot", "NuttX", "Zephyr"]
8+
---
9+
10+
## Introduction
11+
12+
In the embedded world, firmware updates are essential and increasingly important. As IoT solutions grow exponentially in numbers and complexity, so does the concern to make devices secure and updatable in the field efficiently.
13+
14+
Some years ago, **MCUboot** emerged as an open source bootloader project for small and low-cost systems, designed to simplify and standardize solutions for these challenges. It started as an Apache Mynewt subproject when developers decided to separate the bootloader from OS development. Later, it was ported to **Zephyr RTOS** and became its default bootloader.
15+
16+
**Espressif Systems** has been expanding support for other **3rd party RTOSes** like **Zephyr** and **NuttX** as attractive options for a developer adopt within its SoCs. Since then, a port for **Espressif** SoCs has been developed in the **MCUboot** project.
17+
18+
This guide shows how to get started with **MCUboot** on your ESP32-based project (note: this is a translated and updated version of my original article in [**Embarcados** website](https://embarcados.com.br/primeiros-passos-com-esp32-utilizando-mcuboot-como-bootloader/)). It covers environment setup, required configuration in the application side, how to build the **MCUboot Espressif Port** bootloader and how to flash it into the device. The [ESP32 DevKitC](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html) board was used to prepare this guide.
19+
20+
{{< alert icon="eye" >}}
21+
The MCUboot-compatible application mentioned in this guide can be either a **NuttX RTOS** application or a **Zephyr RTOS** application built for Espressif SoCs with MCUboot compatibility configuration enable.
22+
23+
This guide assumes that you are familiar with building and configuring the chosen RTOS.
24+
{{< /alert >}}
25+
26+
## What is MCUboot?
27+
28+
As previously mentioned, **MCUboot** is an open source secure bootloader for 32-bit microcontrollers. The project defines a common infrastructure for secure boot and it covers:
29+
30+
- **Fault-tolerant updates**: the flash organization is well defined, using the concept of slots for placing the main bootable image and update image in different flash regions, with a scratch area to help the swapping process during firmware updates. This provides mechanisms for reliable firmware updates and also allows recovering and reverting if any problem during the process.
31+
- **Security**: image validation through hash checking and signature verification using asymmetric key algorithms like RSA 2048/3072, ECDSA and ed25519 ensures integrity and authenticity of firmware images.
32+
33+
The project aims to standardize these aspects comprehensively. **MCUboot** core features are independent from any operating system and hardware, relying on hardware porting layers from the target host OS.
34+
35+
### High-level overview of the boot process
36+
37+
{{< figure
38+
default=true
39+
src="./assets/mcuboot-process-overview1.webp"
40+
caption="MCUboot boot process overview"
41+
>}}
42+
43+
{{< figure
44+
default=true
45+
src="./assets/mcuboot-process-overview2.webp"
46+
caption="MCUboot boot process overview"
47+
>}}
48+
49+
**Important concepts:**
50+
51+
- **Primary slot** is where the main bootable image resides, code always runs from there.
52+
- **Secondary slot** is used for updates; it stores the incoming image and, after the swap, stores the original image to enable recovery if needed.
53+
- **Scratch region** is used to help image swap when updating.
54+
- A header and trailer are added to the image to track general information, swapping, and update states.
55+
56+
{{< alert icon="eye" >}}
57+
The booting process and concepts presented here are related to the **Espressif Port** current support to **MCUboot**. See more at the official [MCUboot documentation](https://docs.mcuboot.com/).
58+
{{< /alert >}}
59+
60+
## Setting the environment
61+
62+
First, prepare the development environment. This guide assumes the use of Linux (Ubuntu 20.04.2 LTS or later).
63+
64+
Ensure you have **Git**, **Python3**, **pip**, **CMake** and **Ninja** installed. If you don't, you can run the following (this step is optional):
65+
66+
```bash
67+
sudo apt update
68+
sudo apt upgrade
69+
sudo apt install git python3 python3-pip cmake ninja-build
70+
```
71+
72+
### **MCUboot Espressif Port** HAL
73+
74+
The **MCUboot Espressif Port** depends on HAL (Hardware Abstraction Layer) sources based on **ESP-IDF**. Then it is required to have either **ESP-IDF** itself installed, which allows building the project in a standalone way, or one of the Espressif's HAL sources used by **Zephyr RTOS** (`zephyrproject-rtos/hal_espressif/`) or **NuttX RTOS**
75+
(`espressif/esp-hal-3rdparty`). For both the later options, it is recommended using them only within their RTOS build system, since their sources revision may differ from what is currently expected.
76+
77+
**Installing ESP-IDF v5.1.6**
78+
79+
```bash
80+
git clone --recursive https://github.com/espressif/esp-idf.git
81+
```
82+
83+
```bash
84+
cd <IDF_PATH>
85+
git checkout v5.1.6
86+
```
87+
88+
```bash
89+
<IDF_PATH>/install.sh
90+
```
91+
92+
```bash
93+
. <IDF_PATH>/export.sh
94+
```
95+
96+
More information about **ESP-IDF** installation can be found [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#manual-installation).
97+
98+
### Clone and set up MCUboot
99+
100+
1. Clone the repository into a directory of your choice (other than **ESP-IDF** installation):
101+
102+
```bash
103+
git clone https://github.com/mcu-tools/mcuboot.git
104+
cd mcuboot
105+
```
106+
107+
2. Install the additional dependencies needed by **MCUboot**:
108+
109+
```bash
110+
pip3 install -r scripts/requirements.txt
111+
```
112+
113+
3. Update the **MbedTLS** submodule required by **MCUboot**:
114+
115+
```bash
116+
git submodule update --init --recursive ext/mbedtls
117+
```
118+
119+
4. For any images you may boot with **MCUboot**, you need to sign them using `imgtool`. This tool adds the **MCUboot** expected headers and trailers to the binary, signs the firmware, and can also generate the signing keys. `imgtool` can be found at `<MCUBOOT_DIR>/scripts/imgtool.py` (where `<MCUBOOT_DIR>` is the path of the cloned repository), or you can install it as follows (optional):
120+
121+
```bash
122+
pip3 install imgtool
123+
```
124+
125+
{{< alert icon="eye" >}}
126+
**MCUboot** repository includes some sample cryptographic keys for signing and testing the secure boot verification. It is crucial that you never use these keys in production since the private key is available in the **MCUboot** repository.
127+
See how to generate and manage cryptographic keys at the [imgtool documentation](https://docs.mcuboot.com/imgtool.html).
128+
{{< /alert >}}
129+
130+
## Building and flashing MCUboot on ESP32
131+
132+
With everything set, let's configure, build and flash the **MCUboot Espressif Port** bootloader.
133+
134+
1. Firstly set the flash layout organization in the `<MCUBOOT_DIR>/boot/espressif/port/esp32/bootloader.conf` file, it must match the same flash organization as the chosen RTOS. For example:
135+
136+
```text
137+
CONFIG_ESP_FLASH_SIZE=4MB
138+
CONFIG_ESP_BOOTLOADER_SIZE=0xF000
139+
CONFIG_ESP_BOOTLOADER_OFFSET=0x1000
140+
CONFIG_ESP_IMAGE0_PRIMARY_START_ADDRESS=0x20000
141+
CONFIG_ESP_APPLICATION_SIZE=0x150000
142+
CONFIG_ESP_IMAGE0_SECONDARY_START_ADDRESS=0x170000
143+
CONFIG_ESP_SCRATCH_OFFSET=0x3E0000
144+
CONFIG_ESP_SCRATCH_SIZE=0x1F000
145+
```
146+
147+
2. Compile and generate the ELF:
148+
149+
```bash
150+
cd <MCUBOOT_DIR>/boot/espressif
151+
cmake -DCMAKE_TOOLCHAIN_FILE=tools/toolchain-esp32.cmake -DMCUBOOT_TARGET=esp32 -DESP_HAL_PATH=<IDF_PATH> -DMCUBOOT_FLASH_PORT=/dev/ttyUSB0 -B build -GNinja
152+
```
153+
154+
```bash
155+
ninja -C build/
156+
```
157+
158+
3. Erase the device's flash:
159+
160+
```bash
161+
esptool.py -p <PORT> erase_flash
162+
```
163+
164+
4. Flash **MCUboot Espressif Port** bootloader to your board:
165+
166+
```bash
167+
ninja -C build/ flash
168+
```
169+
170+
Alternatively:
171+
172+
```bash
173+
esptool.py -p /dev/ttyUSB0 -b 921600 --before default_reset --after hard_reset --chip esp32 write_flash --flash_mode dio --flash_size detect --flash_freq 40m 0x1000 build/mcuboot_esp32.bin
174+
```
175+
176+
> **Note:** You may adjust the USB port (`-p /dev/ttyUSB0`) and baud rate (`-b 921600`) according to your board connection.
177+
178+
5. Check the serial monitor on the UART port (the same port used to flash) and reset your **ESP32** board. This guide uses `picocom` tool, but any serial monitor tool can be used:
179+
180+
```bash
181+
picocom -b 115200 /dev/ttyUSB0
182+
```
183+
184+
Since no image has been flashed yet, you will see **MCUboot** waiting for an application image in the primary slot.
185+
186+
```text
187+
[esp32] [INF] *** Booting MCUboot build v2.3.0-rc2-3-g234c66e6 ***
188+
[esp32] [INF] [boot] chip revision: v3.0
189+
[esp32] [INF] [boot.esp32] SPI Speed : 40MHz
190+
[esp32] [INF] [boot.esp32] SPI Mode : DIO
191+
[esp32] [INF] [boot.esp32] SPI Flash Size : 4MB
192+
[esp32] [INF] [boot] Enabling RNG early entropy source...
193+
[esp32] [INF] Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
194+
[esp32] [INF] Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
195+
[esp32] [INF] Boot source: primary slot
196+
[esp32] [INF] Image index: 0, Swap type: none
197+
[esp32] [ERR] Unable to find bootable image
198+
```
199+
200+
## Preparing and flashing an application
201+
202+
You can use applications from either **Zephyr RTOS** or **NuttX RTOS**, ensuring **MCUboot** compatibility is enabled in whichever RTOS you use.
203+
204+
This section will quickly mention the required `Kconfigs` for setting a **NuttX RTOS** and a **Zephyr RTOS** application with **MCUboot** compatibility, assuming you are familiar with how to configure and build the chosen RTOS.
205+
206+
### NuttX RTOS
207+
208+
Enable the `CONFIG_ESP32_APP_FORMAT_MCUBOOT` configuration and check if the following config values matches what was set in the `<MCUBOOT_DIR>/boot/espressif/port/esp32/bootloader.conf`. For the example from this guide, `menuconfig` was configured as:
209+
210+
- System Type -> Bootloader and Image Configuration->
211+
- Enable MCUboot-bootable format (`CONFIG_ESP32_APP_FORMAT_MCUBOOT`): y
212+
- System Type -> SPI Flash Configuration ->
213+
- Application image primary slot offset (`CONFIG_ESPRESSIF_OTA_PRIMARY_SLOT_OFFSET`): 0x20000
214+
- Application image secondary slot offset (`CONFIG_ESPRESSIF_OTA_SECONDARY_SLOT_OFFSET`): 0x170000
215+
- Application image slot size (in bytes) (`CONFIG_ESPRESSIF_OTA_SLOT_SIZE`): 0x150000
216+
- Scratch partition offset (`CONFIG_ESPRESSIF_OTA_SCRATCH_OFFSET`): 0x3E0000
217+
- Scratch partition size (`CONFIG_ESPRESSIF_OTA_SCRATCH_SIZE`): 0x1F000
218+
219+
### Zephyr RTOS
220+
221+
Enable the `CONFIG_BOOTLOADER_MCUBOOT` configuration and check if the flash partitions on the `DTS` from your board matches what was set in the `<MCUBOOT_DIR>/boot/espressif/port/esp32/bootloader.conf`. For this example it is possible to set an overlay file with the following content:
222+
223+
```text
224+
&flash0 {
225+
partitions {
226+
boot_partition: partition@0 {
227+
label = "mcuboot";
228+
reg = <0x0 DT_SIZE_K(64)>;
229+
};
230+
231+
slot0_partition: partition@20000 {
232+
label = "image-0";
233+
reg = <0x20000 DT_SIZE_K(1344)>;
234+
};
235+
236+
slot1_partition: partition@170000 {
237+
label = "image-1";
238+
reg = <0x170000 DT_SIZE_K(1344)>;
239+
};
240+
241+
storage_partition: partition@3b0000 {
242+
label = "storage";
243+
reg = <0x3B0000 DT_SIZE_K(192)>;
244+
};
245+
246+
scratch_partition: partition@3e0000 {
247+
label = "image-scratch";
248+
reg = <0x3E0000 DT_SIZE_K(124)>;
249+
};
250+
};
251+
};
252+
```
253+
254+
### Signing and Flashing the Application
255+
256+
Before flashing, sign the application binary using `imgtool`. Replace `<APP_BIN>` with your application binary (e.g., `nuttx.hex` or `zephyr.bin`), and `signed.bin` will be the output signed image:
257+
258+
```bash
259+
python3 scripts/imgtool.py sign --align 4 -v 0 -H 32 --pad-header -S 0x00150000 <APP_BIN> signed.bin
260+
```
261+
262+
Alternatively, if you installed **imgtool** through **pip**:
263+
264+
```bash
265+
imgtool sign --align 4 -v 0 -H 32 --pad-header -S 0x00150000 <APP_BIN> signed.bin
266+
```
267+
268+
> **Note:** a **Zephyr** image with **MCUboot** compatibility doesn't need the `--pad-header` parameter.
269+
270+
Here is a quick look on what these `imgtool sign` parameters do:
271+
272+
- `--align 4`: Specify the flash device alignment as 4 bytes (32-bit word).
273+
- `-v 0`: Specify the image version (in this case, `0`).
274+
- `-H 32`: Specify the **MCUboot** header size to be added to the image binary.
275+
- `--pad-header`: Indicates to `imgtool` to add the **MCUboot** header into the beginning of the image binary, instead of overwritting it (the **Zephyr** build for some platforms already pads the binary beginning with 0s and may not need this parameter).
276+
- `-S 0x00150000`: Indicates the slot size so the tool can add the **MCUboot** trailer properly.
277+
278+
> **Note:** This step just adds the basic required header, however notice that we didn't add any cryptographic key for security verification yet. It will be covered in the next parts of this **MCUboot** guide series. Refer to the [imgtool documentation](https://docs.mcuboot.com/imgtool.html) for more information.
279+
280+
Flash the signed image to the device at the primary slot address:
281+
282+
```bash
283+
esptool.py -p /dev/ttyUSB0 -b 921600 --before default_reset --after hard_reset --chip esp32 write_flash --flash_mode dio --flash_size detect --flash_freq 40m 0x20000 signed.bin
284+
```
285+
286+
Checking the serial monitor, **MCUboot** should now successfully load and boot the application from the primary slot.
287+
288+
**NuttX's nsh**:
289+
290+
```text
291+
[esp32] [INF] *** Booting MCUboot build v2.3.0-rc2-3-g234c66e6 ***
292+
[esp32] [INF] [boot] chip revision: v3.0
293+
[esp32] [INF] [boot.esp32] SPI Speed : 40MHz
294+
[esp32] [INF] [boot.esp32] SPI Mode : DIO
295+
[esp32] [INF] [boot.esp32] SPI Flash Size : 4MB
296+
[esp32] [INF] [boot] Enabling RNG early entropy source...
297+
[esp32] [INF] Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
298+
[esp32] [INF] Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
299+
[esp32] [INF] Boot source: primary slot
300+
[esp32] [INF] Image index: 0, Swap type: none
301+
[esp32] [INF] Disabling RNG early entropy source...
302+
[esp32] [INF] br_image_off = 0x20000
303+
[esp32] [INF] ih_hdr_size = 0x20
304+
[esp32] [INF] Loading image 0 - slot 0 from flash, area id: 1
305+
[esp32] [INF] DRAM segment: start=0x250a4, size=0xce4, vaddr=0x3ffb2010
306+
[esp32] [INF] IRAM segment: start=0x20080, size=0x5024, vaddr=0x40080000
307+
[esp32] [INF] start=0x40082048
308+
IROM segment aligned lma 0x00040000 vma 0x400d0000 len 0x012afc (76540)
309+
DROM segment aligned lma 0x00030000 vma 0x3f410000 len 0x003060 (12384)
310+
311+
NuttShell (NSH) NuttX-10.4.0
312+
nsh>
313+
```
314+
315+
**Zephyr's hello world**:
316+
317+
```text
318+
[esp32] [INF] *** Booting MCUboot build v2.3.0-rc2-3-g234c66e6 ***
319+
[esp32] [INF] [boot] chip revision: v3.0
320+
[esp32] [INF] [boot.esp32] SPI Speed : 40MHz
321+
[esp32] [INF] [boot.esp32] SPI Mode : DIO
322+
[esp32] [INF] [boot.esp32] SPI Flash Size : 4MB
323+
[esp32] [INF] [boot] Enabling RNG early entropy source...
324+
[esp32] [INF] Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
325+
[esp32] [INF] Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
326+
[esp32] [INF] Boot source: primary slot
327+
[esp32] [INF] Image index: 0, Swap type: none
328+
[esp32] [INF] Disabling RNG early entropy source...
329+
[esp32] [INF] br_image_off = 0x20000
330+
[esp32] [INF] ih_hdr_size = 0x20
331+
[esp32] [INF] Loading image 0 - slot 0 from flash, area id: 1
332+
[esp32] [INF] DRAM segment: start=0x25f10, size=0x1110, vaddr=0x3ffb0000
333+
[esp32] [INF] IRAM segment: start=0x20080, size=0x5e90, vaddr=0x40080000
334+
[esp32] [INF] start=0x400830e0
335+
I (327) boot: IROM : lma=00040000h vma=400d0000h size=036D8h ( 14040) map
336+
I (327) boot: DROM : lma=00030000h vma=3f400000h size=00E94h ( 3732) map
337+
I (343) boot: libc heap size 182 kB.
338+
I (343) spi_flash: detected chip: generic
339+
I (343) spi_flash: flash io: dio
340+
*** Booting Zephyr OS build v4.3.0-rc2-65-g03ce83a18fd5 ***
341+
Hello World! esp32_devkitc/esp32/procpu
342+
```
343+
344+
## Flash Organization
345+
346+
**MCUboot** defines a flash organization where a flash area can contain multiple executable images depending on its boot and update configuration. Each image area contains two image slots: a primary and a secondary. By default, the bootloader only runs an image from the primary slot. The secondary slot is where an incoming image is staged prior to being installed; then its content will be either swapped to the primary slot or overwrite it when updating.
347+
348+
Therefore, we can identify four types of flash areas in the layout:
349+
350+
| AREA | ID | DESCRIPTION |
351+
|------|----|----|
352+
| Bootloader | 0 | Bootloader region (MCUboot) |
353+
| Primary Slot | 1 | Main bootable image |
354+
| Secondary Slot | 2 | Staging area for firmware updates |
355+
| Scratch | 3 | Temporary area for image swapping |
356+
357+
**MCUboot** also supports multiple images, allowing you to define additional image areas with their own primary and secondary slots.
358+
359+
The layout information is stored in the `bootloader.conf` file from the **Espressif Port**. Addresses and sizes can be modified, but the following rules must be followed:
360+
361+
- Bootloader address must be kept since it's where **ESP32** jumps after reset by default.
362+
- None of the slots must overlap.
363+
- Primary and Secondary slots must be the same size.
364+
- Scratch area must be large enough to store at least the largest flash sector that will be swapped.
365+
- Both **MCUboot** and the application must be aware of this layout for correct operation.
366+
367+
{{< alert icon="eye" >}}
368+
Flash organization is configurable and must match between **MCUboot** and the application being booted to ensure proper operation.
369+
{{< /alert >}}
370+
371+
## Conclusion
372+
373+
**MCUboot** provides a solid structure and defines a standard flow for firmware updates and secure boot. These features are already implemented in the bootloader and can be easily enabled without many modifications when developing firmware.
374+
375+
Furthermore, as an open source project, **MCUboot** benefits from development by an interested and diverse community, resulting in faster issue resolution and active development.
376+
377+
In this guide, we covered how to build **MCUboot** bootloader for ESP32, how to sign an image, and how to organize flash properly. For more detailed information, refer to the [official MCUboot documentation](https://docs.mcuboot.com/).
378+
379+
The next step for this series is to understand how updates work in the **MCUboot** and use this feature appropriately.
380+
381+
## References
382+
383+
- https://embarcados.com.br/primeiros-passos-com-esp32-utilizando-mcuboot-como-bootloader/
384+
- https://docs.mcuboot.com/design.html
385+
- https://docs.mcuboot.com/readme-espressif.html
386+
- https://interrupt.memfault.com/blog/mcuboot-overview

0 commit comments

Comments
 (0)