部分西门子的RS485模块不能使用西门子自带的Modbus RTU通信指令,需要通过自己拼接、发送、接收和解析报文完成Modbus RTU通信。通过多次尝试完成该功能:

IF #触发 THEN
    // 触发后先将读取完成复位
    #读写完成 := 0;
    #读写错误 := 0;
    #扫描次数 := 0;
    #校验信息 := 16#6001;
END_IF;

#扫描次数 := #扫描次数+1;

//读写超时
IF #扫描次数>10 THEN
    #读写完成 := 0;
    #读写错误 := 1;
    #校验信息 := 16#6002;
    RETURN;
END_IF;

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;

利用西门子博图创建了一个FB块,采用西门子SCL语言编写,在编写过程中有几个问题需要注意:

1、使用的指令:

codesys 自由协议串口_寄存器


2、在调用这两个指令时我才用的是多重实例,同时该指令在FB块中多次使用也不需要再创建背景数据块了,否则会出错。

codesys 自由协议串口_网络_02


codesys 自由协议串口_java_03


在FB块中我只有一个SEND和RECEIVE的背景数据块

3、Send的触发必须要使用上升沿触发否则会报错。

codesys 自由协议串口_寄存器_04


4、扫描次数定义在FB块的Static区,不能定义在Temp区,扫描次数的作用是保证第一次执行FB块时初始化部分的读写完成、读写错误和校验信息三个输出变量能够输出到块外,在第二次之后执行才进行三个变量的逻辑判断,扫描次数超过10次就跳出该块并读写完成为0,读写错误为1,由于FB块的调用是在OB30中,OB30可以设置中断扫描的时间,这里设置为100ms,所以就是在10*100ms=1s后,块没有收到信息就会跳出该块并报错,还有读写长度报错必须放在Send_P2P_Instance之后。

codesys 自由协议串口_网络_05

FB块调用,在调用时触发必须使用上升沿,

codesys 自由协议串口_java_06


codesys 自由协议串口_寄存器_07

codesys 自由协议串口_开发语言_08


codesys 自由协议串口_开发语言_09

FB块的变量定义

codesys 自由协议串口_网络_10