部分西门子的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 #功能码 OF1: // 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 IFIF #读取接收报文[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 IFIF #写入接收报文[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、使用的指令:
2、在调用这两个指令时我才用的是多重实例,同时该指令在FB块中多次使用也不需要再创建背景数据块了,否则会出错。
在FB块中我只有一个SEND和RECEIVE的背景数据块
3、Send的触发必须要使用上升沿触发否则会报错。
4、扫描次数定义在FB块的Static区,不能定义在Temp区,扫描次数的作用是保证第一次执行FB块时初始化部分的读写完成、读写错误和校验信息三个输出变量能够输出到块外,在第二次之后执行才进行三个变量的逻辑判断,扫描次数超过10次就跳出该块并读写完成为0,读写错误为1,由于FB块的调用是在OB30中,OB30可以设置中断扫描的时间,这里设置为100ms,所以就是在10*100ms=1s后,块没有收到信息就会跳出该块并报错,还有读写长度报错必须放在Send_P2P_Instance之后。
FB块调用,在调用时触发必须使用上升沿,
FB块的变量定义