作为一名嵌入式开发者,我最近在STM32F407平台上尝试了Zephyr RTOS的MCUboot引导加载程序。MCUboot作为一款开源的嵌入式系统引导程序,能够实现固件的安全启动、OTA升级和回滚等功能。本文将分享我从零开始搭建MCUboot环境的完整过程,包括开发环境配置、设备树修改、固件签名验证等关键环节。
在传统嵌入式开发中,固件升级往往需要专门的烧录工具,而MCUboot通过标准化的引导机制,使得固件更新就像智能手机安装APP一样简单。它支持A/B双备份系统(即slot0和slot1分区),当新固件验证失败时能自动回滚到旧版本,大大提高了系统可靠性。
我使用的是正点原子探索者开发板(STM32F407ZGT6),搭配J-Link调试器。选择这个平台主要基于三点考虑:
开发环境采用CLion + Zephyr工具链的组合:
bash复制# 安装Zephyr SDK
wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.1/zephyr-sdk-0.16.1_linux-x86_64.tar.xz
tar xvf zephyr-sdk-0.16.1_linux-x86_64.tar.xz
cd zephyr-sdk-0.16.1
./setup.sh
注意:Zephyr环境变量需要正确配置,特别是ZEPHYR_BASE和PATH,否则会导致编译错误。建议将以下内容添加到~/.bashrc:
export ZEPHYR_BASE=~/zephyrproject/zephyr
export PATH=$PATH:~/zephyr-sdk-0.16.1/sysroots/x86_64-pokysdk-linux/usr/bin
从Zephyr官方仓库获取MCUboot子模块:
bash复制west init zephyrproject
cd zephyrproject
west update
在CLion中导入项目时,关键是要正确设置工具链路径。我遇到的一个典型问题是CMake无法自动识别工具链,需要手动指定:
STM32F407ZGT6的Flash布局如下:
code复制0x08000000 - 0x08003FFF 16KB Sector 0 (Bootloader)
0x08004000 - 0x08007FFF 16KB Sector 1
0x08008000 - 0x0800BFFF 16KB Sector 2
0x0800C000 - 0x0800FFFF 16KB Sector 3
0x08010000 - 0x0801FFFF 64KB Sector 4
0x08020000 - 0x0803FFFF 128KB Sector 5 (Slot0)
0x08040000 - 0x0805FFFF 128KB Sector 6 (Slot1)
0x08060000 - 0x0807FFFF 128KB Sector 7 (Scratch)
对应的设备树配置如下(关键部分):
c复制&flash0 {
partitions {
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 DT_SIZE_K(128)>; // 占用前128KB
read-only;
};
slot0_partition: partition@20000 {
label = "image-0";
reg = <0x00020000 DT_SIZE_K(128)>; // 128KB
};
slot1_partition: partition@40000 {
label = "image-1";
reg = <0x00040000 DT_SIZE_K(128)>; // 128KB
};
scratch_partition: partition@60000 {
label = "image-scratch";
reg = <0x00060000 DT_SIZE_K(128)>; // 最后128KB
};
};
};
在overlay文件中需要明确指定控制台设备和Flash接口:
c复制&usart1 {
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
current-speed = <115200>;
status = "okay";
};
&spi1 {
status = "okay";
pinctrl-0 = <&spi1_sck_pb3 &spi1_miso_pb4 &spi1_mosi_pb5>;
cs-gpios = <&gpiob 14 GPIO_ACTIVE_LOW>;
w25q128: w25q128@0 {
compatible = "jedec,spi-nor";
size = <DT_SIZE_M(128)>;
spi-max-frequency = <4000000>;
jedec-id = [ef 40 18];
};
};
在bootloader目录下执行编译前,需要特别注意几个关键配置:
bash复制# 禁用自动计算扇区数(避免分区大小不匹配)
CONFIG_BOOT_MAX_IMG_SECTORS_AUTO=n
CONFIG_BOOT_MAX_IMG_SECTORS=16
# 启用串口日志输出
CONFIG_LOG=y
CONFIG_UART_CONSOLE=y
编译命令示例:
bash复制west build -b stm32f4_disco bootloader/mcuboot/boot/zephyr
使用J-Link Commander时需要注意以下参数:
烧录后通过串口观察启动日志,正常应看到类似输出:
code复制[INF] Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
[INF] Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
[INF] Bootloader chainload address offset: 0x20000
[INF] Jumping to the first image slot
应用程序必须启用MCUboot支持并配置签名密钥:
bash复制# prj.conf关键配置
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="bootloader/mcuboot/root-rsa-2048.pem"
设备树分区必须与bootloader严格一致,特别是分区地址和大小:
c复制&flash0 {
partitions {
slot0_partition: partition@20000 {
reg = <0x00020000 DT_SIZE_K(128)>;
};
// 其他分区定义...
};
};
使用MCUboot提供的imgtool进行签名:
bash复制python3 scripts/imgtool.py sign \
--key bootloader/mcuboot/root-rsa-2048.pem \
--header-size 0x200 \
--align 8 \
--version 1.0.0 \
--slot-size 0x20000 \
zephyr/zephyr.bin signed.bin
生成的可执行文件需要通过以下命令转换为hex格式烧录:
bash复制arm-none-eabi-objcopy -I binary -O ihex signed.bin signed.hex
问题现象:MCUboot启动后卡住,无日志输出
问题现象:提示"Image hash mismatch"
启动时间优化:
空间优化:
可靠性增强:
通过串口实现简单OTA的代码示例:
c复制void ota_update(const struct device *flash_dev, uint8_t *data, size_t len)
{
const struct flash_area *fa;
int rc = flash_area_open(FLASH_AREA_ID(image_1), &fa);
if (rc == 0) {
rc = flash_area_erase(fa, 0, fa->fa_size);
if (rc == 0) {
rc = flash_area_write(fa, 0, data, len);
if (rc == 0) {
LOG_INF("OTA update successful");
boot_request_upgrade(BOOT_UPGRADE_TEST);
}
}
flash_area_close(fa);
}
}
启用加密验证:
bash复制CONFIG_MCUBOOT_ENCRYPT_RSA=y
CONFIG_MCUBOOT_ENC_IMAGES=y
防回滚保护:
bash复制CONFIG_MCUBOOT_ANTI_ROLLBACK=y
CONFIG_MCUBOOT_IMAGE_VERSION=2
硬件安全扩展:
在实际项目中,我发现MCUboot的调试信息非常关键,建议在开发阶段保持CONFIG_LOG_LEVEL_DBG,而在量产时调整为CONFIG_LOG_LEVEL_ERR以节省空间。另外,分区大小的计算要预留至少10%的余量,因为签名后的固件会比原始bin文件大不少