3、MCUboot配置:从源码获取到槽位计算的实战指南

好,咱们进入第三章。这一章我打算跟你聊聊MCUboot的配置。

说实话,MCUboot的配置是整个OTA升级里最容易被忽视、但又最容易出坑的环节。我见过太多人,签名算法选错了,槽大小算错了,结果升级到一半直接变砖。嗯,咱们今天就把这些坑一个个填平。

3.1 MCUboot源码获取

先说说源码怎么拿。我个人习惯直接从官方仓库拉取,这样能保证是最新、最稳定的版本。

# 克隆MCUboot仓库
git clone https://github.com/mcu-tools/mcuboot.git
cd mcuboot

# 切换到稳定版本(我建议用v1.9.0或v1.10.0)
git checkout v1.9.0

为什么我推荐用稳定版?因为我在项目里踩过坑——用master分支,结果某个API突然变了,整个编译链全崩。你想想看,生产环境谁敢用开发分支?

我的小建议: 如果你用的是Zephyr,其实MCUboot已经作为子模块集成进去了。你只需要在Zephyr的west.yml里配置好版本就行,不用单独拉代码。

3.2 单镜像槽 vs 双镜像槽

这是个大问题。说白了,单槽还是双槽,取决于你的Flash有多大、你对可靠性要求有多高。

3.2.1 单镜像槽(Single Slot)

单槽模式,就是只有一个镜像槽。升级时直接覆盖原有固件。

  • 优点: 省Flash空间,适合资源受限的芯片
  • 缺点: 升级失败就变砖,没有回滚能力
  • 适用场景: 产品稳定后的小版本更新,或者Flash实在太小

我记得有一次做智能灯控项目,芯片只有256KB Flash。没办法,只能用单槽。每次升级前我都提心吊胆,生怕断电。后来加了个外部Flash做备份,才算安心。

3.2.2 双镜像槽(Dual Slot)

双槽模式,就是有主槽(Primary Slot)和备槽(Secondary Slot)。升级时先写到备槽,校验通过后再交换。

  • 优点: 升级失败可以回滚,安全性高
  • 缺点: 需要双倍Flash空间
  • 适用场景: 绝大多数产品,尤其是远程升级场景
核心要点: 双槽模式下,MCUboot会做“镜像交换”(Image Swap)。升级成功后,主槽和备槽的角色互换。下次升级时,新的固件会写入原来的主槽位置。

你想想看,如果升级到一半断电了,重启后MCUboot检测到备槽的镜像不完整,会自动回滚到主槽的旧版本。这就是双槽最大的价值。

3.3 签名算法选择:EC256 vs RSA

签名算法,说白了就是给你的固件加个“防伪标签”。MCUboot支持两种主流算法:EC256(ECDSA P-256)和RSA-2048。

特性 EC256 (ECDSA P-256) RSA-2048
密钥长度 32字节 256字节
签名速度
验证速度 较快
安全性 高(等效RSA-3072)
Flash占用

我个人强烈推荐EC256。为什么?

第一,签名速度快。我在一个Cortex-M4项目上测试过,EC256签名只需要几十毫秒,RSA要几百毫秒。你想想看,如果每次OTA升级都要等签名,用户体验得多差。

第二,密钥短。32字节的私钥,存起来方便,传输也安全。

第三,安全性不输RSA。EC256的安全强度等效于RSA-3072,完全够用。

避坑指南: 我曾经在项目里选了RSA-2048,结果发现芯片的硬件加速器只支持EC256。软件实现RSA签名,慢得离谱。最后只能改方案,白白浪费了两周时间。所以,选算法前一定要确认你的芯片支持什么硬件加速。

3.4 槽大小与偏移量计算

这是最烧脑的部分,但也是最关键的。槽大小和偏移量算错了,MCUboot根本跑不起来。

3.4.1 基本概念

先明确几个术语:

  • 槽(Slot): 存放固件镜像的区域
  • 偏移量(Offset): 槽在Flash中的起始地址
  • 大小(Size): 槽的最大容量

3.4.2 计算公式

假设你的Flash起始地址是0x08000000,总大小是1MB(0x100000)。

# 单槽模式
主槽偏移量 = Flash起始地址 + MCUboot占用大小
主槽大小   = Flash总大小 - MCUboot占用大小

# 双槽模式
主槽偏移量 = Flash起始地址 + MCUboot占用大小
主槽大小   = (Flash总大小 - MCUboot占用大小) / 2
备槽偏移量 = 主槽偏移量 + 主槽大小
备槽大小   = 主槽大小

举个例子:

Flash起始地址: 0x08000000
Flash总大小:   0x100000 (1MB)
MCUboot占用:   0x10000  (64KB)

# 双槽计算
主槽偏移量 = 0x08000000 + 0x10000 = 0x08010000
主槽大小   = (0x100000 - 0x10000) / 2 = 0x78000 (480KB)
备槽偏移量 = 0x08010000 + 0x78000 = 0x08088000
备槽大小   = 0x78000 (480KB)
重要提醒: 槽大小必须是Flash擦除块大小(通常是4KB)的整数倍。否则MCUboot会报错。我当初就因为这个,折腾了一整天。

3.4.3 在Zephyr中的配置

在Zephyr里,这些参数通过设备树(DTS)和Kconfig配置。我一般这样写:

// 在设备树中定义Flash分区
&flash0 {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        boot_partition: partition@0 {
            label = "mcuboot";
            reg = <0x00000000 0x00010000>;  // 64KB for MCUboot
        };

        slot0_partition: partition@10000 {
            label = "image-0";
            reg = <0x00010000 0x00078000>;  // 480KB for primary slot
        };

        slot1_partition: partition@88000 {
            label = "image-1";
            reg = <0x00088000 0x00078000>;  // 480KB for secondary slot
        };
    };
};

然后在Kconfig里配置:

# MCUboot配置
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="bootloader/mcuboot/root-ec-p256.pem"
CONFIG_MCUBOOT_SLOT_SIZE=0x78000

3.4.4 常见错误与排查

我总结几个常见问题:

  • 槽太小: 固件编译后超过槽大小,MCUboot会拒绝写入。建议留20%余量。
  • 偏移量重叠: 两个槽的地址范围重叠了,MCUboot启动时会崩溃。
  • 对齐问题: 偏移量不是Flash擦除块的整数倍,写入会失败。
我的调试技巧:west build -t flash烧录后,用west debug启动GDB,查看mcuboot的日志输出。如果配置有误,MCUboot会打印详细的错误信息,比如“Image in slot 0 is not valid”。

3.5 小结

嗯,这一章内容不少。从源码获取到槽位计算,每一步都有讲究。

我个人觉得,最核心的是两点:

第一,双槽模式是标配,除非你的Flash实在太小。安全第一,别省那点空间。

第二,EC256是首选,速度快、密钥短、安全性高。除非你的芯片不支持,否则别犹豫。

下一章,咱们会深入MCUboot的启动流程,看看它到底是怎么校验和交换镜像的。到时候你会发现,今天算的这些偏移量,全都会用上。

好,今天就到这儿。有问题随时找我。