引言
在工业现场通信中,Modbus、Profinet、自定义串口协议等数据传输场景里,CRC(循环冗余校验) 是最常用的数据完整性校验方式,相当于给数据包加了一个“唯一指纹”——只要传输过程中有 1 位数据被干扰出错,接收方计算出的 CRC 值就会和发送方不一致,从而直接丢弃错误数据,避免设备误动作。 很多工程师在使用 PLC 做通信开发时,要么依赖厂商自带的 CRC 库(灵活性差、无法适配自定义协议),要么自己写 scattered 的零散逻辑(容易出 bug、复用性差)。今天给大家介绍一款轻量、兼容性强、可灵活配置的 IEC 61131-3 标准 CRC 计算功能块FB_CRCCalculator,无需额外依赖库文件,几乎所有品牌的 PLC(西门子、汇川、信捷、台达等)都能直接导入使用。
1
核心程序代码及逻辑详解
1.1
完整功能块代码
下面是完整的 PLC 程序代码(代码基于 CodeSys 平台的 ST 语言):
FUNCTION_BLOCK FB_CRCCalculator
VAR_INPUT
dataIn : BYTE; // 输入的单字节数据
dataValid : BOOL; // 数据有效标志(上升沿触发处理)
polynomial : WORD; // CRC多项式,可配置适配不同协议
reset : BOOL; // 复位信号,高电平有效
END_VAR
VAR_OUTPUT
crcValue : WORD; // 计算完成的CRC结果
dataReady : BOOL; // 计算完成标志,单周期脉冲输出
END_VAR
VAR
i : INT; // 移位循环计数
crcReg : WORD; // CRC运算寄存器
bitCount : INT; // 已接收数据位数计数
lastDataValid : BOOL; // 上一次dataValid的状态,用于上升沿检测
END_VAR
// 复位逻辑:最高优先级,复位时清空所有状态
IF reset THEN
crcReg := 16#FFFF; // CRC寄存器初始值,可根据协议修改
crcValue := 0;
dataReady := FALSE;
bitCount := 0;
RETURN;
END_IF;
// 数据处理:仅当dataValid出现上升沿(从无效变有效)时处理当前字节
IF dataValid AND NOT lastDataValid THEN
// 1. 当前字节与CRC寄存器异或
crcReg := crcReg XOR BYTE_TO_WORD(dataIn);
// 2. 核心CRC移位运算:对当前字节的8位逐位处理
FOR i := 1 TO 8 DO
// 判断CRC寄存器最低位是否为1
IF (crcReg AND 16#0001) <> 0 THEN
crcReg := SHR(crcReg, 1) XOR polynomial; // 最低位为1则右移后异或多项式
ELSE
crcReg := SHR(crcReg, 1); // 最低位为0仅右移
END_IF;
END_FOR;
// 3. 累计已处理的数据位数
bitCount := bitCount + 8;
// 4. 达到预设帧长后输出CRC结果(默认每8字节/64位输出一次)
IF bitCount >= 64 THEN // 8字节,可根据实际帧长修改阈值
crcValue := crcReg;
dataReady := TRUE; // 输出单周期脉冲,提示结果可用
bitCount := 0; // 计数器清零,准备下一帧计算
ELSE
dataReady := FALSE;
END_IF;
ELSE
dataReady := FALSE;
END_IF;
// 更新上一次dataValid状态,供下次扫描做上升沿判断
lastDataValid := dataValid;
1.2
逻辑逐段解读
-
变量设计逻辑
-
输入
dataValid采用上升沿触发机制,配合内部变量lastDataValid,避免同一个字节被重复计算(比如串口接收一个字节需要 10ms,dataValid 拉高 10ms,仅第一个扫描周期会处理该字节,后续即使 dataValid 仍为高也不会重复运算,从根源避免 CRC 错误)。 -
polynomial和 CRC 寄存器初始值可配置,无需修改核心逻辑就能适配 Modbus RTU、CRC16-IBM、CRC16-CCITT 等几乎所有常用 CRC 协议。 -
bitCount默认按 8 字节(64 位)为一帧输出结果,如果是变长帧或者单字节校验的场景,可以自行调整阈值逻辑。
-
-
核心运算逻辑
-
复位信号优先级最高,拉高复位时会直接清空寄存器、计数器和输出,适合通信故障时快速重启校验逻辑。
-
8 次移位循环是 CRC16 算法的核心:通过多项式除法逐位计算校验值,所有运算都是位操作,对 PLC 的资源消耗极低,哪怕 100 点以下的小型 PLC 也能流畅运行。
-
2
典型应用场景
2.1
工业串口通信校验(最常用场景)
在 Modbus RTU、自定义串口协议通信中,PLC 作为主站/从站接收数据时,可以在串口接收中断中调用该功能块:
-
每收到 1 个字节,将字节值赋给
dataIn,给dataValid拉高 1 个扫描周期(保证能检测到上升沿)。 -
接收满一帧数据(比如 Modbus 默认一帧最多 256 字节,可根据需求调整
bitCount阈值)后,dataReady会输出一个扫描周期的高电平脉冲,此时读取crcValue,和数据帧末尾的 CRC 校验位比对,即可判断数据是否传输错误。 -
示例:适配 Modbus RTU 的 CRC16 时,只需将
polynomial设为16#8005,复位后拉高,收满一帧后读出的crcValue和 Modbus 协议要求的校验值完全一致。
2.2
现场传感器数据完整性校验
很多模拟量传感器、数字仪表会通过 RS485/以太网传输采集数据,如果现场电磁干扰强,很容易出现数据跳变。可以在 PLC 采集端调用该功能块,每凑够 8 字节(或自定义帧长)就做一次 CRC 校验,错误数据直接丢弃,避免干扰数据进入控制逻辑导致设备误动作。
2.3
老旧设备改造与边缘网关预处理
老旧设备可能用的是专用 CRC 校验芯片,故障率高且无法适配新协议;用该功能块软实现 CRC 校验,无需额外硬件,还能灵活修改多项式适配新协议。如果是 PLC 做边缘网关的场景,可以在 PLC 端先做 CRC 校验,错误数据直接过滤,不用上传到上位机,大幅降低网络带宽压力。
3
拓展优化思考
这个功能块的框架非常灵活,大家可以根据自己的项目需求自行裁剪优化:
-
适配更多 CRC 协议如果需要计算 CRC8、CRC32,只需要修改内部变量的位宽即可:比如 CRC32 把
crcReg改成 DWORD 类型,polynomial改成 DWORD,移位循环次数改成 32 次,输出改成 DWORD 类型就能直接使用;如果适配左移优先的 CRC 协议,只需要把SHR(右移)改成SHL(左移)即可。 -
调整输出频率原程序默认每 8 字节输出一次结果,如果需要每收到 1 个字节就输出 CRC 结果(比如实时校验场景),可以直接删除
bitCount的判断逻辑,每次处理完字节后直接更新crcValue并拉高dataReady即可。 -
效率优化如果 PLC 内存资源充足,可以把逐位移位的 FOR 循环改成 CRC 查表法,运算速度能提升数倍,适合高速通信场景;如果是资源紧张的小型 PLC,原程序的逐位运算逻辑已经足够轻量,几乎不占用额外资源。
-
时序适配如果现场
dataValid是窄脉冲触发(比如中断输出脉冲宽度小于 1 个 PLC 扫描周期),可以把上升沿检测逻辑改成下降沿,或者把功能块放在中断任务中调用,避免漏触发。
总结
FB_CRCCalculator是一款典型的“小而美”的工业功能块,没有复杂的依赖,逻辑清晰易懂,既可以满足常规通信校验的需求,也能通过简单修改适配绝大多数自定义 CRC 协议。对于经常需要做通信开发的 PLC 工程师来说,把这个功能块放进自己的工具库里,能节省大量重复造轮子的时间,同时避免不同项目中 CRC 逻辑不兼容的问题。 使用时只需要注意三个核心点:上升沿触发dataValid、根据协议配置正确的多项式和初始值、根据帧长调整bitCount阈值,就能稳定运行,是工业现场数据校验的实用工具。
你来出题 我来答![]()
![]()
![]()
欢迎大家在评论区提一些标准功能块的需求,如果合适,我们会在以后的文章中与大家分享。
往期回顾
每周一个编程小例子 :Bit 到 Word 转换的两种实现方案对比
每周一个编程小例子:记录设备运行时间
每周一个编程小例子:基于长按检测的启动控制系统
每周一个编程小例子:探秘 PLC 数据记录功能块
每周一个编程小例子:心跳检测与 PLC 应用
每周一个编程小例子:斜坡发生器
每周一个编程小例子:旋转速度计算
每周一个编程小例子:计算 PLC 循环周期|例程需求征集!
每周一个编程小例子:如何优雅的分割字符串
每周一个编程小例子:带反馈的阀门控制程序
每周一个编程小例子:西门子 LGF 库中的脉冲继电器功能块
每周一个编程小例子:曲线加减速的控制应用
每周一个编程小例子:轻松去除字符串前导字符
每周一个编程小例子:根据阈值更新输入
每周一个编程小例子:官方模拟量输入处理模块
每周一个编程小例子:主从电机的时序控制
每周一个编程小例子:基于一阶滞后滤波的优化方案
每周一个编程小例子:16 进制到浮点数的神奇转换
每周一个编程小例子:PLC 长按控制程序
每周一个编程小例子:PLC 程序实现多位整数拆分
每周一个编程小例子:流量累积计算器
每周一个编程小例子:用于连锁的电机标准控制模块
每周一个编程小例子:PID 控制算法在 PLC 系统中的实现与应用
每周一个编程小例子:自定义种子生成随机数
每周一个编程小例子:可调时间的脉冲生成器
每周一个编程小例子:带滤波的标准模拟量输入功能块
每周一个编程小例子:使用 PLC 将字符串转换为日期格式
每周一个编程小例子:PLC 配方管理
每周一个编程小例子:比较两个 DB 块(STL)
每周一个编程小例子:如何使用 PLC 实现数组逆序
每周一个编程小例子:流量累计与批次控制
每周一个编程小例子:自定义循环执行控制程序
每周一个编程小例子:步进控制功能块
每周一个编程小例子:PLC 权重比例分配器
每周一个编程小例子:数字量输入组监控
每周一个编程小例子:实时数据分析
每周一个编程小例子:PLC 多轴同步控制的理解与应用
每周一个编程小例子:PLC 波形生成器 (正弦 方波 三角波)
每周一个编程小例子:模拟量信号报警
每周一个编程小例子:组合数概率的实现
每周一个编程小例子:多传感器信号融合与优化方案
每周一个编程小例子:监控模拟信号的健康状态
每周一个编程小例子:PLC 程序实现模拟信号的线性校准
每周一个编程小例子:智能报警过滤器:优化报警系统的 PLC 程序
每周一个编程小例子:使用 PLC 程序让灯光呼吸
每周一个编程小例子:自适应性滤波器:动态调整以平衡响应与稳定性
每周一个编程小例子:卡尔曼滤波器在自动化中的简单应用
每周一个编程小例子:多模式定时器
每周一个编程小例子:基于 PLC 的编码器速度计算器
每周一个编程小例子:电机顺序启动与停止程序
每周一个编程小例子:电梯控制程序
每周一个编程小例子:自动门控制系统
每周一个编程小例子:智能停车场计数系统
每周一个编程小例子:智能照明控制系统
每周一个编程小例子:变频器控制
每周一个编程小例子:比例阀控制
每周一个编程小例子:脉冲输出的灵活控制
每周一个编程小例子:计算移动平均值
每周一个编程小例子:速率限制器
每周一个编程小例子:线性插值法线性化传感器
每周一个编程小例子:数据转换的魔力
每周一个编程小例子:PLC 安全监控功能块
每周一个编程小例子:PLC 消抖功能块
每周一个编程小例子:「看门狗」功能块
每周一个编程小例子:实现工业数据轻量压缩
2026年10月