• 正文
  • 相关推荐
申请入驻 产业图谱

STM32H5 I2C Bootloader 深度技术解析

09/29 10:04
180
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

1. STM32H563启动模式与Boot Mode机制

STM32H563的启动模式(Boot Mode)是理解整个固件升级方案的逻辑起点。与传统STM32F系列仅依赖BOOT0引脚决定启动路径不同,STM32H5系列引入了Product State这一选项字节维度,使启动路径的决策更加灵活,也为硬件受限场景提供了软件替代方案。

根据RM0481参考手册,当TrustZone未使能(TZEN=0xC3)时,启动模式由PRODUCT_STATE选项字节、BOOT0引脚电平以及用户选项字节中的NSBOOTADD0/NSBOOTADD1共同决定。具体规则如下表所示:

PRODUCT_STATE BOOT0引脚 启动地址定义(选项字节) 启动区域 ST编程默认值
Open 0 NSBOOTADD0[31:8] 用户Flash(由选项字节定义) Flash: 0x08000000
Open 1 NSBOOTADD1[31:8] 系统Bootloader Bootloader
- Provisioning NA RSS Bootloader
Provisioned, Closed, Locked x NSBOOTADD0[31:8] 用户Flash(由选项字节定义) Flash: 0x08000000

从上表可以看出,当PRODUCT_STATE为Open时,BOOT0引脚的电平直接决定启动区域:BOOT0为0时从用户Flash启动,BOOT0为1时进入系统Bootloader。而当PRODUCT_STATE处于Provisioning状态时,无论BOOT0电平如何,芯片均进入RSS(Root Security Service,根安全服务)区域,用于执行安全配置操作。当PRODUCT_STATE处于Provisioned、Closed或Locked状态时,启动路径完全由选项字节NSBOOTADD0决定,BOOT0引脚被忽略。

这一机制正是本方案的技术基石:在BOOT0被硬件固定为高电平的情况下,通过将Product State从Open切换至Closed状态,即可使芯片忽略BOOT0引脚,转而从选项字节指定的用户Flash地址启动。上电复位后,芯片首先读取Product State选项字节,根据其值执行相应的启动决策流程,这一过程完全由硬件自动完成,无需用户程序干预。

资料获取:实战经验 | LAT1726 如何通过STM32H5 Bootloader进行固件升级与运行

2. Product State状态机深度解析

Product State是STM32H5系列引入的核心安全状态管理机制,通过选项字节中的PRODUCT_STATE字段(代码为OB)实现。该机制定义了8种产品状态,每种状态对应不同的调试策略与安全策略,形成一个从完全开放到完全锁定的渐进式安全模型。

PRODUCT_STATE(代码) 状态名称 调试策略 安全策略描述
0x2ED Open 开放(TZECE安全和非安全均开放) 用户Flash开放,调试接口完全开放
0x2F7 Provisioning 当TZEN=0xC3时,调试正在被安装 不可变根安全(IROT)正在被安装
0x2EE IROT-Provisioned 当TZEN=0xC3时,调试在HDP_LNS中开放 不可变根安全已安装,不可回退到Open
0x2FC TZ-Closed 调试限制在非安全区域 设计限制在非安全区域
0x2F2 Closed 关闭(需要安全认证进行调试访问) 默认安全状态,调试需认证
0x2EC Locked 转换到其他状态被禁止,即使有调试认证也不允许 最高安全锁定状态
0x2FA Regression 转换到Open状态 由调试认证系统初始化的回退
0x2F8 N2-Region 转换到TZ-Closed 由调试认证系统初始化的转换

在本方案中,核心涉及三种状态的转换:Open(初始状态)→ Provisioning(中间配置状态)→ Closed(最终运行状态)。Open状态下调试接口完全开放,BOOT0引脚控制启动路径,是芯片出厂的默认状态。Provisioning状态是一个过渡状态,专门用于执行OBKEY等安全配置的安装操作,此时芯片进入RSS区域执行安全服务。Closed状态是产品交付后的默认安全状态,调试接口需要通过安全认证(OBKEY)才能访问,启动路径由选项字节决定,BOOT0引脚不再起作用。

状态转换并非任意可行,而是受到严格的规则约束。例如,从Closed状态无法直接回退到Open状态,必须通过Regression流程并经过调试认证系统验证。Locked状态则禁止任何状态转换,即使拥有调试认证也无法更改。这些规则确保了产品状态的安全性与不可逆性,防止攻击者通过状态切换绕过安全机制。Product State的修改通过Bootloader的Special命令(0x50)配合更改Product State子命令(0x01)实现,命令参数中包含目标状态的代码值。

