FUNCTION_BLOCK BS_ModbusCom VAR_EXTERNAL END_VAR VAR_INPUT SerialCom : byte; NetSend : array[0..64] of NetSendConfig; NetSendDelay : time; ComSend : array[0..64] of ComSendConfig; ComSendDelay : time; (*系统默认t#200ms*) ComRecvDelay : time; (*系统默认t#120ms*) ComAnalysisDelay: time; (*系统默认t#150ms*) DataReadLength : int; END_VAR VAR_OUTPUT DataStr : array[0..128] of byte; DataRead : array[0..128] of DataFormat; END_VAR VAR SendFB : NW_SerSend; RecvFB : NW_SerRecv; SendP : pointer; RecvP : pointer; Send_act : bool; Recv_act : bool; i,j,k : int; i0,i1,i2,i3 : int; CRC16Lo1 : Byte; CRC16Hi1 : Byte; CRC16Lo : Byte; CRC16Hi : Byte; CL,CH : Byte; SaveHi,SaveLo : Byte; Flag : int; ENO_Count0 : dword; ENO_Count1 : dword; timer1 : ton; timer1_run : bool; NetVarSend1 : NetVarSend; t1 : ton; t1_in : bool; t1_q : bool; t2 : ton; t2_in : bool; t2_q : bool; t3 : ton; t3_in : bool; t3_q : bool; Commandsend : int; SendStr : array[0..8] of byte; RecvStr : array[0..200] of byte; CalcStr : array[0..200] of byte; ErrStr : array[0..100] of int; Recv_len : int; Recv_lenp : int; Recv_DataLen : int; Recv_CRCLo : int; Recv_CRCHi : int; TestMode : bool; StopMem : int; ComTimeoutCount : int; ComTimeoutClear : bool; checkmark1 : bool; checkmark2 : bool; checkmark3 : bool; checkmark4 : bool; DATA1 : int; DATA2 : int; TempStr : array[0..128] of byte; TempReal : real; v2v : NW_MemCopy; P1 : Pointer; P2 : Pointer; END_VAR (* 时 间:20200915 版 本:1.3 作 者:姚立 名 称:通讯功能块 说 明: 支持Modbus协议的03/04命令的读取及解析 支持读取失败次数查询 支持读取失败数据清除或保留设置 支持Modbus协议的06命令的单个寄存器写入 进一步修正了通讯错位问题 input 输入说明 SerialCom : byte; NetSend : array[0..64] of NetSendConfig; NetSendDelay : time; ComSend : array[0..64] of ComSendConfig; ComSendDelay : time; ComRecvDelay : time; ComAnalysisDelay: time; DataReadLength : int; DataType : array[0..256] of int ; output 输出说明 DataStr : array[0..256] of byte; DataRead : array[0..256] of DataFormat; config 读写设置 ComSend[1].Enable :=1; 读取启用1 禁用为0,测试为2,测试时,该码被跳过,但不中断; ComSend[1].EquipAddr :=1; 设备站号0-255 ComSend[1].FunctionCode :=16#03; 功能码 目前支持 读取HR区16#03,读取AR区16#04 ,写入HR区,16#06 ComSend[1].StartAddr :=2504; Modscan测试地址 ComSend[1].Length :=12; 读取寄存器数量 ComSend[1].StartMem :=0; 存放入DataStr数组起始位置,一般为前一数据的起始地址+前一数据读取寄存器长度*2 ComSend[1].WriteDataH1 :=0; 写入数据的高位,读取时不需要输入 ComSend[1].WriteDataL1 :=WriteTestW; 写入数据的低位,读取时不需要输入 read 读取数据 DataType[00]:=1; 数据类型设置 1 int 2 float 3 byte 0 任意,数据多时不建议为0 DATA:=DataRead[00].TO_BYTE; 分别对应 DataType 3 DATA:=DataRead[00].TO_REAL; 分别对应 DataType 2 DATA:=DataRead[00].TO_INT; 分别对应 DataType 1 备 注: 目前为示例程序,暂未完成封装,测试程序需两台EXC5000C3版本以上CPU,本程序下载CPU配置COM口为自由口,另一CPU配置COM口为Modbus 配套示例读取程序为表具仿真程序MeterSim 依赖块:无 *) (**********************************************通讯配置**********************************************) (* 以下延迟时间关系为基于EXC1000COM口485读取,测试速度为9600BPS所得 若简短和延长,需同步调整,比例关系参考目前关系 延迟时间和总线波特率,被访问设备响应速度有关,需根据实际情况调整 *) (********************************************串口通讯发送********************************************) t1_in:=not t1.q; t1(IN :=t1_in , PT :=ComSendDelay ); t2(IN :=t1_in , PT :=ComRecvDelay ); t3(IN :=t1_in , PT :=ComAnalysisDelay ); if t1.q=0 and t1_q=1 then Send_act:=1; else Send_act:=0; end_if; if t2.q=1 then Recv_act:=1; else Recv_act:=0; end_if; if ComSend[1].Enable=1 then (*序列首位有配置的话开始执行发送命令*) if Send_act=1 then if TestMode=0 then Commandsend:=Commandsend + 1; end_if; end_if; if ComSend[Commandsend].Enable=0 then (*执行到非启用位结束*) Commandsend:=0; end_if; if ComSend[Commandsend].Enable=1 then (*读取命令*) if ComSend[Commandsend].FunctionCode = 16#03 then SendStr[0]:=ComSend[Commandsend].EquipAddr;(*地址*) SendStr[1]:=ComSend[Commandsend].FunctionCode;(*功能码*) SendStr[2]:=int_to_byte((ComSend[Commandsend].StartAddr-1) / 256);(*起始地址高位*) SendStr[3]:=int_to_byte((ComSend[Commandsend].StartAddr-1) mod 256);(*起始地址低位*) SendStr[4]:=0;(*长度高位*) SendStr[5]:=ComSend[Commandsend].Length;(*长度低位*) end_if; (*读取命令*) if ComSend[Commandsend].FunctionCode = 16#04 then SendStr[0]:=ComSend[Commandsend].EquipAddr;(*地址*) SendStr[1]:=ComSend[Commandsend].FunctionCode;(*功能码*) SendStr[2]:=int_to_byte((ComSend[Commandsend].StartAddr-1) / 256);(*起始地址高位*) SendStr[3]:=int_to_byte((ComSend[Commandsend].StartAddr-1) mod 256);(*起始地址低位*) SendStr[4]:=0;(*长度高位*) SendStr[5]:=ComSend[Commandsend].Length;(*长度低位*) end_if; (*写入命令*) if ComSend[Commandsend].FunctionCode = 16#06 then SendStr[0]:=ComSend[Commandsend].EquipAddr;(*地址*) SendStr[1]:=ComSend[Commandsend].FunctionCode;(*功能码*) SendStr[2]:=int_to_byte((ComSend[Commandsend].StartAddr-1) / 256);(*起始地址高位*) SendStr[3]:=int_to_byte((ComSend[Commandsend].StartAddr-1) mod 256);(*起始地址低位*) SendStr[4]:=ComSend[Commandsend].WriteDataH1; SendStr[5]:=ComSend[Commandsend].WriteDataL1; end_if; end_if; if ComSend[Commandsend].Enable=2 then SendStr[0]:= 16#00 ; SendStr[1]:= 16#00 ; SendStr[2]:= 16#00 ; SendStr[3]:= 16#00 ; SendStr[4]:= 16#00 ; SendStr[5]:= 16#00 ; end_if; (*CRC校验*) CRC16Lo := 255; CRC16Hi := 255; CL := 1; CH := 160; for i := 0 To 5 by 1 do CRC16Lo := CRC16Lo xor SendStr[i]; for Flag := 0 to 7 by 1 do SaveHi := CRC16Hi; SaveLo := CRC16Lo; CRC16Hi := shr(CRC16Hi,1); CRC16Lo := shr(CRC16Lo,1); if ((SaveHi and 1) = 1) then CRC16Lo := CRC16Lo Or 128; end_If; If ((SaveLo and 1) = 1) then CRC16Hi := CRC16Hi xor CH; CRC16Lo := CRC16Lo xor CL; end_if; end_for; end_for; SendStr[6]:=CRC16Lo; SendStr[7]:=CRC16Hi; end_if; if t3.q=1 then (*接收数据处理*) if Recv_len>=6 then(*有返回*) Recv_lenp :=Recv_len-3;(*去掉校验位-2,0开始-1*) CRC16Lo := 255; CRC16Hi := 255; CL := 1; CH := 160; for i := 0 To Recv_lenp by 1 do CRC16Lo := CRC16Lo xor RecvStr[i]; for Flag := 0 to 7 by 1 do SaveHi := CRC16Hi; SaveLo := CRC16Lo; CRC16Hi := shr(CRC16Hi,1); CRC16Lo := shr(CRC16Lo,1); if ((SaveHi and 1) = 1) then CRC16Lo := CRC16Lo Or 128; end_If; If ((SaveLo and 1) = 1) then CRC16Hi := CRC16Hi xor CH; CRC16Lo := CRC16Lo xor CL; end_if; end_for; end_for; CRC16Hi1:=CRC16Hi; CRC16Lo1:=CRC16Lo; Recv_CRCLo:=Recv_lenp+1; Recv_CRCHi:=Recv_lenp+2; (* funtionCode03 Tx 设备地址 03 起始地址高位 起始地址地位 寄存器数量高位 寄存器数量低位 CRCH CRCL Rx 设备地址 03 字节数 寄存器1高位 寄存器1低位... CRCH CRCL Recv_DataLen =(RecvStr[2])-1 是因为数据长度=“字节数”(字节数刚好是RecvStr[2])-1(i从0开始,所以-1) j = i+3 是因为数据位是从第3位之后开始的 funtionCode06 Tx 设备地址 06 起始地址高位 起始地址地位 寄存器值高位 寄存器值低位 CRCH CRCL Rx 设备地址 06 起始地址高位 起始地址地位 寄存器值高位 寄存器值低位 CRCH CRCL Recv_DataLen =(RecvStr[2])-1 是因为数据长度=2(单个寄存器读取只有2位)-1(i从0开始,所以-1) j = i+4 是因为数据位是从第4位之后开始的 *) if ComSend[Commandsend].FunctionCode = 16#03 or ComSend[Commandsend].FunctionCode = 16#04 then Recv_DataLen:=byte_to_int(RecvStr[2])-1; end_if; if ComSend[Commandsend].FunctionCode = 16#06 then Recv_DataLen:=1; end_if; if Commandsend<>0 then if RecvStr[Recv_CRCLo]=CRC16Lo and RecvStr[Recv_CRCHi]=CRC16Hi then(*校验正确*) for i:=0 to Recv_DataLen by 1 do if ComSend[Commandsend].FunctionCode = 16#03 or ComSend[Commandsend].FunctionCode = 16#04 then j:=i+3; end_if; if ComSend[Commandsend].FunctionCode = 16#06 then j:=i+4; end_if; if (SendStr[2]=int_to_byte((ComSend[Commandsend].StartAddr-1) / 256)) and (SendStr[3]=int_to_byte((ComSend[Commandsend].StartAddr-1) mod 256) )and (SendStr[1]=ComSend[Commandsend].FunctionCode) then(*功能码*) CalcStr[i]:=RecvStr[j]; k:=ComSend[Commandsend].StartMem+i; DataStr[k]:=CalcStr[i]; end_if; end_for; ErrStr[commandsend]:=0; else (*错误累计*) ErrStr[Commandsend]:=ErrStr[Commandsend]+1; if ErrStr[Commandsend]>999 then ErrStr[Commandsend]:=999; end_if; end_if; end_if; (*清空接收*) for i:=0 to 200 by 1 do RecvStr[i]:=0; CalcStr[i]:=0; end_for; (*错误返回累计超时,清空内容*) if ErrStr[Commandsend]>ComTimeoutCount and ComTimeoutClear=1 then StopMem:=ComSend[Commandsend].StartMem+byte_to_int(ComSend[Commandsend].Length); for i:=ComSend[Commandsend].StartMem to StopMem by 1 do DataStr[i]:=0; end_for; end_if; end_if; end_if; SendP:=&SendStr; RecvP:=&RecvStr; SendFB(EN :=Send_act , COM :=SerialCom , DATA :=SendP , LENGTH :=8 , IQM :=0 ); RecvFB(EN :=Recv_act , COM :=SerialCom , DATA :=RecvP , IQM :=0 | Recv_len:= LENGTH); t1_q:=t1.q; t2_q:=t2.q; t3_q:=t3.q; (********************************************数据处理********************************************) for i := 0 To DataReadLength by 1 do (* Type 为0时全读*) i0:=i; i1:=i+1; i2:=i+2; i3:=i+3; (* 1 int *) DataRead[i].TO_INT:=byte_to_int(DataStr[i0])*256+byte_to_int(DataStr[i1]); (* 2 Float *) TempStr[0]:=DataStr[i1]; TempStr[1]:=DataStr[i0]; TempStr[2]:=DataStr[i3]; TempStr[3]:=DataStr[i2]; P1:=&TempStr; P2:=&TempReal; v2v(DEST :=P2 , SRC :=P1 , LENGTH :=4 , IQM :=0 ); DataRead[i].TO_REAL:=TempReal; (* 3 byte *) DataRead[i].TO_BYTE:=1; end_for; (* 此段解析方式上后会导致错位,有时间时测试原因 for i := 0 To DataReadLength by 1 do if DataType[i]=1 or DataType[i]=0 then i0:=i; i1:=i+1; DataRead[i].TO_INT:=byte_to_int(DataStr[i0])*256+byte_to_int(DataStr[i1]); end_if; if DataType[i]=2 or DataType[i]=0 then i0:=i; i1:=i+1; i2:=i+2; i3:=i+3; TempStr[0]:=DataStr[i1]; TempStr[1]:=DataStr[i0]; TempStr[2]:=DataStr[i3]; TempStr[3]:=DataStr[i2]; P1:=&TempStr; P2:=&TempReal; v2v(DEST :=P2 , SRC :=P1 , LENGTH :=4 , IQM :=0 ); DataRead[i].TO_REAL:=TempReal; end_if; if DataType[i]=3 or DataType[i]=0 then i0:=i; DataRead[i].TO_BYTE:=DataStr[i]; end_if; 4 BCD if DataType[i]=4 or DataType[i]=0 then i0:=i; i1:=i+1; k1:=byte_to_int(DataStr[i0])*256+byte_to_int(DataStr[i1]); DataRead[i].TO_BCD01:=int_to_bool( k1 Mod 2#0000000000000001 ); DataRead[i].TO_BCD02:=int_to_bool( k1 Mod 2#0000000000000010 ); DataRead[i].TO_BCD03:=int_to_bool( k1 Mod 2#0000000000000100 ); DataRead[i].TO_BCD04:=int_to_bool( k1 Mod 2#0000000000001000 ); DataRead[i].TO_BCD05:=int_to_bool( k1 Mod 2#0000000000010000 ); DataRead[i].TO_BCD06:=int_to_bool( k1 Mod 2#0000000000100000 ); DataRead[i].TO_BCD07:=int_to_bool( k1 Mod 2#0000000001000000 ); DataRead[i].TO_BCD08:=int_to_bool( k1 Mod 2#0000000010000000 ); DataRead[i].TO_BCD09:=int_to_bool( k1 Mod 2#0000000100000000 ); DataRead[i].TO_BCD10:=int_to_bool( k1 Mod 2#0000001000000000 ); DataRead[i].TO_BCD11:=int_to_bool( k1 Mod 2#0000010000000000 ); DataRead[i].TO_BCD12:=int_to_bool( k1 Mod 2#0000100000000000 ); DataRead[i].TO_BCD13:=int_to_bool( k1 Mod 2#0001000000000000 ); DataRead[i].TO_BCD14:=int_to_bool( k1 Mod 2#0010000000000000 ); DataRead[i].TO_BCD15:=int_to_bool( k1 Mod 2#0100000000000000 ); DataRead[i].TO_BCD16:=int_to_bool( k1 Mod 2#1000000000000000 ); end_if; *) END_FUNCTION_BLOCK