stm32/mcu/hw-crc.md

硬件 CRC 单元:一次实验看懂循环冗余校验

知识库 · STM32 📅 初版 2026-09-15

硬件 CRC 单元:一次实验看懂循环冗余校验

2026-09-15 实测验证:洋桃 1 号开发板(STM32F103C8T6),demo47_CRC 改造版,串口每秒输出 CRC a=56327939 b=C527FC48 c=5A,Keil MDK 5.25.3.0 编译 0 Error 0 Warning,STM32CubeProgrammer CLI 2.23.0 SWD 烧录通过。

#一、元信息

字段值
芯片/平台STM32F103C8T6(洋桃 1 号开发板,标准库)
外设类型硬件 CRC32 单元(AHB 总线外设,多项式 0x4C11DB7,初始值 0xFFFFFFFF)
验证状态✅ 已验证(2026-09-15)
验证环境洋桃1号开发板 / Keil MDK 5.25.3.0(0 Error 0 Warning)/ STM32CubeProgrammer CLI 2.23.0(SWD 烧录)/ 2026-09-15
驱动方式官方 demo47_CRC 改造版(crc.c),结果经 USART1 printf 每秒输出

#二、实验平台与改造:demo47_CRC

  • 官方原版的局限:demo47_CRC 把计算结果只放在调试器变量里——接上调试器才能看到,脱机运行什么都观察不到;
  • 我们的改造:把结果改成串口 printf 每秒输出一次,接个 USB-TTL 就能实时观察,也让"每秒结果都一样"这件事变得肉眼可验证;
  • 顺带一课:官方 demo 是"裸"工程,要补上板级串口驱动才能真正用 printf。

给任意官方 demo 补板级串口驱动,三步:

  1. 把 usart.c / usart.h 拷进工程(含时钟、引脚、USART1 初始化与 printf 重定向);
  2. 把文件注册进 Keil 工程文件(.uvproj)——不注册就不参与编译,头文件找得到、函数链接不到;
  3. main 里 USART1_Init() 后即可 printf。

#三、STM32 硬件 CRC 速览

函数/操作作用
RCC_AHBPeriph_CRC使能 CRC 单元的 AHB 时钟:RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE)
CRC_ResetDR()复位清态:把滚动状态清回初始值 0xFFFFFFFF,新一轮计算前必须先清态
CRC_CalcCRC(x)喂一个 32 位字:把 x "搅拌"进滚动状态,并返回搅拌后的当前 CRC 值(可连续喂,逐次累积)
CRC_CalcBlockCRC(p, n)整块计算:一次性喂入 n 个 32 位字,返回最终值
CRC_SetIDRegister() / CRC_GetIDRegister()独立 8 位 IDR 寄存器:与 CRC 计算无关,可当 1 字节"临时便签"自由读写

核心调用顺序:

cRCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);   /* 1. 开时钟 */

CRC_ResetDR();                                      /* 2. 清态回初值 */
a = CRC_CalcCRC(0x12345678);                        /* 3. 逐个喂字,返回累积 CRC */
a = CRC_CalcCRC(0x23456789);
a = CRC_CalcCRC(0x34567890);                        /* a = 最后一次返回值 */

CRC_ResetDR();
b = CRC_CalcBlockCRC(y, 3);                         /* 4. 整块一次算完 */

CRC_SetIDRegister(0x5A);                            /* 5. IDR 便签:写 0x5A */
c = CRC_GetIDRegister();                            /*    回读 0x5A → 寄存器通路 OK */

#四、实测数据(串口每秒输出)

USART1 调试串口,每秒输出一行:

txtCRC a=56327939 b=C527FC48 c=5A
输出来源含义
a=56327939对 x[]={0x12345678, 0x23456789, 0x34567890} 逐个 CRC_CalcCRC() 喂入最后一次返回的累积 CRC(十进制显示,即 0x035B7F03)
b=C527FC48对 y[]={0x87654321, 0x98765432, 0x09876543} 用 CRC_CalcBlockCRC() 整块计算同一算法、另一份数据的 CRC(十六进制显示)
c=5AIDR 寄存器写 0x5A 再回读写进什么读出什么,寄存器读写通路正常

#五、关键问答

  • Q1:为什么 a 每秒都一样?

因为 CRC 是确定性算法:相同输入序列 + 相同初态 = 相同输出。每次输出前都 CRC_ResetDR() 清回初值,喂的字也一字不差,结果自然每秒一致。确定性正是 CRC 能当校验码的前提——发送方、接收方各算一遍,对得上就是好数据。

  • Q2:为什么 a≠b?

因为它们根本不是同一份数据:a 算的是 x 数组,b 算的是 y 数组。同一个数,逐个喂和整块算结果完全一样(把 y 数组逐个喂,得到的值就是 b);不同数据算出不同值,CRC 才有"发现损坏"的能力。

  • Q3:a 和 b 能直接比大小吗?

不能拿数值直接比——一个按十进制打印、一个按十六进制打印(a=56327939 即 0x035B7F03)。它们本来就来自两份不同的数据,比较没有意义。

#六、CRC 应用一览

场景用的 CRC说明
Modbus RTUCRC16工控串口总线标准帧尾校验
CAN 总线帧内建 CRC控制器硬件自动计算/校验,帧尾 15 位
以太网CRC32(FCS)每个以太网帧尾 4 字节帧校验序列
bootloader常用 CRC32跳转 App 前校验 App 区完整性,防止跑到半个固件上变砖
EEPROM / Flash 配置块CRC8/16/32上电校验配置数据,坏了回默认值
ZIP / PNG 等文件格式CRC32格式内部自带的完整性校验

#七、边界:CRC 能做什么、不能做什么

  • 只能发现意外损坏(线噪、位翻转、写坏的 Flash),不能纠错——发现错了只能重传/重读;要"就地纠错"得上 ECC / 海明码(NAND Flash、内存 ECC 那一套);
  • 防不了恶意篡改:算法公开、无密钥,攻击者可以连数据带 CRC 一起改好;防篡改要用密码学哈希(SHA-256)+ 签名(ECDSA/RSA);
  • 一句话:CRC 管"坏了没有",签名管"是不是我发的"。

#八、CRC 在 OTA 里的位置

  1. 下载固件(HTTPS/TLS 通道)→ 写入 Flash 备用区;
  2. 下载完先做 CRC / 哈希校验,不过关直接丢弃,绝不碰正在运行的固件;
  3. 校验通过 → 在元数据里标记有效;
  4. 复位 → bootloader 再验一次 CRC/签名 → 通过则跳转新固件;启动失败或再校验不过 → 回滚旧固件。

CRC 只解决"下载坏没坏"。完整 OTA 还需要固件签名(ECDSA/RSA)防伪造、TLS 防通道窃听篡改——安全启动就是"CRC 之外的那几层"。

#九、参考库/链接

  • STM32F103 参考手册(RM0008):CRC 章节(多项式 0x4C11DB7、数据寄存器 DR、独立 IDR)
  • 洋桃1号官方例程:demo47_CRC(本页改造对象)

#十、相关阅读

#11、更新日志

📝共 1 条记录
日期类型更新内容
2026-09-15新增首次建档(实测验证版)