Modbus CRC16 校验:查表法与移位法及串口调试对照

长线 RS-485 上电机启停、变频器噪声容易造成 bit 翻转。**Modbus RTU 强制使用 CRC16**:对「地址+功能码+数据」整段计算 16 位冗余,附在帧尾;接收方重算不一致则丢弃,避免误动作。

### 算法要点

  • 常用生成式为反射多项式 **0xA001**(对应理想多项式 0x8005 的实现形式)。
  • 初值一般 `0xFFFF`,算完得到 2 字节,**低字节先上线**(little-endian)。
  • 校验范围 **不含 CRC 自身**;也不含 Modbus ASCII 的冒号与 CRLF。

### 移位法 vs 查表法

| 方法 | 优点 | 缺点 | 适用 | |---|---|---|---| | 移位法 | 代码短、无大表 | 高频帧时 CPU 忙 | 51/资源极紧 | | 查表法 | 每字节 O(1),快 | 约 512 字节表 | 从站/上位机主流 |

上位机与网页工具内部多为查表法;MCU 可用同一测试向量对齐。

### 串口调试对照法

  1. 取一帧公认正确的报文(文档或抓包)。
  2. 对前 N−2 字节自算 CRC,比较最后两字节顺序。
  3. 若总差 1 字节:检查是否多算了地址域外的附加头。
  4. 在 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 位循环校验字
}