3. I2C Bootloader通信协议详解

I2C Bootloader通信协议是Host端与目标端之间交互的基础。根据AN4221应用笔记,STM32的I2C Bootloader采用标准I2C通信协议,目标端作为I2C从设备,Host端作为主设备发起通信。

在物理层参数方面,STM32H563的I2C Bootloader使用7位器件地址0x65。需要特别注意的是,在使用STM32 HAL库的I2C函数(如HAL_I2C_Master_Transmit)时,Slave地址参数应为7位地址左移1位后的结果,即0x65 << 1 = 0xCA。这是因为HAL库的地址参数包含了读写位,7位地址需要左移一位腾出最低位用于R/W控制。若直接传入0x65,将导致寻址错误,通信无法建立。

在引脚映射方面,参考工程使用PD12作为I2C_SCL时钟线,PD13作为I2C_SDA数据线。这两个引脚在NUCLEO-H563开发板上可用,且支持I2C复用功能。I2C总线的SCL和SDA线必须外接上拉电阻,这是I2C协议的物理层要求。若缺少上拉电阻,总线将无法正确释放,导致通信失败。典型的上拉电阻阻值为4.7kΩ,具体取值需根据总线电容与通信速率调整。

在协议层,I2C Bootloader遵循标准的命令-应答机制。Host端发送命令字节后,目标端返回ACK(0x79)或NACK(0x1F)表示命令是否被接受。对于需要数据传输的命令(如写Memory),Host端在收到ACK后继续发送数据,目标端每接收一个数据字节后返回ACK。命令执行完成后,目标端返回最终状态码。整个通信过程严格遵循AN4221中定义的时序要求,开发者在实现Host端时需严格按照文档中的时序图进行编程。

4. 固件升级命令集:0x44擦除与0x31写入

固件升级过程中,数据的写入涉及两个核心命令:Flash擦除命令(0x44)和写Memory命令(0x31)。这两个命令属于STM32 Bootloader的标准命令集,在AN4221中有详细的协议规范。

Flash擦除命令(0x44)用于在写入新固件前擦除目标Flash区域。STM32的Flash采用按页擦除机制,每次擦除一个或多个Flash页。Host端发送0x44命令后,需跟随擦除参数,包括要擦除的页数量及页编号列表。固件升级前,Host端需根据待下载固件的大小计算所需的Flash空间,确定需要擦除的页范围,然后发送0x44命令擦除对应页面。擦除操作是写入的前置条件,未擦除的Flash区域无法正确写入数据。

写Memory命令(0x31)用于将固件数据写入目标端的Flash或RAM区域。该命令的一个重要限制是每次最多支持写入256字节数据。因此,对于较大的固件文件,Host端需要将固件分割为多个256字节(或更小)的数据块,分多次调用0x31命令依次写入。每次写入时,Host端需指定目标起始地址和数据长度,目标端Bootloader接收到数据后将其写入指定地址的Flash或RAM。写入完成后,目标端返回状态码表示操作结果。

参考工程中的BOOT_Erase函数实现了Flash擦除命令的完整协议,BOOT_WriteMem函数则实现了写Memory命令。这两个函数封装了I2C通信的细节,包括命令发送、参数传输、ACK/NACK检测以及状态码解析,为上层应用提供了简洁的调用接口。开发者在移植时可直接参考这两个函数的实现逻辑。

5. Special命令(0x50)体系

Special命令(0x50)是STM32H5系列Bootloader特有的扩展命令集,用于支持Product State修改、OBKEY Provisioning、MCU复位等高级操作。这些操作不属于标准Bootloader命令,而是STM32H5安全架构的专属功能。根据AN2606与AN4221,Special命令的格式包含2字节命令码、2字节子命令码以及可变长度的数据字段。

功能 Special命令 子命令 数据字段 接收/发送数据 数据字节数 状态
更改Product State 0x50 0x01 Product State代码 发送:状态值 0x01 0x00
复位 0x50 0x02 Get 0x00000000 NA 0x00
打开Provisioning 0x50 0x03 Get 64位密码(两部分) 0x02 0x00
关闭Provisioning(HDP_L=1) 0x50 0x03 Get 64位密码(两部分) 0x02 0x00
Provisioning 0x50 0x83 Get Provisioning命令/错误代码 0x01 0x00

