1. 项目概述:RX MCU的安全启动与升级基石
在嵌入式开发领域,尤其是涉及物联网终端、工业控制或消费电子设备时,我们常常面临一个核心矛盾:设备需要具备远程更新固件的能力以修复漏洞或增加功能,但更新过程本身又可能成为系统安全的致命弱点。一个未经保护的引导流程,就像把自家大门的钥匙挂在门外,攻击者可以轻易植入恶意代码,完全掌控设备。我经历过不少项目,早期为了快速上线,往往采用简单的“擦写-覆盖”式升级,直到某次现场设备批量“变砖”后,才痛定思痛,开始深入研究安全启动与升级机制。
瑞萨电子的RX系列MCU,凭借其出色的性能与可靠性,在众多工业与消费场景中占据一席之地。而MCUboot FIT模块,正是为RX系列量身打造的安全启动与固件升级解决方案。它并非一个从零开始的全新轮子,而是基于开源、经过广泛验证的MCUboot项目,通过瑞萨的FIT(Firmware Integration Technology)框架进行了深度集成和硬件优化。简单来说,它把MCUboot这个强大的“安全引擎”,封装成了RX平台上一颗即插即用的“芯片”,让开发者无需从底层移植和适配,就能快速获得企业级的安全启动能力。
这个模块的核心价值在于“安全”与“便捷”的平衡。它利用RX MCU内置的硬件安全IP(如TSIP或RSIP)来处理最敏感的加解密和签名验证操作,确保私钥永不离开安全区域,同时提供了多种灵活的升级策略(Overwrite, Swap, DirectXIP)来适应不同的存储布局和可靠性要求。对于开发者而言,这意味着你可以将精力更多地集中在应用逻辑本身,而不是耗费数月去构建和审计一套脆弱的安全启动框架。接下来,我将结合自己的实操经验,为你深入拆解这套方案的实现细节、配置要点以及那些容易踩坑的地方。
2. MCUboot FIT模块核心架构与设计思路
2.1 系统组成与模块依赖关系
要理解MCUboot FIT模块,首先要看清它在整个系统中所处的位置。它不是一个孤立运行的魔法盒,而是一个依赖于特定硬件和软件环境的核心中间件。一个典型的基于MCUboot FIT的安全启动系统,其软件栈通常分为三层。
最底层是 硬件驱动层 ,这是模块运行的基石。MCUboot FIT强制依赖以下几个FIT模块:
- 板级支持包(BSP) :提供基础的时钟、中断、IO初始化,是任何RX项目都离不开的。
- Flash驱动模块 :这是关键中的关键。所有对主程序(Primary Slot)、升级程序(Secondary Slot)和交换缓冲区(Scratch Area)的擦写操作,都通过这个模块进行。RX系列MCU的Flash通常有特定的编程时序和块大小限制,这个驱动封装了所有硬件细节。
-
安全IP驱动模块
:这是安全能力的硬件加速器。对于RX65N/RX72M等型号,使用的是
r_tsip模块;对于RX261,则使用r_rsip_protected_rx模块。签名验证(ECDSA/RSA)和镜像解密(如果启用)这些计算密集型操作,都会卸载到这些硬件密码引擎中执行,速度远超软件实现,且密钥材料更安全。 - SCI驱动与BYTEQ模块 :用于调试信息输出或构建串口升级通道。虽然MCUboot核心不强制依赖它们,但配套的演示项目和实际升级流程通常需要串口通信。
中间层就是
MCUboot FIT模块本身
。它基于开源MCUboot,但通过FIT接口进行了“本地化”改造。它负责协调底层驱动,执行预设的启动、验证、升级流程,并向上提供一个简洁的API(主要是
boot_go
和
RM_MCUBOOT_BootApp
)。
最上层则是 用户的应用代码 ,包括我们编写的业务逻辑、以及用于接收新固件(例如通过串口、OTA)的升级器(Updater)程序。在演示项目中,这个升级器被集成在“初始镜像”里。
实操心得:模块选型与内存规划 在项目初期规划时,务必根据你选用的具体RX型号(如RX65N vs RX261)确认对应的安全IP驱动模块。同时,Flash驱动的版本也需要匹配你的MCU型号和Flash容量。一个常见的坑是,在资源紧张的RX261上,如果同时启用了复杂的加密和签名,需要仔细评估
r_rsip_protected_rx和MCUboot FIT本身带来的ROM/RAM开销,避免最后空间不足。后文会给出具体的尺寸参考表。
2.2 内存布局设计哲学
MCUboot管理Flash的核心思想是 分区 。它将单片机的Flash内存划分为几个逻辑区域,这种布局是后续所有升级策略的基础。理解这个布局,是进行后续配置和调试的前提。
- Bootloader区 :存放MCUboot引导程序本身。这是设备上电后最先运行的代码。其大小需要在编译时就确定,并通过链接脚本固定地址。通常,它被放置在Flash的起始地址(例如0x00000000)。
- 主程序槽(Primary Slot) :存放当前可启动的应用程序镜像。系统正常运行时,就是从该区域启动用户程序。这是设备的“生产版本”所在。
- 次程序槽(Secondary Slot) :存放待升级的镜像文件。当需要通过网络、串口等方式更新固件时,新的固件文件会被写入这个区域。它相当于一个“暂存区”或“升级包仓库”。
- 交换区(Scratch Area) :这是一个临时缓冲区, 仅在Swap升级方法中需要 。在Swap过程中,它用于临时存放被换出的旧镜像,确保升级过程意外断电时,至少有一个完整的镜像可用,是实现可靠回滚(Rollback)的关键。
这些区域在物理Flash上是连续排列的。例如,一个典型的布局可能是:
[Bootloader: 64KB][Primary Slot: 448KB][Scratch: 64KB][Secondary Slot: 448KB]
。具体的地址和大小,完全由开发者在链接脚本(或通过FIT配置工具)中定义,并需要与MCUboot FIT模块中的配置宏(如
RM_MCUBOOT_CFG_APPLICATION_AREA_SIZE
)严格对应。
注意事项:对齐与块大小 Flash编程有一个重要特性:擦除以“扇区”或“块”为单位进行。RX系列MCU的Flash块大小可能是1KB、2KB、4KB或更大。在规划上述分区大小时, 必须确保每个分区的起始地址和大小都是Flash块大小的整数倍 。否则,在擦写操作时会导致硬件错误或数据损坏。在配置
RM_MCUBOOT_CFG_APPLICATION_AREA_SIZE和RM_MCUBOOT_CFG_SCRATCH_AREA_SIZE时,务必查阅你所用MCU的数据手册,确认其Flash块大小。
2.3 支持的升级模式深度解析
MCUboot FIT提供了三种主流的升级策略,每种策略在可靠性、空间开销和复杂度上各有取舍。
2.3.1 Overwrite Only / Overwrite Only Fast(线性覆盖) 这是最直观、最简单的模式。当检测到Secondary Slot有合法的更新镜像时,MCUboot会将其 直接复制 到Primary Slot,覆盖旧镜像。完成后,Secondary Slot会被擦除。
- Overwrite Only :复制整个Secondary Slot区域。无论新镜像实际多大,都会复制整个分区大小。操作简单,但如果Secondary Slot很大而更新很小,会浪费时间和Flash寿命。
- Overwrite Only Fast :仅复制有效的镜像部分(通过镜像头中的大小信息判断)。效率更高,是推荐的方式。
- 优点 :实现简单,不需要额外的Scratch Area,节省Flash空间。
- 缺点 :升级过程中发生断电,Primary Slot的镜像可能被部分覆盖而损坏,导致设备无法启动(变砖)。 不具备回滚能力 。
- 适用场景 :对空间极度敏感,且更新包较小、升级过程供电相对可靠(或有备用电源)的场景。
2.3.2 Swap(交换模式) 这是最可靠、提供完整回滚能力的模式。它需要额外的Scratch Area作为临时交换区。
- 将Secondary Slot中的新镜像临时移动到Scratch Area。
- 将Primary Slot中的旧镜像移动到Secondary Slot。
- 将Scratch Area中的新镜像移动到Primary Slot。 最终效果是Primary Slot和Secondary Slot的镜像互换了位置。如果升级后新版本有问题,可以触发一次“回滚”操作(本质上是再次执行Swap),换回之前保存在Secondary Slot的旧版本。
- 优点 :断电安全。在任何单步操作后断电,系统仍保留一个可启动的完整镜像(要么是旧的,要么是新的)。支持版本回滚。
- 缺点 :需要额外的Scratch Area(通常与单个Slot大小相同),Flash总开销最大。升级过程涉及三次搬运,时间较长。
- 适用场景 :对可靠性要求极高的场景,如工业控制、医疗设备,必须保证设备在任何意外情况下都能保持可运行状态。
2.3.3 DirectXIP(直接执行) 这是一种独特的模式,其核心思想是 无需搬运 。Primary Slot和Secondary Slot中各自存放一个完整的、可独立执行的镜像。MCUboot的角色变为一个“选择器”,根据策略(例如,验证Secondary Slot镜像有效后)决定本次从哪个Slot启动。
- 线性模式 :两个Slot位于同一Flash Bank的连续地址。MCUboot通过修改自身的启动指针或变量,来决定跳转到哪个地址执行。
- 双Bank模式 :利用RX系列MCU(如RX65N)支持的双Bank Flash特性。两个Slot分别位于Bank0和Bank1。MCUboot通过硬件寄存器交换两个Bank的映射地址,从而实现镜像切换。这种切换通常是原子性的,速度极快。
- 优点 :升级速度最快(无需复制镜像),Flash磨损小。双Bank模式切换瞬间完成。
- 缺点 :需要Flash有足够的容量同时存储两个完整应用镜像。应用程序的链接地址需要特殊处理(尤其是在线性模式下),设计更复杂。
- 适用场景 :对升级速度有严格要求,或Flash容量充裕,且应用设计支持位置无关或固定偏移寻址的场景。
3. 工程配置与关键实现细节
3.1 配置选项详解与实战设置
MCUboot FIT模块的行为几乎完全由
rm_mcuboot_config.h
文件中的一系列配置宏控制。理解每一个选项的含义,是成功部署的关键。下面我将结合常见的使用场景,解释几个最核心的配置项。
3.1.1 升级模式与签名验证配置 这是最基础的配置,决定了MCUboot的“行为模式”。
// 升级模式选择:0-Overwrite Only, 1-Overwrite Only Fast, 2-Swap, 3-DirectXIP
#define RM_MCUBOOT_CFG_UPGRADE_MODE (1)
// 是否验证主槽镜像:0-不验证,1-验证(推荐启用,确保每次启动的镜像都是可信的)
#define RM_MCUBOOT_CFG_VALIDATE_PRIMARY_SLOT (1)
// 签名验证算法:0-无,1-ECDSA P-256(默认,推荐),2-RSA 2048(RX261不支持)
#define RM_MCUBOOT_CFG_SIGN (1)
// 防版本降级:0-禁用,1-启用(仅Overwrite模式有效)
#define RM_MCUBOOT_CFG_DOWNGRADE_PREVENTION (1)
-
RM_MCUBOOT_CFG_UPGRADE_MODE:根据前文分析的优缺点和你的硬件资源(是否有双Bank,Flash是否够用)来选择。对于大多数需要可靠性的应用, Swap模式是首选 。如果Flash紧张且能接受一定风险,可选Overwrite Only Fast。 -
RM_MCUBOOT_CFG_VALIDATE_PRIMARY_SLOT: 强烈建议始终启用(设为1) 。这确保了即使没有升级操作,每次启动时也会检查Primary Slot中应用程序的签名,防止固件在静态存储时被篡改。 -
RM_MCUBOOT_CFG_SIGN:ECDSA P-256是当前嵌入式安全领域的首选,它比RSA 2048签名更短、验证速度在硬件加速下也很快。除非有特殊兼容性要求,否则用默认的ECDSA即可。注意RX261不支持RSA。 -
RM_MCUBOOT_CFG_DOWNGRADE_PREVENTION:这是一个有用的安全特性。启用后,MCUboot会比较镜像头中的版本号,只允许升级到更高版本,防止攻击者用旧版本(可能含有已知漏洞)替换新版本。注意,此功能仅在Overwrite模式下有效,Swap和DirectXIP模式有其自身的版本管理逻辑。
3.1.2 内存区域大小配置
这部分配置必须与你的链接脚本(
.ld
文件或IDE中的分散加载设置)
严格同步
,否则会导致MCUboot访问错误的内存地址,引发硬件错误。
// MCUboot自身代码所需空间大小。需考虑Flash块大小对齐。
#define RM_MCUBOOT_CFG_MCUBOOT_AREA_SIZE (0x10000) // 64KB
// 单个应用程序槽(Primary 或 Secondary)的大小。必须为Flash块大小的整数倍!
#define RM_MCUBOOT_CFG_APPLICATION_AREA_SIZE (0xF0000) // 960KB for RX65N示例
// Scratch交换区大小(仅Swap模式需要)。必须为Flash块大小的整数倍!
#define RM_MCUBOOT_CFG_SCRATCH_AREA_SIZE (0x10000) // 64KB
配置步骤 :
- 在IDE中规划好Flash布局,确定Bootloader、Primary Slot、Scratch、Secondary Slot的起始地址和大小。
- 在链接脚本中,将代码段和数据段分配到对应的地址范围。
-
将上述分区大小,准确填写到
rm_mcuboot_config.h的对应宏中。 -
编译Bootloader工程,确保生成的二进制文件大小不超过
RM_MCUBOOT_CFG_MCUBOOT_AREA_SIZE的定义,并且其结束地址正好是Primary Slot的起始地址。
3.1.3 密钥与加密相关配置 这是安全功能的核心,涉及密钥的注入和管理。
// 是否使用用户提供的DER格式公钥:0-使用模块内置示例密钥,1-使用用户密钥
#define RM_MCUBOOT_CFG_DER_PUB_USER_KEY_ENABLE (1)
// 公钥存储地址(当启用用户密钥时)。通常指向Flash中某个固定位置。
#define RM_MCUBOOT_CFG_VERIFY_KEY_ADDRESS (0x000F0000)
// 是否启用镜像加密:0-禁用,1-启用(Key Wrap模式)
#define RM_MCUBOOT_CFG_APPLICATION_ENCRYPTION_SCHEME (1)
// 加密密钥的存储地址(当启用加密时)。
#define RM_MCUBOOT_CFG_ENCRYPT_KEY_ADDRESS (0x000F0100)
-
密钥注入
:
绝对不要
在量产固件中使用默认或硬编码的密钥。瑞萨提供了
密钥注入(Key Injection)
流程和配套的
SKMT(Security Key Management Tool)
工具。基本流程是:在产线,通过调试器将一个特殊的“密钥注入程序”烧录到设备,该程序利用TSIP/RSIP的安全特性,将你的公钥(用于验证签名)和加密密钥(用于解密镜像)以“封装密钥(Wrapped Key)”的形式写入Flash的指定位置(即上述
VERIFY_KEY_ADDRESS和ENCRYPT_KEY_ADDRESS)。之后,再烧录正式的Bootloader和应用程序。这样,密钥本身在Flash中也是加密状态,即使被读出也无法直接使用。 - 地址选择 :密钥存储地址应选择在Bootloader和应用程序都不会使用的Flash区域,通常是在Flash的末尾部分,并做好对齐。需要在链接脚本中预留出这些空间。
3.2 主程序与Bootloader的集成流程
理解了配置,我们来看代码层面如何集成。MCUboot FIT的使用接口非常简洁,核心就是两个函数调用。
3.2.1 Bootloader工程中的主流程
在你的Bootloader项目的主函数(通常是
main.c
)中,流程如下:
#include "rm_mcuboot.h"
int main(void)
{
struct boot_rsp rsp;
fih_ret ret;
// 1. 硬件初始化:时钟、看门狗、必要的GPIO等
hardware_init();
// 2. 初始化MCUboot FIT所依赖的模块(BSP, Flash, TSIP/RSIP等)
// 这些通常在FIT配置工具中已自动生成初始化代码,或需手动调用 xxx_open()。
// 3. 核心:执行MCUboot引导流程
ret = boot_go(&rsp);
if (FIH_SUCCESS != ret) {
// 引导失败处理:可能是签名验证失败、镜像损坏等
// 可以点亮错误LED,或尝试恢复出厂镜像
error_handler();
while(1); // 或触发系统复位
}
// 4. 引导成功,关闭Bootloader使用的驱动,跳转到应用程序
RM_MCUBOOT_BootApp(&rsp);
// RM_MCUBOOT_BootApp 不会返回,它直接跳转到应用程序入口点
// 如果使用了DirectXIP双Bank模式,此函数内部会执行Bank交换和软件复位
while(1);
}
boot_go
函数完成了所有脏活累活:检查Secondary Slot、验证签名、执行升级(如果需要)、验证Primary Slot。它填充一个
boot_rsp
结构体,其中包含了要启动的镜像的地址信息。
RM_MCUBOOT_BootApp
则负责“交接班”,清理现场并跳转。
3.2.2 应用程序工程的特殊处理 你的用户应用程序(即放在Primary/Secondary Slot中的程序)也需要做一些调整:
-
中断向量表重映射
:RX MCU的中断向量表通常固定在Flash起始地址。Bootloader占用了这个位置。因此,你的应用程序需要将自己的中断向量表放在其链接地址的起始处,并在启动代码中重新配置MCU的向量表基址寄存器(例如
INTB)。瑞萨的编译器(CC-RX,GCC for RX)在生成启动代码时,通常可以通过链接脚本或项目属性选项来配置这一点。 - 链接地址 :应用程序的链接起始地址必须是Primary Slot的起始地址(例如0x00010000,假设Bootloader占了0x00000000~0x0000FFFF)。这需要在项目的链接器设置中明确指定。
-
与Bootloader的通信(可选)
:有时应用程序需要知道自己是正常启动还是升级后启动,或者需要触发一次回滚。这可以通过在固定RAM地址设置标志位,或通过Bootloader留下的特定信息(例如在
boot_rsp结构体中扩展)来实现。MCUboot本身支持在镜像头中定义“共享数据区(Shared TLV)”,应用程序可以从中读取信息。
3.3 生成与处理安全镜像
你的应用程序编译生成的二进制(
.bin
或
.hex
)并不能直接用于MCUboot升级。它需要被加工成一个带有
镜像头(Image Header)
和
签名(Signature)
的“安全镜像”。瑞萨的MCUboot FIT包中包含了来自上游MCUboot项目的
imgtool
工具,用于完成这个工作。
基本命令示例 :
# 1. 生成密钥对(如果还没有)。私钥务必妥善保管!
openssl ecparam -name prime256v1 -genkey -noout -out priv.pem
openssl ec -in priv.pem -pubout -out pub.pem
# 2. 使用 imgtool 为你的 app.bin 添加镜像头和签名
# -k 指定私钥,--align 指定Flash对齐大小,--version 设置镜像版本
imgtool sign --key priv.pem --align 8 --version 1.2.3 --header-size 0x200 app.bin signed-app.bin
这个
signed-app.bin
就是可以用于升级的镜像。镜像头里包含了版本号、镜像大小、哈希值等信息,末尾附带了ECDSA签名。MCUboot在验证时,会用预先注入到设备中的公钥(对应
priv.pem
的公钥)来验证这个签名。
加密镜像生成
(如果启用了
RM_MCUBOOT_CFG_APPLICATION_ENCRYPTION_SCHEME
):
# 需要额外的 --encrypt 参数和加密密钥
imgtool sign --key priv.pem --align 8 --version 1.2.3 --header-size 0x200 --encrypt enc_key.bin app.bin signed-encrypted-app.bin
这里的
enc_key.bin
是一个二进制格式的加密密钥。其生成和管理同样需要借助SKMT工具和瑞萨的安全IP流程,确保密钥在设备端被安全解密。
4. 演示项目实操与问题排查实录
4.1 演示项目运行流程分析
瑞萨提供的演示项目(Demo Project)是一个极佳的学习和测试起点。它通常包含三个子工程:
boot_loader
,
application_primary
(初始镜像), 和
key_injection
(密钥注入工具)。其工作流程清晰地展示了端到端的升级过程:
-
初始状态
:使用编程器将
boot_loader和application_primary烧录到设备。application_primary被放在Primary Slot,其中包含一个简单的串口升级功能。 -
密钥注入(产线步骤)
:在量产前,通过调试器运行
key_injection程序。该程序通过串口或调试接口接收来自PC端SKMT工具生成的“封装密钥”,并将其写入Flash中RM_MCUBOOT_CFG_VERIFY_KEY_ADDRESS等定义的固定位置。完成后,设备复位。 -
正常启动
:设备上电,MCUboot运行,验证Primary Slot中
application_primary的签名(使用注入的公钥),验证通过后跳转执行。 -
触发升级
:
application_primary运行后,通过串口等待PC端发送新的、已签名的signed-app.bin(即更新镜像)。它通过XMODEM或自定义协议接收数据,并将其写入Secondary Slot。 -
固件更新
:写入完成后,
application_primary软件复位MCU。 - MCUboot处理升级 :MCUboot再次启动,这次它在Secondary Slot发现了有效的更新镜像。它执行签名验证,并根据配置的升级模式(如Swap),将新镜像交换到Primary Slot。
- 启动新镜像 :MCUboot验证更新后的Primary Slot镜像,并跳转执行。此时,设备已经运行在新版本的固件上。
4.2 常见问题与排查技巧
在实际部署中,你几乎一定会遇到各种问题。下面是我总结的一些常见坑点及排查思路。
4.2.1 启动时卡住或立即复位
- 症状 :设备上电后,没有任何日志输出,或者很快复位循环。
-
排查步骤
:
- 检查Bootloader链接地址 :确认Bootloader的链接起始地址是否为0x00000000(或MCU的启动地址)。这是最常见的错误。
-
检查中断向量表
:确认应用程序的中断向量表是否正确重映射。一个简单的测试方法是,在应用程序的
main函数最开始点灯或发送串口消息,如果连这个都执行不到,很可能是向量表问题。可以暂时禁用所有中断来测试。 -
检查Flash驱动初始化
:MCUboot在
boot_go中会初始化Flash驱动。如果Flash型号或时钟配置错误,可能导致初始化失败。确保r_flash_rx模块的配置与你板载的Flash型号匹配。 -
启用调试日志
:将
RM_MCUBOOT_CFG_LOG_LEVEL设为4(Debug),并通过串口输出日志(确保SCI驱动已正确初始化并指向正确的串口引脚)。观察MCUboot执行到哪一步出错。
4.2.2 签名验证失败
- 症状 :Bootloader日志显示“Image validation failed”或类似信息,然后可能尝试启动备份镜像或进入错误状态。
-
排查步骤
:
-
确认密钥匹配
:用于签名的私钥
priv.pem,必须与注入到设备Flash中的公钥是配对的一对。使用openssl命令检查公钥摘要,与注入工具生成的摘要对比。 -
检查镜像头格式
:使用
imgtool的info子命令检查生成的signed-app.bin文件,确认其版本、哈希值是否正确。imgtool info signed-app.bin -
检查Flash写入
:确保升级器(Updater)程序将
signed-app.bin完整、正确地写入到了Secondary Slot的 正确起始地址 。任何一位错误都会导致哈希校验或签名失败。可以在写入后,再读出来与原始文件做二进制比较。 -
检查对齐
:
imgtool sign命令中的--align参数必须与目标Flash的编程对齐要求(通常是8字节)一致。MCUboot在验证时会对齐要求进行检查。
-
确认密钥匹配
:用于签名的私钥
4.2.3 Swap升级后,回滚功能异常
- 症状 :升级成功,但触发回滚后,设备没有恢复到旧版本,或者直接启动失败。
-
排查步骤
:
-
确认Scratch Area配置
:Swap模式必须正确定义
RM_MCUBOOT_CFG_SCRATCH_AREA_SIZE,且其大小至少能容纳你的应用程序镜像。地址也必须在链接脚本中预留,且不能与其他区域重叠。 -
检查镜像状态标志
:MCUboot在镜像头中会设置“待定(Pending)”和“已确认(Confirmed)”状态。回滚逻辑依赖于这些标志。确保你的应用程序在升级后成功调用了
boot_write_img_confirmed()之类的API(在MCUboot的应用程序接口中)来确认新镜像,否则MCUboot在下一次启动时会自动回滚。 - 分析日志 :在Debug日志级别下,MCUboot会输出每个Slot中镜像的状态。根据日志判断它认为哪个是“待定”,哪个是“已确认”。
-
确认Scratch Area配置
:Swap模式必须正确定义
4.2.4 DirectXIP模式应用程序运行异常
- 症状 :在DirectXIP模式下,特别是线性模式,应用程序可能跑飞或访问硬件错误。
-
排查步骤
:
-
检查位置无关代码
:线性DirectXIP模式下,两个Slot中的应用程序代码必须能在不同的基址运行。确保编译器生成了位置无关代码(PIC)。对于GCC,可能需要
-fPIC选项;对于CC-RX,需要检查相关链接选项,并确保代码中没有使用绝对地址寻址(例如,通过.far段访问的数据地址需要特殊处理)。 -
检查Bank切换(双Bank模式)
:在双Bank模式下,确认
RM_MCUBOOT_BootApp函数正确触发了Flash Bank交换和软件复位。有些MCU的Bank交换需要在特定时序下操作。 - 向量表地址 :即使是在DirectXIP下,应用程序的中断向量表地址也需要正确设置。在双Bank模式下,Bank交换后,硬件中断向量可能仍然从原Bank读取,需要仔细查阅芯片手册,确认是否需要重新配置向量表基址寄存器。
-
检查位置无关代码
:线性DirectXIP模式下,两个Slot中的应用程序代码必须能在不同的基址运行。确保编译器生成了位置无关代码(PIC)。对于GCC,可能需要
4.3 资源消耗分析与优化建议
嵌入式开发总是绕不开资源约束。下表整理了在典型配置(Swap模式, ECDSA签名, 启用调试日志)下,不同RX型号和编译器组合的Bootloader大致资源占用,数据来源于官方文档的统计:
| 编译器 | 设备型号 | 模式 | 示例工程 | ROM占用 (字节) | RAM占用 (字节) | 栈最大深度 |
|---|---|---|---|---|---|---|
| CC-RX | RX65N | Linear | boot_loader (swap) | ~49,363 | ~19,878 | 316 |
| GCC | RX65N | Linear | boot_loader (swap) | ~47,406 | ~21,680 | 928 |
| IAR | RX65N | Linear | boot_loader (swap) | ~46,812 | ~17,563 | 2588 |
分析 :
- ROM大小 :Bootloader本身需要约45-60KB空间,这对于Flash较小的型号(如RX231的256KB)需要仔细规划。可以考虑禁用非必需功能(如降低日志等级、不使用加密)来缩减体积。
- RAM占用 :主要开销来自签名验证时的缓冲区、Flash操作缓存以及MCUboot内部数据结构。对于RAM紧张的设备(如只有20-30KB RAM),需要关注此值,确保应用程序有足够RAM运行。
- 栈空间 :IAR编译器报告的栈深度较大,需要在启动文件中分配足够的栈空间,防止栈溢出。
优化建议 :
-
按需裁剪
:在
rm_mcuboot_config.h中,关闭不需要的功能,如RM_MCUBOOT_CFG_VALIDATE_PRIMARY_SLOT(不推荐)、加密功能、或将日志等级设为0(Off)。 -
编译器优化
:开启最高级别的尺寸优化(-Os)。注意,某些安全相关的循环(标记有
WAIT_LOOP的)不能被编译器优化掉。 -
库函数选择
:使用
newlib-nano等缩略版C库,可以显著减少代码体积。
5. 进阶话题与生产部署考量
5.1 密钥管理与安全生命周期
对于量产产品,密钥管理是安全的核心。MCUboot FIT结合瑞萨TSIP/RSIP的方案,提供了从开发到产线的完整安全路径:
-
开发阶段
:使用工具(如
openssl)生成测试用的密钥对。在开发板上,可以通过key_injection演示程序,将测试公钥注入Flash进行调试。 切记,此私钥绝不能用于量产 。 - 量产密钥生成 :在安全的离线环境中,使用硬件安全模块(HSM)或专门的密码机生成量产密钥对。私钥由公司严格保管,用于为所有出厂固件签名。
-
密钥注入
:在产线,每个设备需要注入唯一的或批量的公钥。这通过瑞萨的
SKMT工具
和
DLM服务
完成。流程是:SKMT生成一个“用户工厂编程密钥(UFPK)”,将其发送到瑞萨的DLM服务器,服务器用其内部的硬件根密钥(HRK)进行封装,生成一个“已封装的UFPK(W-UFPK)”。这个W-UFPK被下载到产线PC,再通过
key_injection程序和安全调试接口,注入到每个MCU的TSIP/RSIP中。整个过程,真正的私钥和UFPK从未离开过安全环境。 - 固件签名 :使用妥善保管的私钥,在构建服务器上对所有出厂固件进行签名。
- 设备验证 :设备端的MCUboot使用注入的公钥验证签名。由于公钥也是被封装保护的,即使从Flash中提取出来也无法直接使用,有效防止了密钥克隆。
5.2 看门狗与超时处理
在Bootloader执行Flash擦写操作时,耗时可能很长(几十到几百毫秒),容易触发独立看门狗(IWDT)复位。MCUboot FIT提供了看门狗喂狗机制。
-
配置
:启用
RM_MCUBOOT_CFG_WATCHDOG_FEED_ENABLED,并实现一个喂狗函数,通过RM_MCUBOOT_CFG_WATCHDOG_FEED_FUNCTION宏注册给MCUboot。 - 实现 :这个函数需要根据你使用的看门狗外设来编写。例如,如果是IWDT,就写入特定的重载值。
// 示例:用户定义的看门狗喂狗函数
void my_watchdog_feed(void) {
R_IWDT_Refresh(&g_iwdt_ctrl); // 假设使用r_iwdt FIT模块
}
// 在配置文件中注册
#define RM_MCUBOOT_CFG_WATCHDOG_FEED_ENABLED (1)
#define RM_MCUBOOT_CFG_WATCHDOG_FEED_FUNCTION my_watchdog_feed
- 注意 :喂狗间隔需要合理设置。如果Flash操作时间超过看门狗超时时间,就需要在Flash驱动的底层操作循环中也加入喂狗点,或者将看门狗超时时间设置得足够长。
5.3 与OTA更新框架的集成
MCUboot FIT负责的是本地验证和安装,它不包含无线下载(OTA)功能。一个完整的OTA系统通常分为三层:
- 通信与下载层 :由应用程序实现,负责通过Wi-Fi、BLE、LoRa等从服务器下载更新包,并写入Flash的Secondary Slot。需要处理网络协议、断点续传、数据校验(如CRC)等。
- 安全验证与安装层 :即MCUboot FIT。下载层完成后,通过设置标志位或软件复位,触发MCUboot在下一次启动时执行验证和安装。
-
回滚与状态上报层
:应用程序在启动后,需要检查启动状态(是否升级成功/失败),并通过
boot_is_upgrade_pending()等API与MCUboot交互。如果新版本运行不稳定,应用程序可以主动设置回滚标志,然后复位,让MCUboot执行回滚操作。同时,应将升级成功/失败的状态上报给服务器。
集成关键点
:确保下载层写入Secondary Slot的数据,是完整的、经过
imgtool
签名的二进制镜像,并且写入的起始地址与MCUboot配置的Secondary Slot地址完全一致。通常需要在应用程序和Bootloader之间定义一个固定的数据结构(例如在某个RAM地址或Flash页),用于传递升级状态、镜像大小等信息。

1万+


被折叠的 条评论
为什么被折叠?



