一、CRC 基础概述
1.1 CRC 概述
其中 CRC 分为硬件 CRC 和软件CRC,本文主要讲述硬件 CRC。硬件 CRC 在开始时 CRC 寄存器的每一位都预置为 1,然后把 CRC 寄存器与 8-bit 的数据进行异或,之后对 CRC 寄存器从高到低进行移位,在最高位(MSB)的位置补零,而最低位(LSB,移位后已经被移出 CRC 寄存器)如果为 1,则把寄存器与预定义的多项式码进行异或,否则如果 LSB 为零,则无需进行异或。重复上述的由高至低的移位 8 次,第一个 8-bit 数据处理完毕,用此时 CRC 寄存器的值与下一个 8-bit 数据异或并进行如前一个数据似的 8 次移位。所有的字符处理完成后 CRC 寄存器内的值即为最终的 CRC 值。
软件 CRC 的根本思想就是先在要发送的帧后面附加一个二进制序列的校验码,生成一个新帧发送给接收端。当然,这个附加的数不是随意的,它要使所生成的新帧能与发送端和接收端共同选定的某个特定数整除(注意,这里不是直接采用二进制除法,而是采用一种称之为“模 2 除法”)。到达接收端后,再把接收到的新帧除以(同样采用“模 2 除法”)这个选定的除数。因为在发送端发送数据帧之前就已通过附加一个数,做了“去余”处理(也就已经能整除了),所以结果应该是没有余数。如果有余数,则表明该帧在传输过程中出现了差错。
1.2 CRC 引擎特征
① 支持三种多项式算法:
A. CRC-CCITT: x16+ x12+ x5+ 1
B. CRC-16: x16+ x15+ x2+ 1
C. CRC-32: x32+ x26+ x23+ x22+ x16+ x12+ x11+ x10+ x8+ x7+ x5+ x4+ x2+ x + 1
② 可编程的种子数设置
③ 输入数据宽度:字节(8bit)、半字(16bit)、字(32bit)
④ CRC 的输入和输出数据可以位反正和1的补码
1.3 CRC 功能框图
二、CRC 寄存器描述及配置
2.1 CRC 寄存器描述
和 CRC 相关的寄存器只有 4 个:
① MODE: 配置 CRC 的模式(包括多项式、位序等)
② SEED: CRC 计算的种子
③ SUM: CRC 计算结果
④ WR_DATA: 要计算 CRC 的数据寄存器
2.2 CRC 基本配置
使能 CRC 引擎时钟调用 Chip_CRC_Init() 函数使能 CRC 引擎时钟
注:使用 CRC 之前一定要记得使能 CRC 引擎的时钟。
2.3 CRC 功能描述
CRC 算法的标准设置如下表
三、LPC824 CRC 动手实验
3.1 实验目的:通过本实验,理解和掌握 LPC82x CRC 的过程
3.2实验软/硬件环境搭建:
− 硬件:LPC824Lite-V1.0(评估板)
− 软件:SDK 从 NXP 官网下载(https://mcuxpresso.nxp.com/en/select);
工程位置:
..\peri_example\crc\crc_example\project_crc_example.uvprojx
3.3 实验描述:
使用 CRC 引擎计算 8 位、16 位和 32 位的 CRC 计算。CRC 引擎持续的进行 CRC 计算并且校验 CRC 的结果。system tick 产生一个 10Hz 的时钟,每隔 100ms,把 CRC 的结果 result[2] 进行减 1 (即修改为错误的值)。每个循环计算的 result[0] ~ result[4] 的值再进行 CRC32 计算,将计算出的结果 gencrc 与 expect 进行比较,一致时则熄灭 LED1,当计算结果不一致时则点亮 LED1,本实验能看出的直观效果是 LED1 在不停地闪烁。
3.4 实验结果:
3.5 软件设计步骤
3.5.1 初始化 CRC
/**
* @brief Enable system or peripheral clock
* @param clk : Clock to enable
* @return Nothing
*/
STATIC INLINE void Chip_Clock_EnablePeriphClock(CHIP_SYSCTL_CLOCK_T clk)
{
LPC_SYSCTL->SYSAHBCLKCTRL = (1 << clk) | (LPC_SYSCTL->SYSAHBCLKCTRL & ~SYSCTL_SYSAHBCLKCTRL_RESERVED);
}
3.5.2 进行 CRC 计算并且校验
static const uint8_t bytes[] = {0x38, 0x38, 0x38, 0x38}; // 半节
static const uint16_t words[] = {0x3534, 0x3534, 0x3534, 0x3534}; // 半字
static const uint32_t dwords[] = {0x33323130, 0x33323130, 0x33323130, 0x33323130}; // 字
static const uint32_t expect[] = {0x56AB, 0x7A89, 0xD7D6, 0x7D27, 0xA6669D7D}; // 期望值
uint32_t Chip_CRC_CRC8(const uint8_t *data, uint32_t bytes)
{
Chip_CRC_UseCCITT(); // 调用 MODE 和 SEED 寄存器进行模式配置 (MODE_CFG_CCITT) // 以及 Seed (CRC_SEED_CCITT) 选择
while (bytes > 0)
{
Chip_CRC_Write8(*data); // 调用 WRDATA8 寄存器进行数据写入
data++;
bytes--;
}
return Chip_CRC_Sum(); // 调用 SUM 寄存器进行 CRC 结果计算
}
uint32_t Chip_CRC_CRC16(const uint16_t *data, uint32_t hwords)
{
Chip_CRC_UseCRC16(); //调用 MODE 和 SEED 寄存器进行模式配置 (MODE_CFG_CRC16)
//以及 Seed (CRC_SEED_CRC16) 选择
while (hwords > 0)
{
Chip_CRC_Write16(*data); // 调用 WRDATA16 寄存器进行数据写入
data++;
hwords--;
}
return Chip_CRC_Sum(); // 调用 SUM 寄存器进行 CRC 结果计算
}
uint32_t Chip_CRC_CRC32(const uint32_t *data, uint32_t words)
{
Chip_CRC_UseCRC32(); // 调用 MODE 和 SEED 寄存器进行模式配置 (MODE_CFG_CRC32) // 以及 Seed (CRC_SEED_CRC32) 选择
while (words > 0)
{
Chip_CRC_Write32(*data); // 调用 WRDATA32 寄存器进行数据写入
data++;
words--;
}
return Chip_CRC_Sum(); // 调用 SUM 寄存器进行 CRC 结果计算
}
/* 通过设置 4 个寄存器进行计算 半节 半字 字 三种模式的 CRC 运算 */
while (1) {
result[0] = Chip_CRC_CRC8(&bytes[0],1);
result[1] = Chip_CRC_CRC8(bytes, (sizeof(bytes) / sizeof(bytes[0])));
result[2] = Chip_CRC_CRC16(&words[0], 1);
if (ticks % 2 == 0)
{
result[2] -= 1; // 每一个 Tick 时钟人为修改 CRC 计算值
}
result[3] = Chip_CRC_CRC16(words, (sizeof(words) / sizeof(words[0])));
result[4] = Chip_CRC_CRC32(&dwords[0], 1);
result[5] = Chip_CRC_CRC32(dwords, (sizeof(dwords) / sizeof(dwords[0])));
gencrc = Chip_CRC_CRC32((uint32_t *) result, 5); // 实际计算 CRC 值(包括人为错误移位)
result[0] = Chip_CRC_CRC32((uint32_t *) expect, 5); // 计算期望 CRC 值(不包括错误移位)
if (result[0] != gencrc) {
Board_LED_Set(1, true); // 亮灯
}
else {
Board_LED_Set(1, false); // 灭灯
}
__WFE(); // 等待下一个 Tick
}