1. 西门子1200 PLC自由口通讯CRC校验程序实战解析
最近在做一个工业自动化项目时遇到了一个棘手的问题:客户现场的西门子S7-1200 PLC需要与第三方设备通过Modbus RTU协议通讯,但这款PLC居然没有内置的Modbus RTU指令库,更糟的是连CRC校验指令都没有。经过一番折腾,我最终用自由口通讯方式实现了完整的Modbus RTU协议,其中最关键的就是这个CRC校验程序。今天就把这个经过实战检验的解决方案分享给大家。
2. CRC校验在Modbus RTU协议中的关键作用
2.1 为什么Modbus RTU必须使用CRC校验
在工业现场,电气干扰、线路老化等问题都可能导致数据传输错误。Modbus RTU协议规定使用CRC-16校验码作为报文末尾的校验字段,这是确保数据完整性的重要手段。CRC校验能够检测出单比特错误、双比特错误、奇数个错误以及突发错误,误检率极低,特别适合工业环境。
2.2 CRC校验的基本原理
CRC(Cyclic Redundancy Check)循环冗余校验的核心思想是将待发送的数据视为一个二进制多项式,用这个多项式除以一个特定的生成多项式(对于Modbus是x¹⁶ + x¹⁵ + x² + 1,对应16#A001),得到的余数就是CRC校验码。接收方用同样的算法计算CRC值并与接收到的校验码比对,不一致则说明数据有误。
3. 西门子1200 PLC自由口通讯基础配置
3.1 硬件接线与参数设置
在开始编写CRC程序前,必须先正确配置自由口通讯:
- 使用RS485接口(CB1241通讯板或CM1241通讯模块)
- 接线注意:A接A,B接B,一定要做好终端电阻匹配
- 波特率设置:常见9600/19200/38400,必须与从站设备一致
- 数据位:8位
- 停止位:1位或2位(Modbus RTU通常为1位)
- 校验位:无(Modbus RTU协议本身已包含CRC校验)
重要提示:自由口通讯参数一旦设置错误,可能导致通讯完全失败,且难以排查问题。
3.2 自由口通讯程序框架
在OB1主循环中,自由口通讯的基本框架应包括:
- 通讯端口初始化(通常放在第一次扫描时执行)
- 发送数据缓冲区准备
- CRC校验计算
- 数据发送(使用T_SEND指令)
- 接收超时处理
- 接收数据解析
4. CRC校验程序完整实现与解析
4.1 变量定义与初始化
首先在DB块中定义必要的变量:
pascal复制VAR
// 待发送的数据缓冲区
Send_Buffer : ARRAY[0..255] OF BYTE;
Send_Length : INT;
// CRC计算相关变量
CRC_Value : WORD := 16#FFFF; // CRC初始值
CRC_Hi : BYTE; // CRC高字节
CRC_Lo : BYTE; // CRC低字节
// 临时变量
Index : INT;
Bit_Count : INT;
Temp_Byte : BYTE;
END_VAR
4.2 CRC计算核心算法实现
以下是经过优化的CRC计算函数块(FC):
pascal复制FUNCTION "Calculate_CRC" : VOID
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
pData : POINTER TO BYTE; // 数据缓冲区指针
DataLen : INT; // 数据长度
END_VAR
VAR_IN_OUT
CRC : WORD; // 输入输出的CRC值
END_VAR
VAR_TEMP
i : INT;
j : INT;
Temp : WORD;
END_VAR
BEGIN
// 主计算循环
FOR i := 0 TO DataLen-1 DO
CRC := CRC XOR WORD_TO_INT(INT_TO_WORD(MEM_BYTE(pData + i)));
// 位处理循环
FOR j := 0 TO 7 DO
IF (CRC AND 16#0001) <> 0 THEN
CRC := SHR(CRC, 1);
CRC := CRC XOR 16#A001;
ELSE
CRC := SHR(CRC, 1);
END_IF;
END_FOR;
END_FOR;
END_FUNCTION
4.3 代码执行流程详解
-
初始化阶段:
- CRC初始值设为16#FFFF(Modbus RTU标准要求)
- 准备待校验数据缓冲区
-
主计算循环:
- 遍历数据缓冲区中的每个字节
- 当前字节与CRC值进行异或操作
- 对异或结果进行8次位处理
-
位处理细节:
- 检查最低位是否为1
- 如果为1,右移1位后与16#A001异或
- 如果为0,仅执行右移操作
-
结果处理:
- 最终CRC值的高低字节需要交换位置(Modbus RTU要求)
- 将CRC值附加到报文末尾
5. 实际应用中的关键问题与解决方案
5.1 字节序问题
Modbus RTU协议要求CRC校验码按照"低字节在前,高字节在后"的顺序排列。在程序中需要特别注意:
pascal复制// 交换CRC高低字节并附加到发送缓冲区
Send_Buffer[Send_Length] := BYTE_TO_INT(INT_TO_BYTE(CRC AND 16#00FF));
Send_Buffer[Send_Length+1] := BYTE_TO_INT(INT_TO_BYTE((CRC AND 16#FF00) SHR 8));
Send_Length := Send_Length + 2;
5.2 性能优化技巧
-
循环优化:
- 将内层位处理循环展开为8次独立操作,可提高执行速度
- 使用S7-1200的移位指令(SHR)代替除法运算
-
查表法实现:
- 预先计算256个字节的CRC值表
- 通过查表代替实时计算,大幅提升速度
pascal复制// 查表法CRC计算(部分代码)
VAR CONSTANT
CRC_Table : ARRAY[0..255] OF WORD := (
16#0000, 16#C0C1, 16#C181, 16#0140, ..., 16#8201, 16#42C0
);
END_VAR
// 查表计算
CRC := CRC_Table[(CRC XOR DataByte) AND 16#00FF] XOR (CRC SHR 8);
5.3 常见错误排查
-
CRC校验不通过的可能原因:
- 数据缓冲区包含CRC字段本身(计算CRC时应排除最后2个字节)
- 字节序处理错误(高低字节顺序颠倒)
- 初始值不是16#FFFF
- 多项式使用错误(必须是16#A001)
-
调试技巧:
- 使用已知数据测试CRC计算结果(如测试字符串"123456789"的CRC应为16#4B37)
- 在线监视CRC计算过程中的中间值
- 对比标准Modbus主站的CRC计算结果
6. 完整Modbus RTU通讯实现方案
6.1 请求报文组装流程
- 填充从站地址(1字节)
- 填充功能码(1字节)
- 填充数据部分(长度可变)
- 计算CRC校验码(不包括CRC字段本身)
- 附加CRC校验码(2字节,低字节在前)
6.2 响应报文处理流程
- 接收完整报文(至少4字节:地址+功能码+CRC)
- 验证CRC校验码(计算接收数据的CRC并与报文中的CRC比对)
- 检查从站地址是否匹配
- 解析功能码和数据部分
6.3 超时与重试机制
pascal复制// 发送超时处理
IF "Send_Done" = FALSE AND "Send_Timeout_Timer".Q THEN
"Send_Retry_Counter" := "Send_Retry_Counter" + 1;
IF "Send_Retry_Counter" < 3 THEN
// 重试发送
"T_SEND_DB"(REQ := TRUE);
"Send_Timeout_Timer"(IN := TRUE, PT := T#1S);
ELSE
// 重试次数超限,报错
"Comm_Error" := TRUE;
END_IF;
END_IF;
7. 实际项目中的经验总结
在多个工业现场实施这个方案后,我总结出以下几点关键经验:
-
抗干扰措施:
- RS485线路必须使用双绞屏蔽线
- 终端电阻阻值应与电缆特性阻抗匹配(通常120Ω)
- 避免与动力电缆平行走线
-
性能考量:
- 对于频繁通讯的场景,建议使用查表法CRC计算
- 合理设置通讯超时时间(通常1-3秒)
- 考虑使用背景数据块减少扫描周期影响
-
扩展性设计:
- 将CRC计算封装成可重用的函数块
- 支持多种功能码(03读保持寄存器,06写单个寄存器等)
- 设计通用的异常处理机制
这个CRC校验程序虽然看起来简单,但在没有现成指令库的情况下,它成为了实现Modbus RTU通讯的关键。经过多个项目的实际验证,这个方案稳定可靠,希望能帮助到遇到类似问题的同行。