西门子1500 CM PtP RS422/485 BA 模块 自由口 实现 Modbus RTU 通信

该文章已生成可运行项目,

目录

前情提要

程序解析

主站

从站

CRC16校验

使用截图

注意事项


前情提要

大家好,我是工控从业时长一坤年的邦邦(鞠躬

//---------------------------------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------//

本需要通过RS485接口,实现 Modbus RTU 通信,选模块时相中了这两款(名称中都带有485字样),由于项目还是招标阶段匆忙选择了较经济的BA这一款。

b5ce991d481247d596466a6f378c260c.png

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

(以后记得用HF的!!!!!

7ea161b496f8418bbc21416edf6b5f2b.pngb28c514a9cde4f8aa8b76cf539f744b2.png

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

05071c5748dc435688f514c75ecd3e30.png025c4dd7ac6a46f4b25a2e44461e0084.png

(上图为博图自带 Modbus RTU相关通讯指令 与 自由口指令)

搜索的资料基本都是对于BA模块的介绍及  Send_P2P  /  Receive_P2P  (自由口发送和接收)两条指令的介绍,对于小白的我来说根本无从下手。。。

后找到一篇关于自由口实现Modbus RTU通讯的文章

⭐传送门:西门子RS485自由口通信Modbus RTU 通信协议_自由口通讯协议-优快云博客

本文在原帖基础上增加了从站通讯功能,并且附加CRC16校验(原帖没有,但其实搜一下有很多案例)。

由于本人对涉及通讯方面的知识了解较少,能力有限,所以很多通讯错误的报警判断阉割掉了。。

/////////以能用为目标//以能用为目标//以能用为目标(重要事情说三遍)

//---------------------------------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------//

程序详情

1.主站

 FB块的变量定义

7ed66bfb322341598a06fa5c26bc71fe.png

主站程序



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块的变量定义

c0d435af8d0b4957a774a99e3b4310d4.png

从站程序

#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块的变量定义

f1dd6f2977c043c38c65fd64363966db.png

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.使用截图

主站

623778d0da744f8a8ff839131ebb4072.png511c314c079a461686627ad6d8793670.png416c1053890b43fb8d225a39df07b563.png

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

从站

ff623f6a54984bc190538fee30817cd1.png3628a69e2c9e4f86ae351832c2f0deca.png我的项目有三个BA模块,为了测试硬件所以放了三个块,程序段3是让读写的数据方便观察。。

注意事项

(其实和参考那篇差不多,已经挺全面了,推荐看一下原文,在这就简写了     

⭐传送门:西门子RS485自由口通信Modbus RTU 通信协议_自由口通讯协议-优快云博客

1.调用Send_P2P  /  Receive_P2P 时需要使用多重实例

7fdfa4e830264745a28f881e46e68175.png

2.主站触发为沿触发

5dfb99faecc94d22a45789de5ee9aca3.png

//---------------------------------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------//

好了,没了,准备下班✌️

发现挺多老哥遇到问题的,csdn很少看消息,有需要加v 139---5712--7441

本文章已经生成可运行项目
评论 12
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值