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