找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4675

积分

0

好友

609

主题
发表于 3 小时前 | 查看: 4| 回复: 0

在嵌入式通信里,数据校验不是可选项,而是基本防线。

为什么和校验不够

最朴素的校验方式是把所有字节相加,取低字节附在帧尾。实现起来简单,却挡不住一类常见错误:两个字节同时出错且增减相互抵消,或者数据整体发生移位。

// 和校验:易实现,但漏检率高
uint8_t checksum(uint8_t *p, int n)
{
uint8_t s = 0;
for (int i = 0; i < n; i++) s += p[i];
return s;
}

和校验只覆盖“加法”这一个维度,对突发错误(burst error,线路干扰经常连续错多位)几乎无能为力。工业通信(Modbus、CAN、以太网)普遍选用 CRC,正是因为 CRC 对连续多位错误有很强的检出能力。

CRC 到底在算什么

CRC 并不是“循环冗余”这个名字带来的数学玄学。它的本质是:把数据当作一个很大的二进制多项式,除以一个固定的生成多项式,取余数。这个余数就是校验值。接收方用同样的多项式再除一遍,如果余数不为 0,就说明传输有误。

生成多项式决定了检错能力。Modbus RTU 使用 CRC-16/MODBUS(多项式 0x8005,初始值 0xFFFF,输入反转、输出反转、异或输出 0x0000);CAN 使用 CRC-15;以太网使用 CRC-32。多项式不同,同一段数据算出来的 CRC 完全不同——两端必须使用同一套参数定义,否则永远对不上。

参数陷阱

经常有人照搬一段 CRC-16 代码,和对方设备怎么都对不上,排查半天才发现是参数没对齐。CRC 一共有六个关键参数,缺一不可:

  • Width:校验位宽(16/32)
  • Poly:生成多项式
  • Init:寄存器初始值
  • RefIn:每个输入字节是否按位反转
  • RefOut:输出余数是否整体反转
  • XorOut:最终异或值
// Modbus RTU 的 CRC-16,参数缺一不可
uint16_t crc16_modbus(uint8_t *data, int len)
{
uint16_t crc = 0xFFFF;            // Init
for (int i = 0; i < len; i++) {
        crc ^= data[i];               // 这里隐含了 RefIn=true(逐字节处理时等价)
for (int b = 0; b < 8; b++) {
if (crc & 0x0001)
                crc = (crc >> 1) ^ 0xA001;  // 0xA001 是 0x8005 的反转形式
else
                crc >>= 1;
        }
    }
return crc;                        // XorOut=0x0000,无需再异或
}

注意,0xA001 不是随便写的。它是多项式 0x8005 按位反转后的结果,这一项就对应 RefIn/RefOut 都为 true 的约定。如果对方设备 RefOut 为 false,你就应该用 0x8005 直接计算,结果会完全不同。对接任何 CRC 设备,先把这六个参数和 白皮书 逐一对齐,不要凭“都是 CRC-16”就假设一致。

字节序:低字节在前

算出的 16 位 CRC 在帧尾占两个字节,先放高字节还是低字节?Modbus 规定低字节在前(little-endian on the wire)。

// 发送:低字节在前
frame[tail]   = crc & 0xFF;        // 低字节
frame[tail+1] = (crc >> 8) & 0xFF; // 高字节

// 接收端校验:取出后按同样顺序重组再算
uint16_t rx_crc = frame[tail] | (frame[tail+1] << 8);
uint16_t calc   = crc16_modbus(frame, tail);
if (rx_crc != calc) { /* 帧错误,丢弃 */ }

字节序搞反是最常见的对接故障,现象是“本地自算自校验 OK,和对方设备一通信就全错”。先核对协议文档里 CRC 的传输顺序,再写收发代码。

用硬件 CRC 外设加速

软件逐位计算在大批量数据下很占 CPU。STM32 等 MCU 内置了 CRC 外设,一条指令可以算完一个字,并且是 CRC-32(以太网多项式)。

// 使能并配置硬件 CRC(以 STM32 HAL 为例)
__HAL_RCC_CRC_CLK_ENABLE();
hcrc.Instance = CRC;
HAL_CRC_Init(&hcrc);                 // 默认 CRC-32 0x04C11DB7

// 按 32 位字计算,注意数据需 4 字节对齐
uint32_t calc = HAL_CRC_Calculate(&hcrc, (uint32_t *)buf, len_words);

硬件 CRC 大多固定为某一种参数(常见是 CRC-32),和 Modbus 的 CRC-16 不是一回事,不能直接替代 Modbus 校验。它适合固件完整性检查、Flash 数据校验这类场景;对接标准 Modbus,仍然使用上面软件实现的 CRC-16/MODBUS。别为了“用硬件”硬套一个不匹配的多项式。

校验在通信帧里的位置

校验边界要划在“整帧有效载荷”上,不能只覆盖数据段而漏掉地址和功能码。否则从机地址被干扰成别的设备,校验却仍然通过,就可能造成错误响应。CRC 计算范围必须与协议定义完全一致,包括帧头里的每个字段。




上一篇:Mustang Panda新型Rootkit深度剖析:Ftool内核驱动实现进程隐藏
下一篇:AscendNPU IR 开源解析:昇腾 950 SIMT/SIMD 编译优化与 Triton 生态
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-18 09:03 , Processed in 1.138004 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表