📄 全局变量.txt
字号:
~200260e0B00560000
先定义几个全局变量:
FDataStatus:Word; //0 就绪 1.帧头1 2.帧头2
FNextLength:Word; //接下来要读的长度
FCurrentLength:Word; //当前的长度
FtmpStr:string;//一帧数据
代码如下:
procedure ComPortRxChar(Sender: TObject; Count: Integer);
var
S1:string;
J:Integer;
begin
case FDataStatus of
0:begin
J:=0;
FtmpStr:='';
repeat
ComPort.ReadStr(S1,1);
J:=J+1;
until (Ord(S1[1])=$68) or (J=Count);
if Ord(S1[1])=$68 then
begin
FtmpStr:=S1;
FDataStatus:=1;
FNextLength:=10;
FCurrentLength:=0;
end;
end;
1:begin
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
if FCurrentLength=FNextLength then
begin
FDataStatus:=2;
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
FCurrentLength:=0;
end;
end;
2:begin
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
if FCurrentLength=FNextLength then
begin
FDataStatus:=0;
FNextLength:=0;
FCurrentLength:=0;
FReceiveFrame.Str:=FtmpStr;
SendMessage(CommServer.Handle,XM_OutData,0,LongWord(@FReceiveFrame));
end;
end;
end;
end;
case FDataStatus of
判断目前接收的数据段。
0:begin
J:=0;
FtmpStr:='';
repeat
ComPort.ReadStr(S1,1);
J:=J+1;
until (Ord(S1[1])=$68) or (J=Count);
if Ord(S1[1])=$68 then
begin
FtmpStr:=S1;
FDataStatus:=1;
FNextLength:=10;
FCurrentLength:=0;
end;
end;
如果是一帧开始,那么就对相关参数进行初始化,数据帧初始化为空。
J:=0;
FtmpStr:='';
然后开始读,直到读到帧起始符$68或者读完为止,没有的话,就抛弃读到的数据(当然你也可以另外记下来,不过意义不大)。
repeat
ComPort.ReadStr(S1,1);
J:=J+1;
until (Ord(S1[1])=$68) or (J=Count);
J就是读到的数据,J=count表示读完了缓冲区。
Ord(S1[1])=$68,S1是个字符串,ORD(S1[1])表示S1的第一个字节。
ComPort.ReadStr(S1,1);每次只读一个字节,所以S1里只有1个字节。
如果$68在你读的第二个字节里,那不是蒙混过关了?
如果读到了$68,就接下来设置读状态为第二阶段,帧数据加上$68,下阶段长度为10,读取的字节数为0.然后结束函数。
if Ord(S1[1])=$68 then
begin
FtmpStr:=S1;
FDataStatus:=1;
FNextLength:=10;
FCurrentLength:=0;
end;
如果缓冲区还有数据,它将再次触发OnRxChar事件。
只要缓冲区中有数据,没有处理或者没有正在处理,就会一直触发OnRxChar事件。
刚才将读状态设置为第二阶段了,那么现在就会执行第二阶段的代码:
Case语句来判断的。
1:begin
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
if FCurrentLength=FNextLength then
begin
FDataStatus:=2;
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
FCurrentLength:=0;
end;
end;
J:=FCurrentLength;//计数器,记下共读了几个。
RTUA 4 终端逻辑地址
MSTA 2 主站地址与命令序号
68H 1 帧起始符
C 1 控制码
L 2 数据长度
这就是我们第二阶段要读的10个字节。
第二阶段的处理是读10个字节,不管缓冲区里有几个字节,不管读几次,读满10个为止。
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
FNextLength=10是第一阶段设置的。
读到10个字节之后将进入第三阶段。
if FCurrentLength=FNextLength then
begin
FDataStatus:=2;
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
FCurrentLength:=0;
end;
下一阶段要读几个呢?
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
DATA 变长 数据域
CS 1 校验码
16H 1 结束码
这就是最后一阶段要读的字节数。
但是DATA是变长,所以我们才会多分一阶段,如果都是定长的话,只用判断到帧起始符,然后再读需要的字节数就可以了。
好在有个
L 2 数据长度
表示DATA的长度。
所以就有了这一句。
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
+2是最后两个字节。
接下来就是第三个阶段了:
2:begin
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
if FCurrentLength=FNextLength then
begin
FDataStatus:=0;
FNextLength:=0;
FCurrentLength:=0;
FReceiveFrame.Str:=FtmpStr;
SendMessage(CommServer.Handle,XM_OutData,0,LongWord(@FReceiveFrame));
end;
end;
end;
佛山-sky A (11116580) 20:55:21
第三个阶段的读法跟前面一样,都是读一个计一个,直到读完需要的字节为止。
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
读好了就初始化:
if FCurrentLength=FNextLength then
begin
FDataStatus:=0;
FNextLength:=0;
FCurrentLength:=0;
FReceiveFrame.Str:=FtmpStr;
SendMessage(CommServer.Handle,XM_OutData,0,LongWord(@FReceiveFrame));
最后就得到一帧完整的数据FtmpStr,然后将这个数据发送给处理数据的函数。
SendMessage(CommServer.Handle,XM_OutData,0,LongWord(@FReceiveFrame));
相关的函数就会来处理它了。
这样,不关什么东西来,什么时候来,一帧完整的数据都在FtmpStr这个字符串里
当然具体的读法还要根据数据帧的格式来定,但是万变不离其宗:就是将读到的字节跟给定的格式比较。
有了这些知识,就基本上可以应付串口通讯程序的编写了。
考虑到Delphi面向对象的特性,如果不用类来规范数据帧的格式似乎有些可惜了。
还是拿刚才那帧数据格式来举例:
我们可以这样定义基类。
TCustomGY = class(TObject)
//0 规约基类
private
FFrameBegin:Byte; //帧起始符
FTerminalLogicAddr:T4Byte; //终端逻辑地址
FMasterStation:T2Byte; //主站地址与命令序号
FFrameBegin2:Byte; //帧起始符
FFrameControl:Byte; //控制码
FDataLength:Word; //数据长度
FFrameVerify:Byte; //校验码
FFrameEnd:Byte; //结束码
FPosSend:TFramePosTerminal; //帧发起端
FPosReceive:TFramePosTerminal; //帧接收端
procedure SetDataLength(const Value: Word);
procedure SetFrameBegin(const Value: Byte);
procedure SetFrameBegin2(const Value: Byte);
procedure SetFrameControl(const Value: Byte);
procedure SetFrameEnd(const Value: Byte);
procedure SetFrameVerify(const Value: Byte);
function ReadFrameDataLengthHi: Byte;
function ReadFrameDataLengthLo: Byte;
function ReadFrameFSEQ: Byte;
function ReadFrameISEQ: Byte;
function ReadFrameMSTA: Byte;
function ReadFrameMSTA2: Byte;
procedure SetPosReceive(const Value: TFramePosTerminal);
procedure SetPosSend(const Value: TFramePosTerminal);
procedure SetMS1(const Value: Byte);
procedure SetMS2(const Value: Byte);
procedure SetFrameFSEQ(const Value: Byte);
procedure SetFrameISEQ(const Value: Byte);
procedure SetFrameMSTA(const Value: Byte);
procedure SetFrameMSTA2(const Value: Byte);
function GetTerminalAddr: Word;
function GetTerminalLogicAddr(const AIndex: T4Integer): Byte;
procedure SetTerminalAddr(const Value: Word);
procedure SetTerminalLogicAddr(const AIndex: T4Integer;
const Value: Byte);
function GetMasterStation(const AIndex: T2Integer): Byte;
procedure SetMasterStation(const AIndex: T2Integer; const Value: Byte);
function GetTerminalLogicAddrStr: string;
procedure SetTerminalLogicAddrStr(const Value: string);
function GetMasterstationStr: string;
procedure SetMasterstationStr(const Value: string);
protected
procedure SetData(const Value: string); virtual; abstract;//设置数据区 虚函数,实现方法在子类中
function ReadData:string; virtual; abstract;//ReadData虚函数,由子类重载.
function ToVerifyFrame: Boolean; virtual;//帧是否正确
function ReadWholeFrame: string; virtual;
procedure setWholeFrame(const Value: string); virtual;
public
property FrameBegin:Byte read FFrameBegin write SetFrameBegin; //帧起始符
property RTUA[const AIndex:T4Integer]:Byte
read GetTerminalLogicAddr write SetTerminalLogicAddr; //终端逻辑地址
property RTUAStr:string read GetTerminalLogicAddrStr write SetTerminalLogicAddrStr;
property TerminalAddr:Word read GetTerminalAddr write SetTerminalAddr; //终端地址
property MSTAs[const AIndex:T2Integer]:Byte read GetMasterStation write SetMasterStation; //主站地址与命令序号
property MSTAStr:string read GetMasterstationStr write SetMasterstationStr;
property FrameBegin2:Byte read FFrameBegin2 write SetFrameBegin2; //第二个帧起始符
property FrameControl:Byte read FFrameControl write SetFrameControl; //控制码
property DataLength:Word read FDataLength write SetDataLength; //数据长度,2位,地位在前,高位在后
property Data:string read ReadData write SetData; //数据,虚属性,在子类实现
property CS:Byte read FFrameVerify write SetFrameVerify; //帧校验位
property FrameEnd:Byte read FFrameEnd write SetFrameEnd; //帧结束码
property FrameIsRight:Boolean read ToVerifyFrame; //检测帧是否正确
property WholeFrame:string read ReadWholeFrame write setWholeFrame; //整帧信息,虚函数,子类实现
property MS1:Byte read FMasterStation[0] write SetMS1; //MS1
property MS2:Byte read FMasterStation[1] write SetMS2; //MS2
property MSTA:Byte read ReadFrameMSTA write SetFrameMSTA; //MSTA
property MSTA2:Byte read ReadFrameMSTA2 write SetFrameMSTA2; //MSTA2,发送端和接收端都为主站时有效
property ISEQ:Byte read ReadFrameISEQ write SetFrameISEQ; //ISEQ
property FSEQ:Byte read ReadFrameFSEQ write SetFrameFSEQ; //FSEQ
property DataLengthLo:Byte read ReadFrameDataLengthLo; //数据长度低位
property DataLengthHi:Byte read ReadFrameDataLengthHi; //数据长度高位
property PosSend:TFramePosTerminal read FPosSend write SetPosSend; //帧发送端
property PosReceive:TFramePosTerminal read FPosReceive write SetPosReceive; //帧接收端
class function VerifyFrame(S:string): Boolean; //帧是否正确
class function GetVerifyByte(S:string):Byte; //得到校验码
constructor Create; virtual;
destructor Destory; virtual;
function DecodeFrame(const S:string;NoError:Boolean=True):Boolean; //dynamic; //分解操作后返回True
function SumVerify:Byte; dynamic; //计算数据帧CS码
function SumDataLength:Word; dynamic; //计算数据长度
end;
所有的判断处理的东西都交给这个类。
然后不同的命令再派生出不同的子类。
留两个接口,一个进一个出。
其他的那些中间数据就随意处置。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -