PLC程序中的数据校验与CRC计算
在工业现场,PLC与变频器、触摸屏或上位机进行通讯时,信号干扰是不可避免的。电缆铺设在强电旁,电磁场会在传输线上产生噪声,导致原本发送的数据0x03变成了0x02。如果这是启动电机的指令,后果不堪设想。为了确保数据接收方收到的信息与发送方完全一致,必须在数据包末尾附加一串“指纹”,这就是数据校验。在众多校验方式中,CRC(循环冗余校验)因检错能力强、计算速度快,成为了Modbus等工业协议的标准配置。
理解CRC的核心逻辑
不要被复杂的数学术语吓倒。CRC的计算原理本质上就是“除法取余”,只不过是在二进制世界里进行的除法。
想象一下,我们把要发送的一串数据看作一个巨大的二进制数,然后除以一个固定的小整数(这个固定的数叫“生成多项式”)。计算过程中得到的“余数”,就是CRC校验码。
当接收方收到数据后,它用同样的算法把数据部分除以那个固定的小整数。如果算出来的余数和发送过来的CRC码不一样,就说明数据在传输中被修改了。
在Modbus RTU协议中,最常用的是CRC-16模型,其生成多项式为:
$$ P(x) = x^{16} + x^{15} + x^2 + 1 $$
对应的十六进制数通常记为 0xA001。
CRC计算的硬件流程
在编写代码之前,先理清手工计算的逻辑流程。虽然PLC内部有指令可以一步完成,但理解流程有助于排查错误。以下是计算单个字节串的CRC-16标准流程。
实战:手动计算CRC(验证工具)
在PLC里写程序前,先用Windows自带的计算器验证一下算法是否正确。假设我们要计算的数据帧(十六进制)为:01 03 00 00 00 01。
- 打开 Windows计算器,选择 “程序员”模式。
- 输入 初始值
FFFF。 - 取 第一个数据字节
01,将其与当前值进行 异或(XOR)运算。FFFFXOR0101(注意字节位置,通常是低8位运算) =FEFE。
- 判断 最低位是否为1。如果是,异或 多项式
A001;如果不是,直接 右移 一位。 - 重复 右移和异或操作,共进行8次循环(处理完一个字节的所有位)。
- 取 下一个数据字节
03,与当前寄存器值 异或,再次重复8次移位循环。 - 处理 完所有字节(
00 00 00 01)后,寄存器中得到的值通常是340E(注意:高低字节可能需要根据具体规则交换)。
编写PLC程序:三菱FX系列实现
不同品牌的PLC实现方式不同,有的内置CRC指令,有的需要用梯形图逻辑手搓。这里以三菱FX3U为例,展示“手搓”算法的过程。假设数据存储在 D0 开始的连续寄存器中,数据长度为 K6。
步骤1:初始化寄存器
首先需要一个存放CRC结果的寄存器,初始化为 FFFF。
写入 以下梯形图逻辑:
当M0接通时,传送 FFFF 到 D10(CRC寄存器),D10 存放高八位,D11 存放低八位。
步骤2:构建循环与移位逻辑
由于PLC指令集限制,我们通常使用“查表法”或“位操作法”。这里演示位操作法的核心思路。
- 建立 一个索引指针
Z0,初始值为0,指向D0。 - 建立 一个位计数器
D20,初始值为8。
步骤3:异或与移位循环
这是核心算法部分。
-
取 当前指针
Z0指向的数据D0Z0。 -
执行
WOR指令(逻辑异或):将D11(CRC低字节)与D0Z0进行异或,结果存回D11。 -
创建 一个针对
D11的循环,循环8次(对应8个Bit):- 检测
D11的最低位(位0)。 - 如果 为1,执行
WXOR指令:将D10和D11组成的32位数与A001进行异或。 - 执行
ROR/RCR指令:将D10和D11视为一个16位整体,带进位右移 一位。 - 如果 为0,仅 执行 带进位右移一位。
- 递减 位计数器
D20。
- 检测
-
当8位处理完后,递增 指针
Z0。 -
判断
Z0是否小于数据长度6。如果小于,跳转 回步骤3处理下一个字节。
步骤4:高低字节交换
Modbus协议要求先发低字节,再发高字节。而计算结果通常在寄存器中是高位在左。在发送前,交换 D10 和 D11 的内容。
编写PLC程序:西门子S7-1200/1500实现
西门子SCL(结构化控制语言)更适合处理这种算法,代码简洁易读。
打开 TIA Portal软件,新建一个FC块,语言选择SCL。
-
定义 变量接口:
pData:Variant类型,指向数据区。dwLength:DWord类型,数据长度。dwCRC:DWord类型,输出校验码。
-
编写 程序代码:
FUNCTION_BLOCK "FB_CalcCRC"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
pData : Variant;
dwLength : DWord;
END_VAR
VAR_OUTPUT
dwCRC : Word;
END_VAR
VAR_TEMP
iCount : DInt;
jCount : DInt;
byData : Byte;
wCRC : Word := 16#FFFF;
wPoly : Word := 16#A001;
END_VAR
BEGIN
// 将Variant转换为指针访问或直接使用AT覆盖(此处为简化逻辑描述)
// 实际应用中建议使用 "AT" 结构覆盖字节块或使用Serialize指令读取
FOR iCount := 0 TO (dwLength - 1) DO
// 获取当前字节 (此处示意,具体取决于数据源类型)
// 假设源是数组,实际需根据pData具体类型调整
byData := 0;
// 异或操作
wCRC := wCRC XOR byData;
// 8位循环移位
FOR jCount := 0 TO 7 DO
IF (wCRC AND 16#0001) <> 0 THEN
wCRC := SHR(IN:=wCRC, N:=1);
wCRC := wCRC XOR wPoly;
ELSE
wCRC := SHR(IN:=wCRC, N:=1);
END_IF;
END_FOR;
END_FOR;
dwCRC := wCRC;
END_FUNCTION_BLOCK
注意:西门子编程中,直接处理Variant需要谨慎。如果是Modbus库调用,通常直接使用 MB_CLIENT 指令自动处理CRC。如果必须手动计算,建议将数据先 MOVE 到一个 Array of Byte 中,再传入上述逻辑。
常见CRC参数速查表
不同的协议使用的多项式、初始值可能不同。核对 下表,确保你的计算参数正确。
| 协议/名称 | 宽度 | 多项式 | 初始值 | 结果异或 | 输入反转 | 输出反转 |
|---|---|---|---|---|---|---|
| Modbus RTU | 16 | 0xA001 | 0xFFFF | 0x0000 | True | True |
| CRC-16/CCITT | 16 | 0x1021 | 0x0000 | 0x0000 | False | False |
| CRC-16/XMODEM | 16 | 0x1021 | 0x0000 | 0x0000 | False | False |
| CRC-8 | 8 | 0x07 | 0x00 | 0x00 | False | False |
表注:反转指的是字节内的Bit顺序,例如低位在前还是高位在前。
调试技巧与常见错误
在调试通讯程序时,CRC计算错误是最常见的问题。检查 以下几点可以节省大量时间。
-
高低字节反了
- 现象:计算出的CRC值与标准值总是高低位颠倒。
- 解决:Modbus协议规定先发低字节,再发高字节。如果你的PLC是大端存储(如某些旧式PLC),发送前必须 交换 高低字节。
-
数据包含了CRC本身
- 现象:第一次计算正确,后续全错。
- 解决:确保 CRC计算的输入数据长度只包含“有效数据”,不包含末尾的两个字节CRC。
-
多项式搞混了
- 现象:计算结果完全对不上。
- 解决:确认对方设备使用的是
0xA001(Modbus) 还是0x1021(XMODEM) 或其他多项式。
-
使用在线计算器验证
- 当程序跑不通时,复制 你的十六进制数据字符串。
- 打开 浏览器,搜索 "CRC calculator online"。
- 粘贴 数据,选择 Model 为
Modbus。 - 对比 在线结果与PLC寄存器内的结果。如果在线计算器是对的,那就是你的代码逻辑有漏洞。
最终步骤:
完成程序编写后,下载 到PLC。强制 发送一组固定的数据(如 01 03 00 00 00 01)。监控 CRC计算寄存器的值。如果寄存器显示 340E(假设低字节在前),连接 通讯线,观察接收设备的响应状态码是否为正常(如无异常校验错误)。

暂无评论,快来抢沙发吧!