RT-Thread论坛
直播中

尤立虔

9年用户 1068经验值
擅长:控制/MCU
私信 关注
[问答]

OTA采用ymodem_ota升级app分区出现CRC校验错误是怎么回事?

芯片:STM32F103VET6
环境:MDK5
使用的相关软件包:ota_downloader;

如上图,使用官方例程,BOOT运行起来之后,发现APP区CRC校验不对,但是会强制升级,并且升级成功,不知道是哪里问题?这个错误提示是因为我哪里操作错了导致的,还是就是正常现象?虽然表面上看不影响我使用OTA升级,但我想明白一下,原因是什么?有没有大神,指导一下,谢谢。


回帖(2)

安喆

2025-9-10 18:02:10
建议使用qboot
举报

康大争

2025-9-12 18:19:44

你遇到的问题是STM32 OTA升级过程中一个很经典的"CRC校验失败但强制升级成功"现象。这通常是计算CRC的范围或方法在Bootloader和应用(或者传输协议)之间存在轻微差异造成的。以下是最可能的原因和解决方法:


? 核心原因分析




  1. 向量表(VTOR)偏移处理差异(最常见原因):



    • 应用(APP)程序编译链接时,它的向量表起始地址并不是Flash起始位置0x08000000,而是APP区的起始地址(例如0x08010000)。

    • 当Bootloader计算APP区CRC时,它很可能是从APP区的起始地址开始计算(例如0x08010000)。

    • 关键问题: 应用(APP)程序本身在它的开始部分(偏移0处),存放着一个4字节的值,指向它自己的向量表位置(即它自己的起始地址)。这个值称为initial_sp(初始化栈顶指针)。

    • 很多Bootloader计算CRC时,错误地包含了存储这个向量表地址的4字节。而发送端(PC的Ymodem传输程序)在发送.bin文件时,是从APP区的代码开始发送的,并不包含链接器为了向量表偏移额外填入的那个向量表地址指针值(通常是0x20005000这样的RAM栈顶地址,或者编译后实际指向向量表的地址值)。这导致Bootloader计算CRC时多加了4个不该有的字节。

    • 举个例子:假设APP区从0x08010000开始。Bootloader从0x08010000开始计算CRC。但传输过来的.bin文件第一个有效字节对应的是0x08010000处的指令(比如0x20005000这个地址本身对应的机器码的低字节0x00),而Bootloader读取0x08010000处的值期望它是一个栈顶地址值(比如0x20005000)。这种表示方法的不一致就导致了起始CRC计算的差异。




  2. CRC计算范围不匹配:



    • 数据长度差异: Bootloader计算CRC时使用的数据长度是否严格等于通过Ymodem接收并写入Flash的数据的实际长度?传输结束后得到的总长度是否正确?Ymodem协议是否有在文件传输前发送文件长度信息,Bootloader是否准确获取了这个长度并在计算CRC时使用了它?

    • 包含/排除了某些特定区域: Bootloader计算CRC时是否包含了APP区中不应属于应用代码本身的部分(例如预留的配置区、RTC备份寄存器标志区?(这个可能性小一点))。

    • 未初始化区域处理: 应用的.bin文件末尾通常包含大量0xFF(未初始化数据),但实际写入Flash后,计算CRC时会包含这些0xFF。Ymodem传输的.bin文件长度是实际的代码+已初始化数据长度(不含未初始化数据)。如果Bootloader计算CRC的范围是按照整个APP区的理论大小(链接脚本定义的LENGTH),包含了未写入的0xFF区域,那么计算值就肯定对不上传输文件本身的CRC(它只计算了传输过去的部分)。




  3. CRC算法/初始值/输入数据反转等配置不一致:



    • 确保Bootloader中计算CRC使用的函数/硬件CRC模块的配置与PC端生成校验码时使用的CRC算法完全一致。重点检查:

      • 多项式 (Polynomial): 例如 0x04C11DB7 (STM32硬件CRC默认)。

      • 初始值 (Initial Value): 如 0xFFFFFFFF (STM32硬件CRC默认)。

      • 输入数据反转 (Input Data Inversion): STM32硬件CRC默认按字输入无反转。Ymodem协议通常也这样。

      • 输出数据反转 (Output Data Inversion): STM32默认无反转。

      • 异或输出值 (Output XOR Value): STM32默认 0x00000000





  4. 数据包校验 vs 整区校验:



    • Ymodem协议本身通常包含对每个128字节数据包的CRC校验,保证传输过程中单包数据正确。

    • 你收到的"CRC校验错误"是Bootloader在接收完整个APP文件并将其写入Flash后,对整个APP区进行完整性CRC校验失败的错误。这是两个独立的校验环节。




  5. Flash读干扰或时序问题(可能性较低):



    • 在Bootloader读取刚写入的Flash进行计算时,如果系统时钟配置不稳定或Flash读访问时序设置有误,可能导致读出错误数据。但通常整体校验出错(非零星错误)不太会是这个原因,而且升级后APP能运行说明写Flash是基本正确的。




