1. 设备
- PLC型号:三菱 FX3U
- 使用仿真器验证程序。仿真器------凌一仿真器
- 仿真器官网:http://www.ly-plc.com/downloads
- 仿真器简单使用教程视频:【PLCSimulator】凌一FX3U仿真器安装及使用说明-Bilibili
- 通讯设备:电脑模拟串口(com0com)
- 模拟串口软件下载官网(SourceForge上,需要梯子):https://sourceforge.net/projects/com0com/
2. 参考资料
- 三菱官方通讯手册:FX系列微型可编程控制器 用户手册 [通信篇]
- Modbus 协议规范文档(CRC 计算示例在附录 80-86 页):https://image.modbus.cn/wp-content/uploads/2022/03/2022031708564080.pdf
- CRC16-Modbus 校验代码实例(C语言实现,查表法、计算法均有):【CRC笔记】CRC-16 MODBUS C语言实现_modbus crc16 c语言代码-CSDN博客
- 计算法PLC程序(西门子博途ST语言实现,以下代码的参考源于此视频,还可参考视频下的高赞评论代码):MODBUS RTU CRC16校验算法-Bilibili
3. 代码
-
FX3U工程项目使用 "结构化工程" 类型
此类型创建的项目,使用标签作变量,系统自动分配标签给寄存器,不需要纠结具体寄存器位置,可使用 IEC 标准的指令,使用起来有点欧系 PLC 的味道

-
使用计算法实现(占用内存少,但处理大量寄存器计算时速度较慢)
-
按照 RS 指令发送顺序优化(实现以下两种数据发送模式下的 CRC 校验计算)
- M8161 = 0 -- 设置为 16 位数据收发模式
- 一个 WORD 寄存器,先发低字节,再发高字节
- 实际发送字节数 = 寄存器数量 X 2
2. M8161 = 1 -- 设置为 8 位数据收发模式。
- 一个 WORD 寄存器,只发低字节,高字节不管。
- 实际发送数据字节数 = 传入的寄存器数量
- 使用 PLC 中自带的 CRC 指令则应使用这种数据模式,否则算出的校验码和传入的数据不匹配)

- M8161 = 0 -- 设置为 16 位数据收发模式
计算法见上一篇文章
上一篇文章讲解了计算法相关程序:https://blog.csdn.net/weixin_44112083/article/details/155649333
全局标签(全局变量)
计算法不需要全局标签
3.1 功能块 -- MB_CRC16_Calc -- CRC校验码计算程序(计算法)
此功能块每次计算 CRC 时调用,可重复调用同一个实例进行不同数据的计算
局部标签