更改Product State子命令(0x01)是本方案中使用最频繁的Special命令。Host端发送0x50命令码后,跟随0x01子命令码,再发送1字节的目标Product State代码值(如Provisioning状态代码0x2F7,Closed状态代码0x2F2)。目标端接收到命令后,修改选项字节中的PRODUCT_STATE字段,并返回状态码。需要注意的是,Product State的修改涉及选项字节的写入,操作完成后通常需要复位才能生效。

复位子命令允许Host端通过I2C总线远程复位目标端MCU。这一功能在升级流程的最后一步至关重要:在完成固件下载与Product State配置后,Host端发送复位命令,目标端执行系统复位,复位后根据新的Product State从用户Flash启动。参考工程中的BOOT_SP_ResetCmd函数实现了该命令。

Provisioning子命令(0x83)用于执行OBKEY的Provisioning操作。该命令需要配合写Memory命令(0x31)使用:首先通过0x31命令将OBKEY数据写入目标端的SRAM3区域,然后发送0x50 + 0x83命令,Bootloader接收到该命令后,将RAM中的OBKEY数据写入OBKEY Flash区域,完成Provisioning过程。

6. OBKEY Provisioning技术原理

OBKEY(Option Byte Key,选项字节密钥)是STM32H5安全架构中的重要组成部分,用于调试接口的安全认证。当Product State处于Closed状态时,调试接口被锁定,只有通过正确的OBKEY认证才能恢复调试访问权限。OBKEY Provisioning即是将OBKEY数据写入芯片专用OBKEY Flash区域的过程。

OBKEY文件由ST提供的工具生成,包含Header和Payload两部分。Header中包含了OBKEY的元数据与校验信息,Payload则是实际的OB密钥数据。然而,工具生成的OBKEY文件Header格式与Bootloader执行Provisioning时所需的Header格式并不一致,因此在将OBKEY下载到目标端RAM之前,需要按照RM0481参考手册中的要求重新计算Header。

Header重计算的核心是CRC-32校验。根据文档要求,CRC计算使用CRC-32算法,多项式为0x4C11DB7(Ethernet标准),初始值为0xFFFFFFFF。计算范围覆盖OBKEY数据缓冲区(pConfig->pData)中的Provisioning数据,不包含OBKey源数据。计算得到的CRC值写入Header的相应字段。参考工程中的Run_ProvisionCmd函数包含了Header重计算的完整逻辑。

OBKEY Provisioning的完整流程包含两个步骤:第一步,Host端通过写Memory命令(0x31)将处理后的OBKEY数据写入目标端的SRAM3内存区域;第二步,Host端发送Special命令(0x50)配合Provisioning子命令(0x83),Bootloader接收到该命令后,将SRAM3中的OBKEY数据写入OBKEY Flash区域,完成Provisioning。

关于OBKEY的版本选择,由于本方案基于TrustZone未使能的条件,因此必须使用带密码的OBKEY版本,即STM32Cube FW_H5 V1.5.1包中的DA_ConfigWithPassword.obk文件。带密码版本的OBKEY在Provisioning时需要提供64位密码,用于打开和关闭Provisioning状态。若使用无密码版本,在TrustZone未使能的条件下Provisioning将失败。

存放OBKEY数据的目标RAM必须位于SRAM3地址区域。这是因为Provisioning操作由RSS(根安全服务)执行,而RSS仅能访问SRAM3这一非安全区域的内存。若将OBKEY数据存放在其他RAM区域(如SRAM1或SRAM2),Provisioning命令将无法正确读取数据,导致操作失败。SRAM3的具体地址范围需参考RM0481参考手册中的内存映射章节。

7. 关键技术陷阱与规避策略

在实现STM32H5 I2C Bootloader固件升级方案的过程中,开发者可能遇到多个技术陷阱。以下对LAT1726文档中明确指出的常见问题进行归纳,并提供规避策略:

陷阱一:I2C通信缺少上拉电阻。I2C协议要求SCL和SDA线必须为开漏输出并外接上拉电阻。若Host与目标H5之间的I2C通信未接上拉电阻,总线电平将无法正确拉高,导致通信完全失败。规避策略:在硬件设计时,在SCL和SDA线上各接一个4.7kΩ上拉电阻至VDD;在面包板或飞线验证时,务必确认上拉电阻已连接。