? 验证方法




  1. 打印调试信息:



    • 在Bootloader中,在执行完整区CRC计算之前和之后,打印以下关键信息:

      • APP区起始地址: printf("APP Start: 0x%08Xn", APP_START_ADDR);

      • APP区用于计算CRC的长度: printf("CRC Length: %d bytesn", received_file_length); // 或实际使用的长度值

      • Bootloader计算出的CRC值: printf("BL Calc CRC: 0x%08Xn", calculated_crc);

      • Ymodem传输文件时附带的CRC值: printf("Received CRC: 0x%08Xn", received_crc);





  2. 使用独立工具计算CRC:



    • 将你通过Ymoderm成功传输并写入到STM32 Flash中的APP固件数据通过调试器(J-Link, ST-Link等)读出来,保存为一个.bin文件。

    • 在PC上,使用你确认过的可靠CRC计算工具(如带CRC32功能的HEX编辑器HxD, Python的binascii.crc32, 或命令行工具crc32),计算这个读出来的.bin文件的CRC。

      • 计算时注意,工具计算的范围应该是从文件起始到文件结束。


    • 同样在PC上,计算原始用于Ymodem传输的那个.bin文件的CRC。

    • 比较这三个值:

      • A: Bootloader计算的值

      • B: 从Flash读出来的.bin文件计算的值

      • C: 原始传输的.bin文件计算的值


    • 如果 B == C A != B几乎可以肯定Bootloader计算CRC的范围或方法有误 (通常就是起始地址多算了4字节的那个指针值)。

    • 如果 B != C: 说明Flash写入内容与原始文件不一致,需要排查Flash写入过程的问题(可能性小,因为APP能运行)。

    • 如果 A == C: 说明传输过程或Bootloader接收处理有误(极不可能,因为APP能跑)。




  3. 检查Bootloader CRC计算代码:



    • 仔细看Bootloader计算APP区完整CRC的代码片段。

    • 确定它使用的起始地址:是否是严格的APP起始地址(绝对地址)。

    • 确定它使用的长度:是整个APP分区的大小(固定的),还是接收到的实际字节数?

    • 计算前的Flash操作: 是否在计算前等待Flash操作完成?是否需要解锁Flash读操作?

    • 如果使用了硬件CRC:

      • 初始化是否正确?

      • 是否在开始计算前重置了CRC寄存器(写入CRC_CRRESET位)?

      • 数据是否按32位(字)写入?如果长度不是4字节对齐,最后一次写入需要特殊处理吗?





✅ 解决方法 (根据最常见的向量表偏移问题)




  1. 修改Bootloader的CRC计算范围:


    // 假设:
    #define APP_START_ADDR        0x08010000 // APP区起始地址
    uint32_t app_actual_length = ...; // 通过Ymodem接收到的APP文件的实际字节数

    // 计算CRC的起始地址应该跳过最初的向量表偏移地址(通常是4字节)
    // 即计算范围应该是: APP_START_ADDR + 4 到 APP_START_ADDR + app_actual_length - 1
    // 或者更通用地:指向第一条指令(Reset_Handler)的地址开始计算。

    uint32_t crc_calculated = 0;
    uint32_t start_address_for_crc = APP_START_ADDR + 4; // 跳过前4字节(初始SP值)
    // 或者基于你的编译器,这个值可能是VTOR偏移量,确保你理解链接脚本

    // 使用实际接收长度(减去4字节,因为前4字节被跳过了)或者计算到start_address_for_crc + app_actual_length - 4
    uint32_t bytes_to_crc = app_actual_length - 4;

    // ... 调用你的CRC计算函数,从start_address_for_crc开始,计算bytes_to_crc长度的数据 ...



  2. 正确使用实际接收长度:



    • 确保在Ymodem传输结束时,你准确记录并保存了接收到的文件总字节数app_actual_length

    • 不要使用固定的APP分区大小(.ld文件中的长度)来计算CRC,除非你能保证传输的文件总是填满整个分区(通常不是)。




  3. 保持CRC算法一致性:



    • 如果在PC端生成验证CRC(如烧录前的参考值),务必使用与Bootloader完全相同的CRC算法配置。

    • 在Bootloader中使用硬件CRC计算器时,严格按照默认或协议要求配置。




  4. 合理处理强制升级:



    • 当前你的Bootloader在CRC验证失败后选择了"强制升级"(继续跳转),这对于允许降级或在某些应急场景下可能有用。

    • 但这存在风险:如果校验失败是由于Flash损坏、传输错误导致的APP不完整,强制运行可能导致系统死机。

    • 推荐做法:在调试阶段保留"强制升级"选项(通过按键或其它标志),但默认情况下校验失败则停止启动APP并回退到等待再次升级的状态,提高系统可靠性。毕竟能下载成功并不代表文件完全正确。




? 总结


你遇到的"CRC校验错误但升级成功"现象,核心问题大概率是Bootloader计算CRC时错误地包含了APP区前4字节的那个向量表偏移地址值(初始SP指针值)。这个值存在于Flash中供内核读取进行VTOR重定位,但它不是原始传输的.bin文件的组成部分。


解决方法: 修改Bootloader计算CRC的范围,使其从APP_START_ADDR + 4开始,长度为实际接收字节数 - 4(或者确保从第一条指令地址开始计算),确保计算范围与原始传输文件内容完全一致。同时加强调试信息输出以便验证。完成这些修改后,CRC校验应该就能正常通过了,同时保持可靠的文件完整性检查。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分