长线 RS-485 上电机启停、变频器噪声容易造成 bit 翻转。**Modbus RTU 强制使用 CRC16**:对「地址+功能码+数据」整段计算 16 位冗余,附在帧尾;接收方重算不一致则丢弃,避免误动作。
### 算法要点
- 常用生成式为反射多项式 **0xA001**(对应理想多项式 0x8005 的实现形式)。
- 初值一般 `0xFFFF`,算完得到 2 字节,**低字节先上线**(little-endian)。
- 校验范围 **不含 CRC 自身**;也不含 Modbus ASCII 的冒号与 CRLF。
### 移位法 vs 查表法
| 方法 | 优点 | 缺点 | 适用 | |---|---|---|---| | 移位法 | 代码短、无大表 | 高频帧时 CPU 忙 | 51/资源极紧 | | 查表法 | 每字节 O(1),快 | 约 512 字节表 | 从站/上位机主流 |
上位机与网页工具内部多为查表法;MCU 可用同一测试向量对齐。
### 串口调试对照法
- 取一帧公认正确的报文(文档或抓包)。
- 对前 N−2 字节自算 CRC,比较最后两字节顺序。
- 若总差 1 字节:检查是否多算了地址域外的附加头。
- 在 Web Serial Pro 中 HEX 发送完整帧,看从站是否由「CRC 错静默」变为正常应答。
### 实现检查清单
- 字节顺序:`CRC_L` 然后 `CRC_H`。
- 空数据帧也要算地址+功能码。
- 异常应答同样带 CRC。
- 单元测试固定向量:如 `01 03 00 00 00 02` → `C4 0B`。
把 CRC 在上下位机用同一套测例钉死,再谈复杂寄存器表;这是 Modbus 串口联调里性价比最高的一步。
// 快速高效的 Modbus CRC16 查表法 JavaScript 引擎
const CRC16_TABLE_HI = [
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
// ... 完整 256 字节的 CRC 计算预存表(在 utils.ts 中已被完整实现)
];
function calculateCRC16(buffer) {
let crcHi = 0xFF;
let crcLo = 0xFF;
for (let i = 0; i < buffer.length; i++) {
const idx = crcLo ^ buffer[i];
crcLo = crcHi ^ CRC16_TABLE_HI[idx]; // 快速查表
crcHi = CRC16_TABLE_LO[idx];
}
return (crcHi << 8) | crcLo; // 组装 16 位循环校验字
}