陷阱二:I2C HAL函数地址参数未左移。STM32 HAL库的I2C主设备函数要求Slave地址参数为7位地址左移1位后的值。STM32H563 Bootloader的7位地址为0x65,因此HAL函数的地址参数应为0xCA(0x65 << 1)。若直接传入0x65,将导致I2C寻址错误,目标端无法响应。规避策略:在代码中定义地址时明确注释,使用(0x65 << 1)的表达式形式,避免硬编码错误。

陷阱三:OBKEY文件头格式不匹配。工具生成的OBKEY文件Header与Bootloader Provisioning所需的Header格式不一致,若直接下载原始OBKEY文件,Provisioning将失败。规避策略:在下载OBKEY到目标RAM之前,必须按照RM0481的要求重新计算Header,包括CRC-32校验值的更新。参考工程中的Run_ProvisionCmd函数提供了完整的Header重计算实现。

陷阱四:OBKEY数据未使用SRAM3空间。Provisioning操作要求OBKEY数据必须存放在SRAM3区域,若使用其他RAM区域,Bootloader无法读取数据。规避策略:在调用写Memory命令下载OBKEY时,目标地址必须指向SRAM3的地址范围;在代码中使用宏定义SRAM3基地址,避免地址硬编码错误。

陷阱五:TrustZone未使能时误用无密码OBKEY。本方案基于TrustZone未使能条件,必须使用带密码的OBKEY版本(DA_ConfigWithPassword.obk)。若使用无密码版本,Provisioning过程将失败。规避策略:明确项目的TrustZone配置状态,根据配置选择对应的OBKEY文件;在参考工程中,OBKey.s文件已嵌入正确的带密码OBK文件。

陷阱六:Product State切换顺序错误。Product State的转换必须遵循Open → Provisioning → Closed的顺序,且每步切换后需要确认状态已生效。若跳过Provisioning状态直接从Open切换到Closed,或在状态未生效时执行下一步操作,将导致后续流程异常。规避策略:严格按照七步流程执行,每步操作后读取并验证Product State值。

8. 内存布局与SRAM3使用规范

STM32H563具有多层次的SRAM架构,包括SRAM1、SRAM2、SRAM3等多个独立的RAM区域,各区域具有不同的安全属性与访问权限。在OBKEY Provisioning场景中,SRAM3扮演着不可替代的角色。

Provisioning数据在目标端RAM中的组织遵循特定的结构规范,包含以下元素:

结构元素 说明 约束条件
pSource 待Provisioning数据的地址 必须在SRAM3地址范围内(非安全区域)
pDestination 存储Provisioning数据的目标地址 必须在OBKeys区域内
Size 待Provisioning数据的大小(字节数) 必须为16的倍数
CryptoAlgorithm 加密算法标识 RSSUB_DataProvisioning函数必须加密或不加密OBKeys
STM32H56xx:仅允许DwPSAAADU或DwCADAADU
DwTPSAAADU:通知RSSUB_DataProvisioning根据相关OBKey源数据加密
DwCAAADU:通知RSSUB_DataProvisioning以明文编程OBKey源数据
Crc CRC校验值 CRC计算使用CRC-32(多项式0x4C11DB7,Ethernet)
初始值:0xFFFFFFFF

pSource字段指定了待Provisioning数据在RAM中的起始地址,该地址必须落在SRAM3的地址范围内。SRAM3被设计为非安全区域(Non-secure alias),可被RSS安全服务访问。这一设计确保了Provisioning过程的安全性:即使Host端将数据写入SRAM3,RSS在执行Provisioning时也能安全地读取数据,而不会暴露给非安全世界的其他代码。

Size字段要求数据长度必须为16字节的倍数,这是由加密算法的块大小决定的。若OBKEY数据长度不是16的倍数,需要在末尾进行填充。CryptoAlgorithm字段指定了Provisioning过程中使用的加密策略,对于STM32H563,仅支持DwPSAAADU(加密)和DwCADAADU(明文)两种选项。Crc字段存储了Provisioning数据的CRC-32校验值,用于Bootloader验证数据完整性。

在实际实现中,Host端需要在本地内存中构建符合上述结构的Provisioning数据块,然后通过写Memory命令将其完整写入目标端的SRAM3区域,最后发送Provisioning子命令触发Bootloader执行数据搬移与校验。整个过程对内存地址的准确性与数据结构的规范性有严格要求,任何字段的错误都可能导致Provisioning失败。

相关推荐