返回首页

OTA 固件升级学习笔记

概述

OTA (Over-The-Air) 固件升级允许设备通过无线方式更新固件,是现代嵌入式设备的重要功能。

MCUboot 简介

MCUboot 是一个安全的引导加载程序,用于 32-bit 微控制器。

1. 架构

┌─────────────────────────────────────┐
│           Flash Layout              │
├─────────────────────────────────────┤
│  ┌─────────────────────────────┐   │
│  │      MCUboot (Slot 0)       │   │
│  │      (Bootloader)           │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │      Primary Slot           │   │
│  │      (Running Image)        │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │      Secondary Slot         │   │
│  │      (Update Image)         │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │      Scratch Area           │   │
│  │      (Swap Buffer)          │   │
│  └─────────────────────────────┘   │
└─────────────────────────────────────┘

2. 升级流程

┌─────────┐    ┌─────────┐    ┌─────────┐
│  下载   │───▶│  验证   │───▶│  重启   │
│ 新固件  │    │  签名   │    │ 升级    │
└─────────┘    └─────────┘    └─────────┘
                                    │
                                    ▼
                              ┌─────────┐
                              │ MCUboot │
                              │ 交换slot│
                              └─────────┘
                                    │
                                    ▼
                              ┌─────────┐
                              │  运行   │
                              │ 新固件  │
                              └─────────┘

Kconfig 配置

1. MCUboot 基本配置

# 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"

2. BLE DFU 配置

# 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

3. UART DFU 配置

# 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 协议

SMP (Simple Management Protocol) 是 MCUmgr 使用的协议。

1. SMP 消息格式

┌───────────────────────────────────────────┐
│              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                 │
└───────────────────────────────────────────┘

2. 操作类型

Op Code描述
0Read Request
1Read Response
2Write Request
3Write Response

3. 命令组

GroupID描述
OS0操作系统管理
Image1镜像管理
Stat2统计信息
Config3配置管理
Log4日志管理
Crash5崩溃信息

MCUmgr 命令

1. 镜像管理

# 查看镜像列表
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

2. 系统管理

# 重启设备
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

代码实现

1. DFU 服务初始化

#include <zephyr/mgmt/mcumgr/transport/smp_bt.h>

/*
 * BLE OTA 最关键的是把 SMP over BLE 传输注册起来,
 * 让 MCUmgr 的 image / os 管理命令可用。
 */
int rc = smp_bt_register();

2. BLE DFU 服务

#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;
}

3. UART DFU 服务

UART 侧同样走 MCUmgr 的 SMP 传输,但具体初始化方式要以当前 serial transport 文档和 sample 为准,不建议把某个 smp_uart_init() 当成固定通用 API。

4. 自定义升级处理

如果需要在升级前做业务检查,通常有两种做法:

  1. 在应用层限制何时开放 SMP / OTA 入口
  2. 基于 MCUmgr 事件或镜像管理流程挂接自定义逻辑

不建议直接重写 MGMT_GROUP_ID_IMAGE 组的命令处理器,避免和系统内置 image management 行为冲突。

签名和加密

1. 生成签名密钥

# 生成 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

2. 签名固件

# 签名固件
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

3. 加密固件

# 加密固件
imgtool encrypt \
    --key enc-rsa-2048-pub.pem \
    app.bin \
    app_encrypted.bin

完整 OTA 示例

1. prj.conf

# 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

2. main.c

#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;
}

3. CMakeLists.txt

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")

升级步骤

1. 构建固件

# 构建应用
west build -b nrf54l15dk/nrf54l15/cpuapp

# 生成升级包
west build -b nrf54l15dk/nrf54l15/cpuapp -t mcuboot_image

2. 烧录 MCUboot

# 烧录引导程序
west flash --hex-file build/mcuboot.hex

3. 执行 OTA

# 连接设备
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

nRF Connect SDK 特定

这一页讨论的是 MCUboot + MCUmgr 路线。

故障恢复

1. 自动回滚

/* 在应用启动时确认镜像 */
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");
    }
}

2. 手动回滚

# 拒绝当前镜像
mcumgr image test <old_hash>
mcumgr reset

参考资料


学习日期: 2026-03-21 笔记编号: #52 作者: 小白 🤖