一、概述
无论是新手还是大佬,基于STM32单片机的开发,使用STM32CubeMX都是可以极大提升开发效率的,并且其界面化的开发,也大大降低了新手对STM32单片机的开发门槛。
本文主要讲述STM32芯片的CRC外设配置及CRC校验的一些基础知识。CRC也有一些软件算法的实现,但现在大部分芯片都内置了硬件算法,不用白不用,而且最近因为校验了几百k的文件,发现用软件校验速度是个瓶颈,所以还是试一下硬件CRC的速度(手册里写了只要4个指令周期就可以计算一次CRC)。
二、软件说明
STM32CubeMX是ST官方出的一款针对ST的MCU/MPU跨平台的图形化工具,支持在Linux、MacOS、Window系统下开发,其对接的底层接口是HAL库,另外习惯于寄存器开发的同学们,也可以使用LL库。STM32CubeMX除了集成MCU/MPU的硬件抽象层,另外还集成了像RTOS,文件系统,USB,网络,显示,嵌入式AI等中间件,这样开发者就能够很轻松的完成MCU/MPU的底层驱动的配置,留出更多精力开发上层功能逻辑,能够更进一步提高了嵌入式开发效率。
演示版本 6.7.0
三、CRC简介
循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。
- CRC相关参数
数据位宽:如CRC-8、CRC-16、CRC-32,后面的数字表示的是CRC的数据位宽,目前Modbus/RTU使用的是CRC-16。
初始值:用于计算的CRC初始值,因为CRC是循环计算的,所以可以分段计算,只要把初值设置成上一次计算的CRC输出结果即可。
多项式:正序的多项式,省略最高位1,即数据高位在左,低位在右时为正序。如Modbus的正序模型 x16+x15+x2+1(1x16+1x15+0x14+0x13+0x12+0x11+0x10+0x9+0x8+0x7+0x6+0x5+0x4+0x3+1x2+0x1+1x0),二进制为1 1000 0000 0000 0101,省略最高位1,转换为十六进制为0x8005。
逆多项式:倒序的多项式,即数据高位在右,低位在左为倒序。0x8005的逆多项式即为0xA001。
输出异或值:用于在输出CRC结果之前进行异或计算的值。
- CRC计算步骤(以Modbus/RTU的CRC计算为例)
- 预置16位寄存器为十六进制0xFFFF(即全为 1),称此寄存器为CRC寄存器;
- 把第一个8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器;
- 检测相异或后的CRC寄存器的最低位,若最低位为1,CRC寄存器先右移1位,再与多项式A001H进行异或;若为0,则CRC寄存器右移1位,无需与多项式进行异或。
- 重复步骤3,直到右移8次,这样整个8位数据全部进行了处理;
- 重复步骤2到步骤4,进行下一个8位数据的处理,直到需要处理的字节数据都处理完;
- 最后得到的CRC寄存器即为CRC-16校验码。
四、功能配置
首先我们要先明确好学习CRC配置的目标——实现一个Modbus/RTU的CRC校验接口函数,刚好前面写了那么多Modbus的文章,这里就来实践一下,Modbus/RTU中使用的正是CRC校验。先贴上现在使用的CRC校验算法源码。
/* CRC校验函数,返回值是CRC校验值 */
uint16_t MBCrcCalc(const uint8_t *data, /* 需要校验的数据 */
uint16_t length) /* 校验数据的长度 */
{
uint8_t i;
uint16_t crc_value = 0xFFFF;
while (length --)
{
crc_value ^= (uint16_t)*(Data ++);
for (i = 7; i >= 0; i --)
{
crc_value = (crc_value & 0x0001) ? ((crc_value >> 1)^0xa001) : (crc_value >> 1);
}
}
return crc_value;
}
首先使能激活CRC功能,看相关配置里可以配置数据位宽、初始值、多项式等这些信息的,那就根据Modbus的标准进行配置。
根据上文CRC的基本参数原理,我们这里对Modbus-CRC进行如下设置,下面是LL库的代码实现。
/* CRC校验函数,返回值是CRC校验值 */
uint16_t MBCrcCalc(const uint8_t *data, /* 需要校验的数据 */
uint16_t length) /* 校验数据的长度 */
{
/* 计算前复位一下--这个操作会清除内部存储的数据,如果需要分段计算,建议另开一个接口 */
LL_CRC_ResetCRCCalculationUnit(CRC);
/* 设置输出CRC位数--Modbus用的是CRC-16 */
//LL_CRC_SetPolynomialSize(CRC, LL_CRC_POLYLENGTH_16B);
/* 设置输入数据反向模式--输入按字节反向 */
//LL_CRC_SetInputDataReverseMode(CRC, LL_CRC_INDATA_REVERSE_BYTE);
/* 设置输出反向 */
//LL_CRC_SetOutputDataReverseMode(CRC, LL_CRC_OUTDATA_REVERSE_BIT);
/* 设置计算多项式--使用0x8005 */
//LL_CRC_SetPolynomialCoef(CRC, 0x8005);
/* 设置CRC初始值 */
LL_CRC_SetInitialData(CRC, (uint32_t)0xFFFF);
/* 循环写入计算数据--按单个字节写入 */
while(length--)
{
LL_CRC_FeedData8(CRC, *Data++);
}
/* 获取CRC计算结果--按半字取出 */
return LL_CRC_ReadData16(CRC);
}
查看LL库的接口,可使用的基本就以下几个接口。
LL_CRC_ResetCRCCalculationUnit /* 重置计算 */
LL_CRC_SetPolynomialSize/LL_CRC_GetPolynomialSize /* 设置/获取输出CRC的数据位宽 */
LL_CRC_SetInputDataReverseMode/LL_CRC_GetInputDataReverseMode /* 设置/获取输入数据反向模式 */
LL_CRC_SetOutputDataReverseMode/LL_CRC_GetOutputDataReverseMode /* 设置/获取输出数据反向模式 */
LL_CRC_SetInitialData/LL_CRC_GetInitialData /* 设置/获取CRC计算初值 */
LL_CRC_SetPolynomialCoef/LL_CRC_GetPolynomialCoef /* 设置/获取多项式 */
LL_CRC_FeedData32/LL_CRC_FeedData16/LL_CRC_FeedData8/LL_CRC_ReadData32/LL_CRC_ReadData16/LL_CRC_ReadData8/LL_CRC_ReadData7 /* 写入计算数据/获取CRC结果 */
LL_CRC_Read_IDR/LL_CRC_Write_IDR /* 读取/写入通用寄存器(不知道有什么用) */
LL_CRC_DeInit /* 重置初始化 */
五、注意事项
1、国标Modbus的CRC正序多项式为0x8005,倒序多项式为0xA001。如果协议里数据是高位在左低位在右,则按正序多项式设置即可。