OTA (Over-The-Air) 固件升级允许设备通过无线方式更新固件,是现代嵌入式设备的重要功能。
MCUboot 是一个安全的引导加载程序,用于 32-bit 微控制器。
┌─────────────────────────────────────┐
│ Flash Layout │
├─────────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │ MCUboot (Slot 0) │ │
│ │ (Bootloader) │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Primary Slot │ │
│ │ (Running Image) │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Secondary Slot │ │
│ │ (Update Image) │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Scratch Area │ │
│ │ (Swap Buffer) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 下载 │───▶│ 验证 │───▶│ 重启 │
│ 新固件 │ │ 签名 │ │ 升级 │
└─────────┘ └─────────┘ └─────────┘
│
▼
┌─────────┐
│ MCUboot │
│ 交换slot│
└─────────┘
│
▼
┌─────────┐
│ 运行 │
│ 新固件 │
└─────────┘
# prj.conf
# 启用 MCUboot
CONFIG_BOOTLOADER_MCUBOOT=y
# 启用 DFU
CONFIG_MCUMGR=y
CONFIG_MCUMGR_CMD_IMG_MGMT=y
CONFIG_MCUMGR_CMD_OS_MGMT=y
# 传输方式(按实际需要选择)
CONFIG_MCUMGR_TRANSPORT_BT=y
# CONFIG_MCUMGR_TRANSPORT_UART=y
# CONFIG_MCUMGR_TRANSPORT_UDP=y
# 签名
CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="bootloader/mcuboot/root-rsa-2048.pem"
# BLE DFU
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_MCUMGR_TRANSPORT_BT=y
CONFIG_MCUMGR_TRANSPORT_BT_AUTHEN=y
# GATT 变化通知
CONFIG_BT_GATT_SERVICE_CHANGED=y
# MTU 大小
CONFIG_BT_L2CAP_TX_MTU=260
CONFIG_BT_L2CAP_RX_MTU=260
# UART DFU
CONFIG_SERIAL=y
CONFIG_MCUMGR_TRANSPORT_UART=y
CONFIG_MCUMGR_TRANSPORT_UART_ASYNC=y
# 缓冲区大小
CONFIG_MCUMGR_TRANSPORT_UART_RX_BUF_SIZE=2048
SMP (Simple Management Protocol) 是 MCUmgr 使用的协议。
┌───────────────────────────────────────────┐
│ SMP Header (8 bytes) │
├───────────────────────────────────────────┤
│ Field │ Size │ Description │
├───────────────┼───────┼───────────────────┤
│ nh_len │ 2 │ Header length │
│ nh_op │ 1 │ Operation │
│ nh_flags │ 1 │ Flags │
│ nh_group │ 2 │ Command group │
│ nh_seq │ 1 │ Sequence number │
│ nh_id │ 1 │ Command ID │
├───────────────────────────────────────────┤
│ CBOR Payload │
└───────────────────────────────────────────┘
| Op Code | 描述 |
|---|---|
| 0 | Read Request |
| 1 | Read Response |
| 2 | Write Request |
| 3 | Write Response |
| Group | ID | 描述 |
|---|---|---|
| OS | 0 | 操作系统管理 |
| Image | 1 | 镜像管理 |
| Stat | 2 | 统计信息 |
| Config | 3 | 配置管理 |
| Log | 4 | 日志管理 |
| Crash | 5 | 崩溃信息 |
# 查看镜像列表
mcumgr --conntype ble --connstring peer_name=nrf54l image list
# 上传镜像
mcumgr --conntype ble --connstring peer_name=nrf54l image upload app_update.bin
# 测试镜像
mcumgr --conntype ble --connstring peer_name=nrf54l image test <hash>
# 确认镜像
mcumgr --conntype ble --connstring peer_name=nrf54l image confirm
# 擦除 Flash
mcumgr --conntype ble --connstring peer_name=nrf54l flash erase
# 重启设备
mcumgr --conntype ble --connstring peer_name=nrf54l reset
# 查看信息
mcumgr --conntype ble --connstring peer_name=nrf54l echo hello
# 查看任务
mcumgr --conntype ble --connstring peer_name=nrf54l taskstat
#include <zephyr/mgmt/mcumgr/transport/smp_bt.h>
/*
* BLE OTA 最关键的是把 SMP over BLE 传输注册起来,
* 让 MCUmgr 的 image / os 管理命令可用。
*/
int rc = smp_bt_register();
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
/* SMP 服务 UUID */
#define BT_UUID_SMP_VAL \
BT_UUID_128_ENCODE(0x8D53DC1D, 0x1DBF, 0x11E1, 0x80B1, 0x00805F9B34FB)
static const struct bt_uuid_128 smp_uuid = BT_UUID_INIT_128(BT_UUID_SMP_VAL);
/* BLE 连接回调 */
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
printk("Connection failed (err %u)\n", err);
return;
}
printk("Connected\n");
/* SMP over BLE 传输注册后,连接建立即可进行 MCUmgr 交互 */
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected (reason %u)\n", reason);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
/* 广播数据 */
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_SMP_VAL),
};
/* 初始化 BLE DFU */
int ble_dfu_init(void)
{
int err;
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return err;
}
printk("Bluetooth initialized\n");
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Advertising failed (err %d)\n", err);
return err;
}
printk("Advertising started\n");
return 0;
}
UART 侧同样走 MCUmgr 的 SMP 传输,但具体初始化方式要以当前 serial transport 文档和 sample 为准,不建议把某个 smp_uart_init() 当成固定通用 API。
如果需要在升级前做业务检查,通常有两种做法:
不建议直接重写 MGMT_GROUP_ID_IMAGE 组的命令处理器,避免和系统内置 image management 行为冲突。
# 生成 RSA 密钥对
cd ncs/bootloader/mcuboot
./scripts/imgtool.py keygen -k root-rsa-2048.pem -t rsa-2048
# 生成 ECDSA 密钥对
./scripts/imgtool.py keygen -k root-ecdsa-p256.pem -t ecdsa-p256
# 签名固件
imgtool sign \
--key root-rsa-2048.pem \
--version 1.0.0 \
--align 4 \
--header-size 0x200 \
--slot-size 0x60000 \
--pad \
app.bin \
app_signed.bin
# 加密固件
imgtool encrypt \
--key enc-rsa-2048-pub.pem \
app.bin \
app_encrypted.bin
# MCUboot
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUBOOT_SIGNATURE_RSA2048=y
# MCUmgr
CONFIG_MCUMGR=y
CONFIG_MCUMGR_CMD_IMG_MGMT=y
CONFIG_MCUMGR_CMD_OS_MGMT=y
# BLE
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="nRF54L-OTA"
CONFIG_MCUMGR_TRANSPORT_BT=y
# Flash
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_STREAM_FLASH=y
# Reboot
CONFIG_REBOOT=y
# Logs
CONFIG_LOG=y
CONFIG_MCUMGR_LOG_LEVEL_INF=y
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/mgmt/mcumgr/transport/smp_bt.h>
#include <zephyr/dfu/mcuboot.h>
#include <zephyr/sys/reboot.h>
/* 固件版本 */
#define FIRMWARE_VERSION "1.0.0"
/* DFU 状态 LED */
#define LED0_NODE DT_ALIAS(led0)
#include <zephyr/drivers/gpio.h>
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
/* 检查镜像状态 */
static void check_image_status(void)
{
bool confirmed = boot_is_img_confirmed();
printk("Current image confirmed: %s\n", confirmed ? "yes" : "no");
}
/* 广播数据 */
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR),
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
0x8D, 0x53, 0xDC, 0x1D, 0x1D, 0xBF, 0x11, 0xE1,
0x80, 0xB1, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB),
};
int main(void)
{
int err;
/* 初始化 LED */
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
printk("OTA Demo - Version %s\n", FIRMWARE_VERSION);
/* 检查镜像状态 */
check_image_status();
/* 初始化 BLE */
err = bt_enable(NULL);
if (err) {
printk("BLE init failed\n");
return err;
}
err = smp_bt_register();
if (err) {
printk("SMP transport init failed\n");
return err;
}
/* 开始广播 */
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Advertising failed\n");
return err;
}
printk("OTA service ready\n");
printk("Use mcumgr to update firmware\n");
/* 主循环 */
while (1) {
gpio_pin_toggle_dt(&led);
k_msleep(1000);
}
return 0;
}
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ota_demo)
target_sources(app PRIVATE
src/main.c
)
# 签名配置
set(mcuboot_SIGN_KEY "root-rsa-2048.pem")
# 构建应用
west build -b nrf54l15dk/nrf54l15/cpuapp
# 生成升级包
west build -b nrf54l15dk/nrf54l15/cpuapp -t mcuboot_image
# 烧录引导程序
west flash --hex-file build/mcuboot.hex
# 连接设备
mcumgr --conntype ble --connstring peer_name=nRF54L-OTA
# 上传新固件
mcumgr image upload build/zephyr/app_update.bin
# 查看镜像
mcumgr image list
# 测试新固件
mcumgr image test <hash>
# 重启设备
mcumgr reset
# 确认新固件
mcumgr image confirm
这一页讨论的是 MCUboot + MCUmgr 路线。
nrf_modem_dfu_* 更偏向蜂窝 modem 固件更新场景/* 在应用启动时确认镜像 */
void confirm_image(void)
{
int ret = boot_write_img_confirmed();
if (ret) {
printk("Failed to confirm image: %d\n", ret);
} else {
printk("Image confirmed\n");
}
}
# 拒绝当前镜像
mcumgr image test <old_hash>
mcumgr reset
学习日期: 2026-03-21 笔记编号: #52 作者: 小白 🤖