固件升级指南

OTA 概述

OTA (Over-the-Air,空中下载技术),是指通过网络连接向远程设备传输新固件并完成更新的过程。 尽管其名称中包含 "空中"(暗示无线连接),但在实际应用中,通过有线连接(例如以太网)接收的更新,通常也被称为 OTA 更新。

Zephyr 支持多种 OTA 升级方案,具体如下表所示:

示例

路径

Golioth

外部仓库

Eclipse hawkBit

zephyr/samples/subsys/mgmt/hawkbit/

UpdateHub

zephyr/samples/subsys/mgmt/updatehub/

SMP Server

zephyr/samples/subsys/mgmt/mcumgr/smp_svr/

Lightweight M2M (LWM2M)

zephyr/samples/net/lwm2m_client/

其中,SMP Server 示例提供了一套完整的 OTA 升级方案,它结合 MCUboot 安全引导程序与 MCUmgr 设备管理框架,可实现安全可靠的固件升级。下文将基于该示例介绍 OTA 升级流程。

关键组件

实现一次安全可靠的 OTA 更新,主要依赖以下三个组件协同工作:

  • MCUboot:一个开源的、安全的引导加载程序。它在设备启动时负责验证应用固件的完整性与合法性,并管理 Flash 中固件分区的切换与升级流程。

  • SMP Server:内置于您应用程序中的 Simple Management Protocol 服务。它负责接收并处理来自外部管理工具 MCUmgr 的各种命令,包括固件上传、状态查询等。

  • MCUmgr:运行在您开发主机上的客户端管理工具。它可以通过 UART/BT/UDP 等多种方式,向设备端的 SMP Server 发送指令,从而触发和控制整个 OTA 流程。

模块层级图

实现安全 OTA 更新会涉及以下 Zephyr 子模块,层级关系如下图所示:

../_images/zephyr_ota_subsystem.svg

升级流程

基于上述 关键组件,典型的 OTA 升级流程如下图所示:

../_images/zephyr_ota_upgrade_flow_zh.svg

备注

固件存储布局

Flash 分区映射

Flash map 是设备闪存的“分区表”,它将设备上的 Flash 划分为多个独立区域(flash area),并为每个区域分配一个数字 ID。 MCUboot 和应用程序通过这些 ID 找到具体的物理地址和大小,进行读写、擦除、升级等操作。

通常,每个固件会分配两个槽位(Slots):主槽(Primary slot) 和 次槽(Secondary slot)。 以下是两个固件场景下的 Flash 分区配置示例:

设备树中 Flash 布局
&flash0 {
   reg = <0x103FF000 DT_SIZE_M(4)>;
   status = "okay";
   partitions {
      compatible = "fixed-partitions";
      #address-cells = <1>;
      #size-cells = <1>;

      /* flash area ID 0 */
      boot_partition: partition@0 {
         label = "bootloader";
         reg = <0x00000000 0x00014000>;
         read-only;
      };

      /* 为 AP(固件 0)分配 主槽(slot0_partition)和 次槽(slot1_partition) */
      /* flash area ID 1 */
      slot0_partition: partition@14000 {
         label = "image-0";
         reg = <0x00014000 0x00080000>;
      };

      /* flash area ID 2 */
      slot1_partition: partition@94000 {
         label = "image-1";
         reg = <0x00094000 0x00080000>;
      };

      /* 为 NP(固件 1) 分配 主槽(slot2_partition)和 次槽(slot3_partition) */
      /* flash area ID 3 */
      slot2_partition: partition@114000 {
         label = "image-2";
         reg = <0x00114000 0x00080000>;
      };

      /* flash area ID 4 */
      slot3_partition: partition@194000 {
         label = "image-3";
         reg = <0x00194000 0x00080000>;
      };
   };
};

固件格式

在 Zephyr 的 OTA 场景中,由 MCUboot 引导的应用固件必须符合 MCUboot 定义的固件格式,包含: Firmware HeaderFirmware BodyTLV Area (Optional)Firmware Trailer。 示意图如下所示:

备注

  • 图示为应用固件的格式要求,并非 MCUboot 自身的固件格式。

TLV 区(类型-长度-值)

TLV 区域位于固件主体之后,属于可选部分,通常用于存储哈希值、签名、安全计数器等信息。

若存在受保护 TLV 区域,则必须包含 IMAGE_TLV_PROT_INFO_MAGIC 标识头,且受保护 TLV 区域会参与哈希计算;若不存在,则哈希仅覆盖 “固件头 + 固件正文”。

为保证回滚保护有效, IMAGE_TLV_SEC_CNT, 0x50 需写入受保护 TLV 区域。使用 imgtool 的 --security-counter 选项会将其放入受保护 TLV 区。

TLV 格式
#define IMAGE_TLV_INFO_MAGIC        0x6907
#define IMAGE_TLV_PROT_INFO_MAGIC   0x6908

/** Image TLV header.  All fields in little endian. */
STRUCT_PACKED image_tlv_info {
   uint16_t it_magic;
   uint16_t it_tlv_tot;                /* size of TLV area (including tlv_info header) */
};

/** Image trailer TLV format. All fields in little endian. */
STRUCT_PACKED image_tlv {
   uint16_t it_type;                   /* IMAGE_TLV_[...]. */
   uint16_t it_len;                    /* Data length (not including TLV header). */
};
与 OTA 相关的常用 TLV 类型
#define IMAGE_TLV_KEYHASH           0x01   /* hash of the public key */
#define IMAGE_TLV_SHA256            0x10   /* SHA256 of image hdr and body */
#define IMAGE_TLV_RSA2048_PSS       0x20   /* RSA2048 of hash output */
#define IMAGE_TLV_ECDSA224          0x21   /* ECDSA of hash output - Not supported anymore */
#define IMAGE_TLV_ECDSA_SIG         0x22   /* ECDSA of hash output */
#define IMAGE_TLV_RSA3072_PSS       0x23   /* RSA3072 of hash output */
#define IMAGE_TLV_ED25519           0x24   /* ED25519 of hash output */
#define IMAGE_TLV_ENC_RSA2048       0x30   /* Key encrypted with RSA-OAEP-2048 */
#define IMAGE_TLV_ENC_KW            0x31   /* Key encrypted with AES-KW-128 or 256 */
#define IMAGE_TLV_ENC_EC256         0x32   /* Key encrypted with ECIES-P256 */
#define IMAGE_TLV_ENC_X25519        0x33   /* Key encrypted with ECIES-X25519 */
#define IMAGE_TLV_SEC_CNT           0x50   /* security counter */

固件尾部( Trailer )

该区域用于记录固件交换状态等元数据,位于槽位末尾,不能用于存放固件正文。

固件 Trailer 结构如下(以字节为单位):

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~                                                               ~
~    Swap status (BOOT_MAX_IMG_SECTORS * min-write-size * s)    ~
~                                                               ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Encryption key 0 (16 octets) [*]              |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    0xff padding as needed                     |
|  (BOOT_MAX_ALIGN minus 16 octets from Encryption key 0) [*]   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Encryption key 1 (16 octets) [*]              |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    0xff padding as needed                     |
|  (BOOT_MAX_ALIGN minus 16 octets from Encryption key 1) [*]   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Swap size (4 octets)                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    0xff padding as needed                     |
|        (BOOT_MAX_ALIGN minus 4 octets from Swap size)         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   Swap info   |  0xff padding (BOOT_MAX_ALIGN minus 1 octet)  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   Copy done   |  0xff padding (BOOT_MAX_ALIGN minus 1 octet)  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   Image OK    |  0xff padding (BOOT_MAX_ALIGN minus 1 octet)  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    0xff padding as needed                     |
|         (BOOT_MAX_ALIGN minus 16 octets from MAGIC)           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       MAGIC (16 octets)                       |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
参数说明
BOOT_MAX_IMG_SECTORS:

默认值为 slot0_partition 所包含的扇区数量。

min-write-size:

Flash 最小写入粒度。

s:

s = 3(Swap_using_move),s= 2(Swap_using_offset

Swap status:

记录每个扇区的 交换状态,用于掉电后恢复升级流程。

Encryption keys:

可选加密密钥,仅在开启固件加密时存在。

Swap size:

本次交换需要搬移的总数据大小,包含固件正文与 TLV 区域。

Swap info:

有效长度为 1 字节,按 BOOT_MAX_ALIGN 对齐;低 4 位为 交换类型,高 4 位为固件编号。

Copy done:

有效长度为 1 字节,按 BOOT_MAX_ALIGN 对齐;表示复制阶段完成状态:0x01=Set,0xFF=Unset。

Image OK:

有效长度为 1 字节,按 BOOT_MAX_ALIGN 对齐;表示新固件是否已确认,未确认将于下次启动回退。0x01=Set,0xFF=Unset。

MAGIC:

16 字节,用于标记尾部区域是否有效。

  • BOOT_MAX_ALIGN=8 时为固定 16 字节模式:

    const union boot_img_magic_t boot_img_magic = {
       .val = {
          0x77, 0xc2, 0x95, 0xf3,
          0x60, 0xd2, 0xef, 0x7f,
          0x35, 0x52, 0x50, 0x0f,
          0x2c, 0xb6, 0x79, 0x80
       }
    };
    
  • 否则,前 2 字节为对齐值,后 14 字节固定模式:

    const union boot_img_magic_t boot_img_magic = {
       .align = BOOT_MAX_ALIGN,
       .magic = {
          0x2d, 0xe1,
          0x5d, 0x29, 0x41, 0x0b,
          0x8d, 0x77, 0x67, 0x9c,
          0x11, 0x0f, 0x1f, 0x8a
       }
    };
    

备注

  • 该区域位于固件槽位的尾部,而不是紧跟固件正文。因此分配的槽位需要预留 Trailer 空间,并按扇区对齐。

OTA 升级机制

MCUboot 支持多种固件升级方式,如下表所示。详细说明可参考 MCUboot 官网介绍

升级方式

特点

Upgrade only

将新固件直接覆盖旧固件,不进行交换,不可回滚

Swap using scratch

通过 scratch 分区做中转,完成新旧固件交换,可回滚

Swap using offset

依靠分区内的偏移布局进行交换,无需 scratch 分区,可回滚

Swap using move

通过移动数据完成交换,无需 scratch 分区,可回滚

Direct XIP

直接原地执行,无需交换

RAM load

将固件完整加载到 RAM 后执行

Firmware loader

通过专用加载器进行升级

当前支持以下两种升级方式:

Swap_using_move

Swap_using_move 是一种无需临时存储区(scratch)的固件交换算法,适用于主 / 次双槽 OTA 升级场景。 其核心是通过将主槽扇区整体后移一位,再交替复制主槽与次槽内容,完成固件交换。

可通过配置宏 CONFIG_BOOT_SWAP_USING_MOVE 启用此升级方式。采用 --sysbuild 时需在应用程序 sysbuild.conf 中配置 SB_CONFIG_MCUBOOT_MODE_SWAP_USING_MOVE=y

工作原理: 1. 扇区下移:先将主槽所有扇区向下移动一个扇区位置。 2. 交替复制:从第 1 个扇区开始,依次执行: 3. 将次槽第 N 个扇区复制到主槽第 N 个扇区; 4. 重复直至所有扇区交换完成。

从图示可知,每个扇区的交换过程包含 3 个状态:

  1. 主槽[N] → 主槽[N+1]已完成

  2. 次槽[N] → 主槽[N] 已完成

  3. 主槽[N+1] → 次槽[N] 已完成

Swap_using_offset

Swap_using_offset 是一种无需临时存储区(scratch)的固件交换算法,是 Swap_using_move 的增强版。 其核心是利用次槽中的一个扇区作为临时缓冲,交替复制主、次槽内容,完成固件交换。

可通过配置宏 CONFIG_BOOT_SWAP_USING_OFFSET 启用此升级方式;采用 --sysbuild 时需在应用程序 sysbuild.conf 中配置 SB_CONFIG_MCUBOOT_MODE_SWAP_USING_OFFSET=y

工作原理: 1. 固件放置:待升级的新固件必须从次槽的第二个扇区开始存放。 2. 交替复制:从第 1 个扇区开始,依次执行: 3. 将主槽第 N 个扇区复制到次槽第 N 个扇区; 4. 将次槽第 N+1 个扇区复制到主槽第 N 个扇区; 5. 重复直至所有扇区交换完成。

从图示可知,每个扇区的交换过程包含 2 个状态:

  1. 主槽[N] → 次槽[N] 已完成

  2. 次槽[N+1] → 主槽[N] 已完成

交换管理机制

交换类型

在正常升级流程中(未异常掉电 / 中断),MCUboot 会根据固件 trailer 中的标志位决定执行的交换类型(swap type)。 受 Flash 硬件特性限制,trailer 的设计并不直观,直接读取原始字节难以直接解析设备状态。 因此,MCUboot 将“各种可能的 trailer 组合状态”映射为“交换类型”,并按优先级顺序判定。

State I 场景 I(仅 Swap_using_offset 模式):在执行 REVERT 前的风险窗口,记录 magic 和 copy-done 到次槽,下次启动会继续执行 REVERT。

State I (swap using offset only)
                 | primary slot | secondary slot |
-----------------+--------------+----------------|
           magic | Any          | Good           |
        image-ok | Any          | Unset          |
       copy-done | Any          | Set            |
-----------------+--------------+----------------'
 result: BOOT_SWAP_TYPE_REVERT                   |
-------------------------------------------------'

State II 场景:次槽固件有效但未确认,执行“测试交换”。系统会将次槽固件交换到主槽并启动。 新固件需在应用中设置 image-ok 完成确认;若未确认,下一次启动将自动回退(REVERT)。

State II
                 | primary slot | secondary slot |
-----------------+--------------+----------------|
           magic | Any          | Good           |
        image-ok | Any          | Unset          |
       copy-done | Any          | Any            |
-----------------+--------------+----------------'
 result: BOOT_SWAP_TYPE_TEST                     |
-------------------------------------------------'

State III 场景:次槽固件已预先标记 image-ok,执行“永久交换”,升级完成后不会回退。

State III
                 | primary slot | secondary slot |
-----------------+--------------+----------------|
           magic | Any          | Good           |
        image-ok | Any          | 0x01           |
       copy-done | Any          | Any            |
-----------------+--------------+----------------'
 result: BOOT_SWAP_TYPE_PERM                     |
-------------------------------------------------'

State IV 场景:主槽复制已完成(copy-done=Set),但新固件未确认(image-ok=Unset),因此本次启动执行回退,恢复为旧固件。

State IV
                 | primary slot | secondary slot |
-----------------+--------------+----------------|
           magic | Good         | Any            |
        image-ok | 0xff         | Any            |
       copy-done | 0x01         | Any            |
-----------------+--------------+----------------'
 result: BOOT_SWAP_TYPE_REVERT                   |
-------------------------------------------------'

若以上状态 State I~IV 均不匹配,MCUboot 不会执行固件交换,而是进入 State V,选择三种交换类型之一。

State V
                 | primary slot | secondary slot |
-----------------+--------------+----------------|
           magic | Any          | Any            |
        image-ok | Any          | Any            |
       copy-done | Any          | Any            |
-----------------+--------------+----------------'
 result: BOOT_SWAP_TYPE_NONE,                    |
         BOOT_SWAP_TYPE_FAIL, or                 |
         BOOT_SWAP_TYPE_PANIC                    |
-------------------------------------------------'

备注

  • State 编号越小优先级越高。一旦匹配某一状态,立即确定对应的交换类型并停止继续匹配。

  • 交换类型:

    • BOOT_SWAP_TYPE_NONE:无升级任务,直接启动主槽固件。

    • BOOT_SWAP_TYPE_TEST:测试运行;若未通过设置 image-ok=0x01 确认,下次启动将触发回退。

    • BOOT_SWAP_TYPE_PERM:永久交换,不需要设置 image-ok=0x01 确认。

    • BOOT_SWAP_TYPE_REVERT:上次 TEST 未确认,回退至原有旧固件。

    • BOOT_SWAP_TYPE_FAIL:目标固件无效(如校验失败)。

    • BOOT_SWAP_TYPE_PANIC:交换过程出现不可恢复错误,启动终止。

交换状态

交换状态(Swap status)用于掉电恢复,记录在主槽的 Trailer 。 若固件交换过程中发生异常重启,MCUboot 可根据已记录的交换状态恢复现场,继续完成未结束的交换流程。 该区域由一系列单字节状态记录组成,每条记录独立写入,并按照设备的最小写入粒度进行对齐填充。 典型交换流程如下:

  1. 初始化: 同时擦除主、次槽 Trailer;然后在主槽 Trailer 写入 swap_info、swap_size 及 MAGIC 标记。

  2. 逐扇区交换:仅对固件实际占用的扇区(包含 TLV 区域)进行交换,并根据所选算法记录每个扇区的状态。

  3. 收尾:交换完成后,在主槽 Trailer 中写入 copy done,标记交换流程结束。

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|sec127,state 0 |sec127,state 1 |sec127,state 2 |sec126,state 0 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|sec126,state 1 |sec126,state 2 |sec125,state 0 |sec125,state 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|sec125,state 2 |                                               |
+-+-+-+-+-+-+-+-+                                               +
~                                                               ~
~               [Records for indices 124 through 1              ~
~                                                               ~
~               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~               |sec000,state 0 |sec000,state 1 |sec000,state 2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

备注

  • 图示假设 min-write-size = 1,每个“扇区索引”对应 3 个状态记录,适用于 Swap_using_move 模式;而在 Swap_using_offset 模式下,每个扇区仅有 2 个状态记录,所需总空间更小。

防回滚功能

为了防止设备被降级到有已知漏洞的固件版本,MCUboot 支持防回滚策略,分为 软件防回滚 与 硬件防回滚。 其中 软件防回滚 又分为基于版本号(version) 和基于安全计数器(security counter) 两种实现方式。

软件防回滚(基于版本号)

基于版本号的软件防回滚,只有 !BOOT_DIRECT_XIP 升级方式下存在。该方式为纯软件实现,无法抵御物理攻击(如 Flash 擦除、重编程)。

  1. 原理:

    • MCUboot 每次启动对固件版本号进行比较。

    • 若新固件版本号低于当前运行固件版本,则拒绝启动。

    • 若新固件版本号大于或等于当前版本,则允许启动。

  2. 配置:

    CONFIG_MCUBOOT_DOWNGRADE_PREVENTION=y
    
  3. 固件签名时嵌入版本号:

    # 示例:在应用程序 prj.conf 或板卡特定配置中设置
    CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="0.0.2"
    

软件防回滚(基于安全计数器)

基于 security counter 的软件防回滚,只有 (BOOT_SWAP_USING_MOVE || BOOT_SWAP_USING_SCRATCH || BOOT_SWAP_USING_OFFSET) 方式下存在。 该方式为纯软件实现,无法抵御物理攻击(如 Flash 擦除、重编程)。

  1. 原理:

    • MCUboot 每次启动对 security counter 进行比较。

    • 若新固件的 security counter 小于当前运行固件的 security counter,则拒绝启动。

    • 若新固件的 security counter 大于或等于当前运行固件的 security counter,则允许启动。

  2. 配置:

    CONFIG_MCUBOOT_DOWNGRADE_PREVENTION=y        # security counter 选项开启依赖于该配置
    CONFIG_MCUBOOT_DOWNGRADE_PREVENTION_SECURITY_COUNTER=y
    
  3. 固件签名时嵌入 security counter :

    # 示例:在应用程序 prj.conf 或板卡特定配置中设置
    CONFIG_MCUBOOT_IMGTOOL_SIGN_SECURITY_COUNTER=2
    

备注

  • 当前 NP 固件使用的 security counter 与 AP 固件相同。

硬件防回滚(基于 OTP)

  1. 原理:

    • MCUboot 将当前已接受的 security counter 保存到硬件计数器(OTP)中。

    • 如果新固件的 security counter 小于 OPT 记录值 ,则拒绝启动。

    • 如果新固件的 security counter 大于或等于 OPT 记录值,则允许启动。

  2. 配置:

    CONFIG_MCUBOOT_HW_DOWNGRADE_PREVENTION=y
    
  3. 固件签名时嵌入 security counter :

    # 示例:在应用程序 prj.conf 或板卡特定配置中设置
    CONFIG_MCUBOOT_IMGTOOL_SIGN_SECURITY_COUNTER=2
    

备注

  • 当前 NP 固件使用的 security counter 与 AP 固件相同。

  • 固件 NP 与 AP 的硬件计数器均有上限,请仅在必要时更新,达到上限后可能造成设备启动失败。

快速上手

环境准备与配置

1. PC 主机安装 MCUmgr 命令行工具

您可以从 Apache Mynewt 官网 下载对应平台的 newtmgr 工具包:

将解压后的 newtmgr 工具包路径添加至对应平台的环境变量中, 命令行执行 newtmgr --help,若显示全局选项即配置成功。

2. Zephyr 设备端的工程配置

示例路径: zephyr/samples/subsys/mgmt/mcumgr/smp_svr

Flash 分区

smp_svr 示例采用的 Flash 分区布局,可参考 Flash 分区映射 配置。

基础 MCUboot 与 SMP 配置

您需要确保 smp_svr 示例的配置文件(如 prj.conf 或板卡特定的配置文件)中包含以下关键配置:

# 启用 MCUboot 引导程序
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_UPDATEABLE_IMAGE_NUMBER=2                            # AP(固件 0) 和 NP(固件 1)
CONFIG_MCUMGR_GRP_IMG_ALLOW_CONFIRM_NON_ACTIVE_IMAGE_ANY=y  # 允许对非运行固件进行确认

# 使能 MCUmgr 必要的命令组
CONFIG_MCUMGR_GRP_IMG=y                                     # 固件管理命令组
CONFIG_MCUMGR_GRP_OS=y                                      # 基本 OS 命令组

# 启用 MCUmgr 和 SMP 服务
CONFIG_MCUMGR=y
CONFIG_MCUMGR_SMP_UART=y                                    # 使用 UART 作为传输层

# 配置 UART 缓冲区与 MTU(最大传输单元)
CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE=1024
CONFIG_UART_MCUMGR_RX_BUF_SIZE=256                          # 需要大于等于 MCUmgr 工具设置的 MTU

您需要在应用程序的 sysbuild 配置文件 sysbuild.conf 中 指定升级方式,否则顶层会按默认逻辑选择 swap_using_move 方式。

# 配置升级方式
SB_CONFIG_MCUBOOT_MODE_SWAP_USING_OFFSET=y

UART 传输层配置

您需要在设备树(overlay 文件)中指定使用的 UART 引脚,并确保该 UART 端口已正确配置并启用。

/ {
   chosen {
      zephyr,uart-mcumgr = &uart2;
   };
};

&uart2 {
   pinctrl-0 = <&uart2_default>;
   pinctrl-names = "default";
   current-speed = <115200>;
   status = "okay";
};

编译命令

./nuwa.py build -d rtl8721f_evb//mcuboot -a zephyr/samples/subsys/mgmt/mcumgr/smp_svr --sysbuild -p

或者

west build -b rtl8721f_evb//mcuboot zephyr/samples/subsys/mgmt/mcumgr/smp_svr  --sysbuild -p

设备上电/复位后,打开串口终端,确认示例已启动并打印初始化日志:

*** Booting Zephyr OS build f50378385a85 ***
<inf> smp_sample: build time: Jan  7 2026 11:52:35

升级操作流程

假设设备已运行包含 smp_svr 示例的固件,并且选定的 UART 端口已连接到 PC 。

建立 MCUmgr 连接

配置连接

命令格式: newtmgr conn add <连接名> type=<类型> connstring="<key=value[,key=value...]>"

示例:

$ newtmgr conn add myConn type=serial connstring=\"dev=<COM3>,baud=115200,mtu=256\"
Connection profile myConn successfully added

备注

  • 波特率配置需要与设备树中的设定一致。MTU 配置需要小于等于应用配置中的 CONFIG_UART_MCUMGR_RX_BUF_SIZE

  • newtmgr conn 配置其他传输方式,请参考 官网说明

查看固件列表

命令格式: newtmgr image list -c <connection_profile>

示例:

$ newtmgr image list -c myConn
Images:
image=0 slot=0
   version: 0.0.0
   bootable: true
   flags: active confirmed
   hash: d0d1fe6e6f39a65d2442009e6a620736ea88e437c7bd297abdd9e9eaf1e3e257
image=1 slot=0
   version: 0.0.0
   bootable: true
   flags: active confirmed
   hash: 8ea9150f95da9c3f97134984b731928ae93e14752c54fe53b629d005ab94a948
参数说明
image:

多个固件时的固件编号 。

slot:

槽位(0=主槽,1=次槽)

version/hash:

版本号与哈希

bootable:

该槽位是否存在有效固件(固件头部正确且可校验)

flags:
  • active 表示是正在运行的固件槽位

  • pending 表示在下次 reset 时 MCUboot 会测试该固件槽位

  • confirmed 表示该固件槽位已被确认,不会回滚

备注

  • 编译新固件时,可修改版本号 CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION,方便版本管理和查询。

  • 当前 NP 固件总是使用与 AP 固件相同的版本号。

上传新固件

使用 image upload 命令将新编译好的、已签名的固件发送到设备。

命令格式: newtmgr image upload <image-file> -c <conn_profile> [-n <image_int>]

示例:

$ newtmgr -c myConn image upload -n 0 /path/to/your/image_0.bin
77.55 KiB / 77.55 KiB [=======================================================================] 100.00% 4.38 KiB/s 17s
Done

$ newtmgr -c myConn image upload -n 1 /path/to/your/image_1.bin
228.48 KiB / 228.48 KiB [=====================================================================] 100.00% 4.44 KiB/s 51s
Done
参数说明
-n:

指定固件编号,缺省时表示固件 0 。

标记为待测试

传输成功后,新固件将存放在对应固件的次槽分区中。通过以下命令标记新固件为 test ,MCUboot 会在下次启动时尝试运行它。

命令格式: newtmgr -c <conn_profile> image test <hex-image-hash>

示例:

$ newtmgr -c myConn image test 99bb5f0867744a238693f522b31be146a74fca2ad587d878feebef70b7d1e812
Images:
image=0 slot=0
   version: 0.0.0
   bootable: true
   flags: active confirmed
   hash: d0d1fe6e6f39a65d2442009e6a620736ea88e437c7bd297abdd9e9eaf1e3e257
image=0 slot=1
   version: 0.0.1
   bootable: true
   flags: pending
   hash: 99bb5f0867744a238693f522b31be146a74fca2ad587d878feebef70b7d1e812
image=1 slot=0
   version: 0.0.0
   bootable: true
   flags: active confirmed
   hash: 8ea9150f95da9c3f97134984b731928ae93e14752c54fe53b629d005ab94a948
image=1 slot=1
   version: 0.0.1
   bootable: true
   flags:
   hash: 137d5f84dc5c302f2d15d22ca7fa76c48e030a5d5c6f73b6ae89598143af92fe
Split status: N/A (0)

$ newtmgr -c myConn image test 137d5f84dc5c302f2d15d22ca7fa76c48e030a5d5c6f73b6ae89598143af92fe
Images:
image=0 slot=0
   version: 0.0.0
   bootable: true
   flags: active confirmed
   hash: d0d1fe6e6f39a65d2442009e6a620736ea88e437c7bd297abdd9e9eaf1e3e257
image=0 slot=1
   version: 0.0.1
   bootable: true
   flags: pending
   hash: 99bb5f0867744a238693f522b31be146a74fca2ad587d878feebef70b7d1e812
image=1 slot=0
   version: 0.0.0
   bootable: true
   flags: active confirmed
   hash: 8ea9150f95da9c3f97134984b731928ae93e14752c54fe53b629d005ab94a948
image=1 slot=1
   version: 0.0.1
   bootable: true
   flags: pending
   hash: 137d5f84dc5c302f2d15d22ca7fa76c48e030a5d5c6f73b6ae89598143af92fe
Split status: N/A (0)

备注

  • 被指定为测试的固件槽位信息中可以看到: flags: pending

重启设备

可以通过 MCUmgr 工具发送重启命令,或手动复位设备。

触发 reset 后,MCUboot 检测到次槽被标记为 test,根据配置的 OTA 升级机制, 决定是否执行主/次槽交换;采用 swap_using_xxx 策略时会在应用启动前完成交换。

命令格式: newtmgr reset -c <conn_profile>

示例:

$ newtmgr -c myConn reset
Done

确认新固件

设备重启并成功运行新固件后,必须执行确认操作,Zephyr 提供了两种确认方式:

  1. 应用程序自行确认

    设备在启动并验证新固件成功后,由应用程序主动调用 MCUboot 提供的确认接口 boot_write_img_confirmed(),将当前固件标记为 confirmed

  2. 通过 MCUmgr 工具下发确认命令

    命令格式: newtmgr image confirm [hex-image-hash] -c <conn_profile>

    示例:

    $ newtmgr -c myConn image confirm 137d5f84dc5c302f2d15d22ca7fa76c48e030a5d5c6f73b6ae89598143af92fe
    Images:
    image=0 slot=0
       version: 0.0.1
       bootable: true
       flags: active confirmed
       hash: 99bb5f0867744a238693f522b31be146a74fca2ad587d878feebef70b7d1e812
    image=1 slot=0
       version: 0.0.1
       bootable: true
       flags: active confirmed
       hash: 137d5f84dc5c302f2d15d22ca7fa76c48e030a5d5c6f73b6ae89598143af92fe
    

备注

  • 被确认的固件槽位信息中可以看到: flags: confirmed

  • 当前 smp_svr 示例采用第二种确认方式。