部分西门子的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、使用的指令:
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块的变量定义