目录
前情提要
大家好,我是工控从业时长一坤年的邦邦(鞠躬
//---------------------------------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------//
本需要通过RS485接口,实现 Modbus RTU 通信,选模块时相中了这两款(名称中都带有485字样),由于项目还是招标阶段匆忙选择了较经济的BA这一款。

但是到货后发现BA款只能实现自由口通讯,无法直接使用博图中自带的Modbus RTU相关通讯指令,而HF款是可以可,BA需要自己写 。。。 😶-_- ......
(以后记得用HF的!!!!!


(上图为BA和HA的产品简介,确实BA不支持Modbus)


(上图为博图自带 Modbus RTU相关通讯指令 与 自由口指令)
搜索的资料基本都是对于BA模块的介绍及 Send_P2P / Receive_P2P (自由口发送和接收)两条指令的介绍,对于小白的我来说根本无从下手。。。
后找到一篇关于自由口实现Modbus RTU通讯的文章
⭐传送门:西门子RS485自由口通信Modbus RTU 通信协议_自由口通讯协议-优快云博客
本文在原帖基础上增加了从站通讯功能,并且附加CRC16校验(原帖没有,但其实搜一下有很多案例)。
由于本人对涉及通讯方面的知识了解较少,能力有限,所以很多通讯错误的报警判断阉割掉了。。
/////////以能用为目标//以能用为目标//以能用为目标(重要事情说三遍)
//---------------------------------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------//
程序详情
1.主站
FB块的变量定义

主站程序
IF #触发 THEN
// 触发后先将读取完成复位
#读写完成 := 0;
#读写错误 := 0;
#扫描次数 := 0;
#校验信息 := 16#6001;
END_IF;
#扫描次数 := #扫描次数 + 1;
//读写超时
IF #扫描次数 > 10 THEN
#读写完成 := 0;
#读写错误 := 1;
#校验信息 := 16#6002;
RETURN;
END_IF;
REGION _name_
// Statement section REGION
END_REGION
CASE #功能码 OF
1: // Statement section case 1
;
2: // Statement section case 2
;
3: // Statement section case 3
//拼接报文
#临时报文[0] := UINT_TO_BYTE(#从站号);//从站地址
#临时报文[1] := UINT_TO_BYTE(16#03);//功能码
#临时报文[2] := UINT_TO_BYTE(#起始地址 / 256);//起始寄存器高位
#临时报文[3] := UINT_TO_BYTE(#起始地址 MOD 256);//起始寄存器低位
#临时报文[4] := UINT_TO_BYTE(#读写长度 / 256);//寄存器数量高位
#临时报文[5] := UINT_TO_BYTE(#读写长度 MOD 256);//寄存器数量低位
"CRC16校验"(校验字节数 := 6,
校验数据 := #临时报文);
//截取报文
#截取结果 := MOVE_BLK_VARIANT(SRC := #临时报文,
COUNT := 8,
SRC_INDEX := 0,
DEST_INDEX := 0,
DEST => #读取发送报文);
#Send_P2P_Instance(REQ := #触发,
"PORT" := #硬件标识符,
BUFFER := #读取发送报文,
LENGTH := 8,
DONE => #发送完成,
ERROR => #发送错误,
STATUS => #发送信息);
IF #读写长度 <= 0 AND #扫描次数 > 1 THEN
// 如果写入长度为0报错
#校验信息 := 16#6030;
#读写错误 := 1;
RETURN;
END_IF;
IF #发送错误 AND #扫描次数 <= 1 THEN
// Statement section IF
#发送错误_1 := #发送错误;
END_IF;
IF #发送错误_1 AND #扫描次数 > 1 THEN
// 发送错误
#校验信息 := 16#6031;
#读写错误 := 1;
RETURN;
END_IF;
#截取结果 := MOVE_BLK_VARIANT(SRC := #零数组,
COUNT := 105,
SRC_INDEX := 0,
DEST_INDEX := 0,
DEST => #读取接收报文);
//接收报文
#Receive_P2P_Instance("PORT" := #硬件标识符,
BUFFER := #读取接收报文,
NDR => #接收完成,
ERROR => #接收错误,
STATUS => #接收信息,
LENGTH => #接收长度);
IF #接收错误 AND #扫描次数 > 1 THEN
// 接收错误
#校验信息 := 16#6032;
#读写错误 := 1;
RETURN;
END_IF;
#截取结果 := MOVE_BLK_VARIANT(SRC := #读取接收报文,
COUNT := #接收长度 - 2,
SRC_INDEX := 0,
DEST_INDEX := 0,
DEST => #校验报文);
IF #接收完成 THEN
//校验报文
IF #读取接收报文[0] <> 0 THEN
// Statement section IF
IF #读取接收报文[0] <> UINT_TO_BYTE(#从站号) THEN
// 接收报文从站号错误
#校验信息 := 16#6033;
#读写错误 := 1;
RETURN;
END_IF;
IF #读取接收报文[1] <> UINT_TO_BYTE(16#03) THEN
// 接收报文功能码错误
#校验信息 := 16#6034;
#读写错误 := 1;
RETURN;
END_IF;
IF #读取接收报文[2] <> UINT_TO_BYTE(#读写长度 * 2) THEN
// 接收报文字节计数错误
#校验信息 := 16#6035;
#读写错误 := 1;
RETURN;
END_IF;
//CRC校验发过来的数据
"CRC16校验"(校验字节数 := #接收长度 - 2,
校验数据 := #校验报文);
IF (#读取接收报文[#接收长度 - 2] <> #校验报文[#接收长度 - 2]) OR (#读取接收报文[#接收长度 - 1] <> #校验报文[#接收长度 - 1]) THEN
// 接收报文字节校验码错误
#校验信息 := 16#6036;
#读写错误 := 1;
RETURN;
END_IF;
#校验信息 := 16#6000;
#读写完成 := 1;
#读写错误 := 0;
//数据解析
#循环计数 := 0;
FOR #循环计数 := 0 TO #读写长度 - 1 BY 1 DO
//"test".cgc := #读写长度;
// 将高位字节和低位字节
#读写数据[#循环计数].%B1 := #读取接收报文[#循环计数 * 2 + 3];
#读写数据[#循环计数].%B0 := #读取接收报文[#循环计数 * 2 + 1 + 3];
END_FOR;
FOR #循环计数 := #读写长度 TO 99 BY 1 DO
// 将其余数据未读取数据清零
#读写数据[#循环计数] := 16#0000;
END_FOR;
ELSE
#校验信息 := 16#6037;
#读写错误 := 1;
RETURN;
END_IF;
END_IF;
4: // Statement section case 4
;
5: // Statement section case 5
;
6: // Statement section case 6
;
15: // Statement section case 15
;
16: // Statement section case 16
//拼接报文
#临时报文[0] := UINT_TO_BYTE(#从站号);//从站地址
#临时报文[1] := UINT_TO_BYTE(16#10);//功能码
#临时报文[2] := UINT_TO_BYTE(#起始地址 / 256);//起始寄存器高位
#临时报文[3] := UINT_TO_BYTE(#起始地址 MOD 256);//起始寄存器低位
#临时报文[4] := UINT_TO_BYTE(#读写长度 / 256);//寄存器数量高位
#临时报文[5] := UINT_TO_BYTE(#读写长度 MOD 256);//寄存器数量低位
#临时报文[6] := UINT_TO_BYTE(#读写长度 * 2);//寄存器数量低位
//数据解析
#循环计数 := 0;
FOR #循环计数 := 0 TO #读写长度 - 1 BY 1 DO
// 将写入的Word转换为两个BYTE
#临时报文[#循环计数 * 2 + 7] := #读写数据[#循环计数].%B1;
#临时报文[#循环计数 * 2 + 8] := #读写数据[#循环计数].%B0;
END_FOR;
"CRC16校验"(校验字节数 := 7 + #读写长度 * 2,
校验数据 := #临时报文);
//截取报文
#截取结果 := MOVE_BLK_VARIANT(SRC := #临时报文,
COUNT := #读写长度 * 2 + 9,
SRC_INDEX := 0,
DEST_INDEX := 0,
DEST => #写入发送报文);
#Send_P2P_Instance(REQ := #触发,
"PORT" := #硬件标识符,
BUFFER := #写入发送报文,
LENGTH := #读写长度 * 2 + 9,
DONE => #发送完成,
ERROR => #发送错误,
STATUS => #发送信息);
IF #读写长度 <= 0 AND #扫描次数 > 1 THEN
// 如果写入长度为0报错
#校验信息 := 16#6160;
#读写错误 := 1;
RETURN;
END_IF;
IF #发送错误 AND #扫描次数 <= 1 THEN
// Statement section IF
#发送错误_1 := #发送错误;
END_IF;
IF #发送错误_1 AND #扫描次数 > 1 THEN
// 发送错误
#校验信息 := 16#6161;
#读写错误 := 1;
RETURN;
END_IF;
//接收报文
#Receive_P2P_Instance("PORT" := #硬件标识符,
BUFFER := #写入接收报文,
NDR => #接收完成,
ERROR => #接收错误,
STATUS => #接收信息,
LENGTH => #接收长度);
IF #接收错误 AND #扫描次数 > 1 THEN
// 接收错误
#校验信息 := 16#6162;
#读写错误 := 1;
RETURN;
END_IF;
#截取结果 := MOVE_BLK_VARIANT(SRC := #写入接收报文,
COUNT := #接收长度 - 2,
SRC_INDEX := 0,
DEST_INDEX := 0,
DEST => #校验报文);
IF #接收完成 THEN
// Statement section IF
IF #写入接收报文[0] <> 0 THEN
// 校验报文
IF #写入接收报文[0] <> UINT_TO_BYTE(#从站号) THEN
// 接收报文从站号错误
#校验信息 := 16#6163;
#读写错误 := 1;
RETURN;
END_IF;
IF #写入接收报文[1] <> UINT_TO_BYTE(16#10) THEN
// 接收报文功能码错误
#校验信息 := 16#6164;
#读写错误 := 1;
RETURN;
END_IF;
IF (#写入接收报文[2] <> UINT_TO_BYTE(#起始地址 / 256)) OR (#写入接收报文[3] <> UINT_TO_BYTE(#起始地址 MOD 256)) THEN
// 接收报文起始地址错误
#校验信息 := 16#6165;
#读写错误 := 1;
RETURN;
END_IF;
IF (#写入接收报文[4] <> UINT_TO_BYTE(#读写长度 / 256)) OR (#写入接收报文[5] <> UINT_TO_BYTE(#读写长度 MOD 256)) THEN
// 接收报文数量错误
#校验信息 := 16#6166;
#读写错误 := 1;
RETURN;
END_IF;
#截取结果 := MOVE_BLK_VARIANT(SRC := #写入接收报文,
COUNT := #接收长度 - 2,
SRC_INDEX := 0,
DEST_INDEX := 0,
DEST => #校验报文);
//CRC校验发过来的数据
"CRC16校验"(校验字节数 := #接收长度 - 2,
校验数据 := #校验报文);
IF (#写入接收报文[#接收长度 - 2] <> #校验报文[#接收长度 - 2]) OR (#写入接收报文[#接收长度 - 1] <> #校验报文[#接收长度 - 1]) THEN
// 接收报文字节校验码错误
#校验信息 := 16#6167;
#读写错误 := 1;
RETURN;
END_IF;
#校验信息 := 16#6000;
#读写完成 := 1;
#读写错误 := 0;
ELSE // Statement section ELSE
#校验信息 := 16#6168;
#读写错误 := 1;
RETURN;
END_IF;
END_IF;
END_CASE;
2.从站
FB块的变量定义

从站程序
#Receive_P2P_Instance("PORT" := #硬件标识符,
BUFFER := #接收报文,
NDR => #接收完成,
ERROR => #接收错误,
STATUS => #接收信息,
LENGTH => #接收长度);
IF #接收完成 THEN
//开始解析报文
//一.站地码
//站地址 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
IF #接收报文[0] = #从站号 AND #从站号 <> 0 THEN
//检测是否超长 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//二.计算读取数据长度
#解析功能码 := #接收报文[1];//功能码
#解析起始地址高位 := #接收报文[2];//起始寄存器高位
#解析起始地址低位 := #接收报文[3];//起始寄存器低位
#解析读写长度高位 := #接收报文[4];//寄存器数量高位
#解析读写长度低位 := #接收报文[5];//寄存器数量低位
//计算 起始地址&数量
#解析起始地址 := BYTE_TO_UINT(#解析起始地址高位) * 256 + BYTE_TO_UINT(#解析起始地址低位);
#解析读写长度 := BYTE_TO_UINT(#解析读写长度高位) * 256 + BYTE_TO_UINT(#解析读写长度低位);
//判断是否超长
IF #解析起始地址 + #解析读写长度 > 51 THEN
#校验信息 := 16#6110;
RETURN;//退出块
END_IF;
//校验码 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//三.校验码 确认消息完整
#截取结果 := MOVE_BLK_VARIANT(SRC := #接收报文,
COUNT := #接收长度 - 2,
SRC_INDEX := 0,
DEST_INDEX := 0,
DEST => #校验报文);
"CRC16校验"(校验字节数 := #接收长度 - 2,
校验数据 := #校验报文);
IF (#接收报文[#接收长度 - 2] <> #校验报文[#接收长度 - 2]) OR (#接收报文[#接收长度 - 1] <> #校验报文[#接收长度 - 1]) THEN
// 接收报文字节校验码错误
#校验信息 := 16#6100;//xxxxxxxxxxxxxxxxxxxxxxxxxxxx
#读写错误 := 1;
RETURN;//退出块
END_IF;
#接收报文[0] := 0; //以防万一 清零站地址
//四.根据功能码 响应指令
CASE #解析功能码 OF
1: // Statement section case 1
;
2: // Statement section case 2
;
3: // Statement section case 3 //读多个寄存器
//主站发送读取命令 返回 INOUT 中"发送数据"
//
//生成临时报文
#临时报文[0] := UINT_TO_BYTE(#从站号);//从站地址
#临时报文[1] := UINT_TO_BYTE(16#03);//功能码
#临时报文[2] := UINT_TO_BYTE(#解析读写长度 * 2);//寄存器数量低位
FOR #循环计数 := 0 TO #解析读写长度 - 1 BY 1 DO
// 将写入的Word转换为两个BYTE
#临时报文[#循环计数 * 2 + 3] := #读数据[#解析起始地址 + #循环计数].%B1;
#临时报文[#循环计数 * 2 + 4] := #读数据[#解析起始地址 + #循环计数].%B0;
END_FOR;
"CRC16校验"(校验字节数 := 3 + #解析读写长度 * 2,
校验数据 := #临时报文); //整合完毕
//返回数据给主站
#发送进行3 := TRUE;
//xxxxxxxxxxxxxxxxxxxxxxxxxxxx//xxxxxxxxxxxxxxxxxxxxxxxxxxxx//xxxxxxxxxxxxxxxxxxxxxxxxxxxx//xxxxxxxxxxxxxxxxxxxxxxxxxxxx//xxxxxxxxxxxxxxxxxxxxxxxxxxxx
6: // Statement section case 6 //写单个寄存器
//待完善
;
//xxxxxxxxxxxxxxxxxxxxxxxxxxxx//xxxxxxxxxxxxxxxxxxxxxxxxxxxx//xxxxxxxxxxxxxxxxxxxxxxxxxxxx//xxxxxxxxxxxxxxxxxxxxxxxxxxxx//xxxxxxxxxxxxxxxxxxxxxxxxxxxx
16: // Statement section case 16 //写多个寄存器
//主站发送写命令 将数据写入 INOUT 中"接收数据"
//
// #解析字节数 := #接收报文[6]; //接收主站发来的字节数//这条好像没有用
//将收到数据 写入
FOR #循环计数 := 0 TO #解析读写长度 - 1 BY 1 DO
// 将写入的byte转换为两个word
#写数据[#解析起始地址 + #循环计数].%B1 := #接收报文[7 + #循环计数 * 2];
#写数据[#解析起始地址 + #循环计数].%B0 := #接收报文[8 + #循环计数 * 2];
END_FOR;
//返回数据给主站
#临时报文[0] := UINT_TO_BYTE(#从站号);//从站地址
#临时报文[1] := UINT_TO_BYTE(16#10);//功能码
#临时报文[2] := UINT_TO_BYTE(#解析起始地址高位);//起始寄存器高位
#临时报文[3] := UINT_TO_BYTE(#解析起始地址低位);//起始寄存器低位
#临时报文[4] := UINT_TO_BYTE(#解析读写长度高位);//寄存器数量高位
#临时报文[5] := UINT_TO_BYTE(#解析读写长度低位);//寄存器数量低位
"CRC16校验"(校验字节数 := 6,
校验数据 := #临时报文); //整合完毕
#发送进行16 := TRUE;
END_CASE;
END_IF;
END_IF;
////防止异常情况 复位
IF #发送进行3 OR #发送进行16 THEN
#t1(IN := TRUE,
PT := t#125ms);
END_IF;
IF #t1.Q THEN
#t1(IN := FALSE,
PT := t#125ms);
#发送进行3 := FALSE;
#发送进行16 := FALSE;
//反馈校验信息
#校验信息 := 6101;
END_IF;
//
IF #发送完成_1 THEN
#发送进行3 := FALSE;
#发送进行16 := FALSE;
#发送完成 := true;
#发送完成_1 := FALSE;
#t1(IN := FALSE,
PT := t#125ms);
ELSE
#发送完成 := FALSE;
END_IF;
#R1(CLK := #发送进行3);
#R2(CLK := #发送进行16);
//持续执行发送指令 上升延触发
IF TRUE THEN
#Send_P2P_Instance(REQ := #R1.Q AND NOT #发送完成_1,
"PORT" := #硬件标识符,
BUFFER := #临时报文,
LENGTH := 5 + #解析读写长度 * 2,
DONE => #发送完成_1,
ERROR => #发送错误,
STATUS => #发送信息);
#Send_P2P_Instance(REQ := #R2.Q AND NOT #发送完成_1,
"PORT" := #硬件标识符,
BUFFER := #临时报文,
LENGTH := 8,
DONE => #发送完成_1,
ERROR => #发送错误,
STATUS => #发送信息);
END_IF;
3.CRC16校验
FC块的变量定义

CRC16校验程序
#CRC := 16#FFFF;
FOR #字节循环 := 1 TO #校验字节数 DO
#CRC := #CRC XOR #校验数据[#字节循环 - 1];
FOR #位循环 := 1 TO 8 DO
IF ((#CRC & 2#1) <> 1) THEN
#CRC := SHR(IN := #CRC, N := 1);
ELSE
#CRC := SHR(IN := #CRC, N := 1) XOR 16#A001;
END_IF;
END_FOR;
END_FOR;
#校验数据[#校验字节数] := #CRC & 16#00FF;
#CRC := SWAP(#CRC);
#校验数据[#校验字节数 + 1] := #CRC & 16#00FF;
(PS:“CRC16校验” 粘贴后会有黄色下划曲线,是运算前后数据类型不同导致精度丢失,不影响使用无视即可
4.使用截图
主站



这个轮询写法是我习惯的一个写法,给通讯程序了6秒冗余,太短有时候会通讯不上,主打一个能用就行🙃
从站

我的项目有三个BA模块,为了测试硬件所以放了三个块,程序段3是让读写的数据方便观察。。
注意事项
(其实和参考那篇差不多,已经挺全面了,推荐看一下原文,在这就简写了
⭐传送门:西门子RS485自由口通信Modbus RTU 通信协议_自由口通讯协议-优快云博客
1.调用Send_P2P / Receive_P2P 时需要使用多重实例

2.主站触发为沿触发

//---------------------------------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------//
好了,没了,准备下班✌️
发现挺多老哥遇到问题的,csdn很少看消息,有需要加v 139---5712--7441

6万+