| 类 | 标签名 | 数据类型 | 注释 |
|---|---|---|---|
| VAR_INPUT | reg_len | Word[Signed] | 寄存器数量(用户输入值) |
| VAR_INPUT | data | Word[Unsigned]/Bit STRING[16-bit] (0...127) | 要计算的数据(INT数组) |
| VAR_INPUT | rs_mode | Bit | RS指令设定的寄存器模式:0/1=16位/8位 |
| VAR_OUTPUT | output | Word[Unsigned]/Bit STRING[16-bit] | 计算结果 |
| VAR | reg_len_OK | Word[Signed] | 寄存器数量(校验OK值) |
| VAR | crc_tmp | Word[Unsigned]/Bit STRING[16-bit] | CRC输出(初始值0xFFFF) |
| VAR | reg_idx | Word[Signed] | 寄存器索引(循环变量1) |
| VAR | index_j | Word[Signed] | 循环变量2 |
| VAR | index_i | Word[Signed] | 循环变量3 |
- 功能块输入(定义同上一篇文章中的计算法功能块):
- 数据:
data,无符号整数数组,最大长度128 - 寄存器数量:
reg_len,无符号整数,范围 0-125 - 数据模式(前文说过的8/16位模式):
rs_mode,0/1 = 8位模式 / 16位模式
- 数据:
- 功能块输出
- 计算出的CRC校验码结果:
output,无符号整数
- 计算出的CRC校验码结果:
程序本体:ST 语言代码段
BASIC
(* Modbus CRC16 校验程序-计算法 *)
(* 2. 边界检查:最多128个寄存器(256字节,功能码&站号(2字节) + 寄存器长度(2字节) + 传输数据最多125字) *)
IF reg_len > 125 THEN
reg_len_OK := 125;
ELSIF reg_len <= 0 THEN
reg_len_OK := 0;
output := 16#FFFF;
RETURN;
ELSE
reg_len_OK := reg_len;
END_IF;
(* 设置输出初始值 *)
crc_tmp := 16#FFFF;
(* 3-1. 16位模式 M8161=0 *)
IF NOT rs_mode THEN
(* 外循环,对每个寄存器 *)
FOR reg_idx := 0 TO reg_len_OK - 1 DO
FOR index_i := 0 TO 1 DO (* 高低字节循环,先低字节后高字节 *)
IF index_i = 0 THEN
crc_tmp := crc_tmp XOR (data[reg_idx] AND 16#00FF);
END_IF;
IF index_i = 1 THEN
crc_tmp := crc_tmp XOR SHR(data[reg_idx], 8);
END_IF;
(* 内循环,对每一位 *)
FOR index_j := 1 TO 8 DO
IF (crc_tmp AND 16#0001) = 1 THEN
crc_tmp := SHR( crc_tmp , 1 );
crc_tmp := crc_tmp XOR 16#A001;
ELSE
crc_tmp := SHR( crc_tmp , 1 );
END_IF;
END_FOR;
END_FOR;
END_FOR;
END_IF;
(* 3-2. 8位模式 M8161=1,只取低Byte作计算 *)
IF rs_mode THEN
(* 外循环,对每个寄存器 *)
FOR reg_idx := 0 TO reg_len_OK - 1 DO
crc_tmp := crc_tmp XOR (data[reg_idx] AND 16#00FF);
(* 内循环,对每一位 *)
FOR index_j := 1 TO 8 DO
IF (crc_tmp AND 16#0001) = 1 THEN
crc_tmp := SHR( crc_tmp , 1 );
crc_tmp := crc_tmp XOR 16#A001;
ELSE
crc_tmp := SHR( crc_tmp , 1 );
END_IF;
END_FOR;
END_FOR;
END_IF;
(* 输出结果 *)
output := crc_tmp;
4. 功能块测试
声明 ST 代码段TEST_CRC,调用编写的功能块并验证输出是否OK
(在上一篇文章基础上,调用计算法功能块,并与查表法功能块比较数据是否一致)
代码段局部标签(局部变量)

| 类 | 标签名 | 数据类型 | 注释 |
|---|---|---|---|
| VAR | crc_result | Word[Unsigned]/Bit STRING[16-bit] | |
| VAR | crc_result2 | Word[Unsigned]/Bit STRING[16-bit] | |
| VAR | crc_result3 | Word[Unsigned]/Bit STRING[16-bit] | |
| VAR | crc_result4 | Word[Unsigned]/Bit STRING[16-bit] | |
| VAR | MB_CRC16_Calc_1 | MB_CRC16_Calc | |
| VAR | MB_CRC16_CheckTable_1 | MB_CRC16_CheckTable | |
| VAR | MB_CRC16_InitCheckTable_1 | MB_CRC16_InitCheckTable | |
| VAR | regs_to_check | Word[Signed] | |
| VAR | test_data_in | Word[Unsigned]/Bit STRING[16-bit] (0...127) | |
| VAR | crc_result5 | Word[Unsigned]/Bit STRING[16-bit] |
BASIC
(* 示例1:3个寄存器-16位数据模式(对应字节流:03 05 00 10 00 1E) *)
test_data_in[0] :=16#0305;
test_data_in[1] :=16#1000;
test_data_in[2] :=16#1E00;
regs_to_check := 3;
(* PLC运行第一个周期,初始化CRC表 *)
IF M8002 THEN
MB_CRC16_InitCheckTable_1();
END_IF;
(* 示例1-1:计算3个寄存器的CRC,16位模式,rs_mode=0 *)
(* 结果: crc_result = 16#4409 *)
MB_CRC16_CheckTable_1(reg_len:= regs_to_check ,
data:= test_data_in,
rs_mode:= 0,
output:= crc_result);
(* 示例1-2:计算3个寄存器的CRC,16位模式,rs_mode=0 *)
MB_CRC16_Calc_1(reg_len:= regs_to_check ,
data:= test_data_in,
rs_mode:= 0,
output:= crc_result5);
(* 示例2:6个寄存器-8位数据模式(对应字节流:03 05 00 10 00 1E) *)
test_data_in[0] :=16#0003;
test_data_in[1] :=16#0005;
test_data_in[2] :=16#0000;
test_data_in[3] :=16#0010;
test_data_in[4] :=16#0000;
test_data_in[5] :=16#001E;
regs_to_check := 6;
(* 示例2:使用内置函数计算3个寄存器的CRC,对应RS中的8位数据模式(M8161=1) *)
CRC( TRUE , test_data_in[0], 6 , crc_result2 );
(* 示例2-2:调用查表功能块,对应RS中的8位数据模式(M8161=1) *)
MB_CRC16_CheckTable_1(reg_len:= regs_to_check ,
data:= test_data_in,
rs_mode:= 1,
output:= crc_result3);
regs_to_check := 6;
(* 示例2-3:调用计算法功能块,8位数据模式(M8161=1) *)
MB_CRC16_Calc_1(reg_len:= regs_to_check ,
data:= test_data_in,
rs_mode:= 1,
output:= crc_result4);
在线验证网站:https://www.23bei.com/tool/59.html
模式1(rs_mode = FALSE,16位数据模式)验证

模式2(rs_mode = TRUE,8位数据模式)验证

5. 最后
此程序仅用于测试、熟悉三菱的编程界面
实际计算 Modbus 的 CRC16 校验码时,建议采用CRC()函数
此函数在 FX3U 以上型号 PLC 都有,不需要额外编程,保证效率