急!利用Rs-2332通讯,如果接收的数据比较长,一次收不完怎办?(串口通讯知识的剖析,大家都来看看) (10分)

  • 主题发起人 主题发起人 青云
  • 开始时间 开始时间

青云

Unregistered / Unconfirmed
GUEST, unregistred user!
我写了一个串口调试软件,在我的网站上 http://www.yzsoftware.com
里面有一个问题我觉得我处理的不是很好,所以想向大家请教:
就是现在我用PC机和终端通过com口进行通讯,PC机问,终端答,都是16进制码。
我自己编写的通讯控件,但是我遇到一个麻烦的问题,就是我每一个报文,不能马上就收,要延时,不然可能只收到终端回复报文的一截,于是我这样处理
   发送报文;
sleep(500);
   接收报文;
但是接受的报文有的短有的长,延时的长短我没有办法把握;
  于是我就这样处理;
   发送报文;
sleep(20);
  看看接收缓冲区的报文长度;
sleep(20);
   再看看接收缓冲区的报文长度;   
  比较这两次报文长度是否相等,如果相等,说明收全了,这时就读出缓冲区的报文,清空缓冲区;如果不等,说明终端正在发报文,还没有发完,再看看接收缓冲区的报文长度,继续比较,如此循环,只到两次收到的报文长度相等了,才读出缓冲区的报文,清空缓冲区;
  对应的代码:
timedelay(20);
ClearCommError(hCommHandle,dwError,@CS); //取得状态
comp:=cs.cbInQue;
// timedelay(1);
for i:=1 to 50 do
begin
timedelay(20);
ClearCommError(hCommHandle,dwError,@CS); //取得状态
if (comp=cs.cbInQue) and (cs.cbInQue>1) then break;//报文收全了
  end;
FreadComCount:=i;
// application.MessageBox(pchar(inttostr(i)),'系统提示!',mb_ok+mb_iconinformation);
RecieveCount:=cs.cbInQue;
if cs.cbInQue >2048 then
begin
PurgeComm(hCommHandle, PURGE_RXCLEAR);
RecieveCount:=0;
// 清除COM 数据
exit;
end;
try
ReadFile(hCommHandle, ByteArray,cs.cbInQue,nBytesRead,nil); // 接收COM 的数据
except

end;


但是我觉得这个方法不好,有谁能告诉我还有什么更好的方法,如果有谁编写过相关控件,
能不能发给我看看 : sy_dzc@yahoo.com.cn





 
为什么不用线程监视串口是否有数据。有数据就接收,自己定义一个规则。我看你做的很不规范。
 
太长就分几次发送.
 
chen_cch朋友,您说得很对,我是做得很不规范,一般都是用线程监视串口的
但是我还是有点疑问,就算用线程监视串口的 ,
当串口上有数据的时候,我是不是就要立即读取呢,万一对方正在发着数据。
这是侯如果读的话,可能只收到一半。那怎么办?
我测试过了,如果报文比较长,可能要发送200毫秒左右;
那么我该如何控制呢?
 
影 子朋友,报文的发送倒没有多大问题,关键是接受处理;
就算监视串口,我怎样才能知道串口收到数据,并且对方已经发送结束了,而不是正在发送着。因为对方正在发送的时候,我收到的报文只是一半。
 
定义协议结构.
结构头,ID,长度,内容,结构尾
 
问题出在协议上,有很多这样的链路层协议,自己找一个,看人家的协议怎么定的,按协议编写一个通讯程序一般就没问题了
 
你的疑问我不太清楚,看你的意思是说会丢数据。这个我告诉你不会丢数据。我用串口接收过几十M的数据,没有丢一个数据。
 
看看别人是怎么写的不就明白了!下载一个带源码的,推荐cport
 
我用串口做过。应该是有报文协议的。每条报文也不应该过长。每条报文之间的时间间隔为50ms,波特率是19.2kbps,不存在这个问题啊,把串口打开,读取规定长度的报文就可以了。
 
sleep(500);
然后判断接收字符串中的末尾字符是否是结束字符
如:<01abcdefg>判断结束字符是否为'>',如果是则表明接收成功!!

最好在发送过程中分段发送……
 
有谁知道 spcomm这个控件是如何控制收取报文的处理机制的。
好像是根据串口缓冲区上接受到数据,触发 oncomm事件,但是报文的结束是如何控制的呢?
 
春意朋友,你的方法有一定局限性,比如最后的字符在中间怎办?
 
根据楼主的情况看,说明发送上下位的发送的数据是不等长的。要用这样的方法解快:
一、重新定义发送规则(也就是说在发送的数据前后加上识别)
二、因为马上收到的数据可能不是对方发送过来的全部数据,可以说明发送是异步的,所以只能用异步接收的办法。
三、判断接收的数据是不是对方发送过来的全部数据(用识别判断),处理接收数据。
 
李艾朋友,您的建议很好,我编的串口调试软件,是争对电气行业的101规约来的。
一般用异步接收。
其中报文的规则我们是不能改的。
每一帧一般都是以 68 开头, 16 结尾。
但是我在通讯过程中,不能用16作为一帧结束的标志,因为
这一帧中中间可能也有16
我想可能是利用api函数,根据串口状态判断一帧的结束。

 
to:青云
一、除非你们发送的数据是定长的。
二、如果你的下位机传送结果中间没有68的话可以用来作判断的条件,但这样没有实时性,并且在一般情况下不会没有这个数。
三、原则上说用API函数是行不通的,因为你做的程序是自动接收的,此为其一,其二就是要达到自接这个功能,一般情况下接收缓冲区有数据它就会接收,你怎么去判断一帧的结束。
我觉得应该查一下你的下位机说明,才能得到根本的解决。
 
直接搬HDLC(高级数据链路控制)不行吗?
 
收到数据就读到自定义的缓冲区里,等检测到达指定长度在处理,
 
朋友发给我下面一个函数:
函数 GetOverlappedResult 返回异步操作结果。
BOOL GetOverlappedResult(
HANDLE hFile, // 句柄
LPOVERLAPPED lpOverlapped, // OVERLAPPED 结构
LPDWORD lpNumberOfBytesTransferred, // 写入或读取的数据
BOOL bWait
);

★★★ bWait : True 直到异步操作完成才返回。
false 函数返回 false ,GetLastError 返回 ERROR_IO_INCOMPLETE。
注:GetOverlappedResult (bWaite = true) 的实质是等到 lpOverlapped.hEvent 变为
有信号时,(异步IO完成后,由系统设置)。函数返回。否则无限等待。
当然这里也可以用 WaitFor函数集 等待 lpOverlapped.hEvent 变为有信号状态。
--------------------------------------------------------
我想这个函数可能是解决我上面问题的方法。
===================================================
为了防止大家不清楚我的问题。我再重述一下我的问题:
比如我用pc机和终端设备通过RS-232进行通信,通信线就用了2、3、5这三个引脚。属于那种最简单的通讯方式。
现在我有一个疑问:

比如pc机发送 10 5B 01 5C 16 给终端
终端收到后,再回复给pc机
PC 机接收到 68 BE BE 68 88 01 02 74 05 01 0D 00 8D 00 02 00 10 3F 00 08 11 00 BF 01 3F 00 C0 00 09 10 01 3F 0B 00 00 BF 10 04 01 00 01 00 00 00 02 00 00 00 00 00 01 00 00 01 00 00 01 00 00 00 01 00 01 00 00 00 00 01 01 00 00 00 00 00 01 00 01 00 00 00 02 00 00 00 00 00 01 00 00 01 00 00 01 00 00 00 01 00 01 00 00 00 00 01 01 00 00 00 00 00 01 00 01 00 00 00 02 00 00 00 00 00 01 00 00 01 00 00 01 00 00 00 01 00 01 00 00 00 00 01 01 00 00 00 00 00 01 00 01 00 00 00 02 00 00 00 00 00 01 00 00 01 00 00 01 00 00 00 01 00 01 00 00 00 00 01 01 00 00 00 00 00 01 00 01 00 00 00 02 00 00 00 00 00 2F 16

现在我发现一个问题,就是
因为接收的抱文有点长
如果我这样写:
发送报文('10 5B 01 5C 16 ');
memo1.text:=接收报文;
那么接收到的可能是 68 BE BE 68 88 01 02 74 05 01 0D 00 8D 00 02 00 10 3F 00 08 11 00 BF 01 3F 00 C0 00 09 10 01 3F 0B 00 00 BF 10 04 01 00 01 00 00 00 02 00 00 00 00 00 01 00
也就是说我接受的时候,终端正在发送报文给pc机,如果我立即收报文,只能收到一截,而不是全部。

所以我改写了一下程序

发送报文('10 5B 01 5C 16 ');
sleep(500);
memo1.text:=接收报文;
这样接收到的报文就全了,但是延时500ms是不是太浪费了,而且如果终端回复给pc机报文太长,也许延时500ms也不一定够。

所以我想到了另一种方法

  发送报文('10 5B 01 5C 16 ');
sleep(20);
  看看接收缓冲区的报文长度;
sleep(20);
   再看看接收缓冲区的报文长度;   
  比较这两次报文长度是否相等,如果相等,说明收全了,这时就读出缓冲区的报文,清空缓冲区;如果不等,说明终端正在发报文,还没有发完,再看看接收缓冲区的报文长度,继续比较,如此循环,只到最后两次收到的报文长度相等了,才读出缓冲区的报文,清空缓冲区;

  对应的代码:
timedelay(20);
ClearCommError(hCommHandle,dwError,@CS); //取得状态
comp:=cs.cbInQue;
for i:=1 to 50 do
begin
timedelay(20);
ClearCommError(hCommHandle,dwError,@CS); //取得状态
if (comp=cs.cbInQue) and (cs.cbInQue>1) then break;//报文收全了
end;
RecieveCount:=cs.cbInQue;
if cs.cbInQue >2048 then
begin
PurgeComm(hCommHandle, PURGE_RXCLEAR);
RecieveCount:=0;
// 清除COM 数据
exit;
end;
try
ReadFile(hCommHandle, ByteArray,cs.cbInQue,nBytesRead,nil); // 接收COM 的数据
except

end;

不过我认为我这种方法不好 ,我看到好多串口调试软件都是把发送和接收分开处理的,因为本来Rs-232就是全双工方式,好象都用到了线程,
而且我发现好象没有报文只收到一半的情况,我觉得很奇怪,因为它们在读取缓冲区数据的时候,怎么知道此时终端发给pc机的数据已经发完了,
万一正在发送,那不是也有可能只收到一截吗?
  

 
朋友给我的资料,供大家参考,大家帮帮我,看看我那个问题该如何解决?
-------------------------------------------------
1、串口控制的方法:

a、打开串口的方法:
打开串口的方法与你说的一样。用 CreateFile 函数定议如下:
HANDLE CreateFile(
PChar lpszName, //指定要打开的串口的逻辑名:用字串表示:如 'COM1','COM2'
DWORD fdwAcess, //访问类型,读: GENERIC_READ 写:GENERIC_WRITE
DWORD fdwShareMode, //共享模式;必须为0。串口不能共享
LPSECURITY_ATTRIBUTES lpsa, //指向一个安全属性结构 设为Nil 。
DWORD fdwCreate, //创建方法:串口操作为:OPEN_EXISTING
DWROD fdwAttrsAndFlags, //串口操作的属性
HANDLE hTemplateFile //模板文件的句柄
);

注: LPSECURITY_ATTRIBUTES lpsa 这个参数的功能我在所有的参考书上都没有找到具体的
解释。一般都设为nil

★★★注意:fdwAttrsAndFlags 是一个比较容易被忽略的参数,那就是 FILE_FLAG_OVERLAPPED
在 Win98 下可以不要。在 2000/XP 下,如果要写成多线程的,那必须加上这个属性。
这在很多参考书上都没有的,是我在实际开发时,总结出来的。
加上 FILE_FLAG_OVERLAPPED 就是说串口以异步方式操作。异步是相对于同步的。比如:
你向串口写一个字串,一直等到它写完你才返回。这就是同步。而异步就不同,异步时:
你写了数据马上返回。而这时数据可能还没有发送出去。你可以在以后查询。


b、分配缓冲区
用函数 SetupComm, 定义如下:
BOOL SetupComm(
Handle hFile, // 串口句柄
DWORD dwInQueue, // 输入缓冲区
DWORD dwOutQueue // 输出缓冲区
);

c、串口参数配置
用函数 GetCommState 获取当前串口的配置
用函数 SetCommState 设置当前串口的配置
函数定义:
BOOL GetCommState(
HANDLE hFile, // 句柄
LPDCB lpDCB // 指向一个DCB 结构
);

BOOL SetCommState(
HANDLE hFile, // 句柄
LPDCB lpDCB // 指向一个DCB 结构
);
DCB 的定义参见 Delphi.
★★★注意:Delphi 里的 DCB 定义与微软文档上的定义不太一样,要少了很多参数。我
也不太明白是什么原因。我曾经在 DelphiBBS 上问过但没人回答。

d、读串口操作:
用 ReadFile 函数。也可以用 ReadFileEx
BOOL ReadFile(
HANDLE hFile, //句柄
LPVOID lpBuffer, //指向一个缓冲区
DWORD nNumberOfBytesToRead, //要读取的字节数
LPDWORD lpNumberOfBytesRead,//实际读取的字节数
LPOVERLAPPED lpOverLapped //一个_OVERLAPPED 结构,用于异步操作。
);

★★★ lpBuffer 指向的缓冲区要预先分配。这个参数在 Delphi 的定义很奇怪 var Buffer
没有指定类型。开始在用的时候有一个奇怪的情况,如果定义一个静态的数组,就
没有问题,如定义一个动态数组就不行。我在 DelphiBBS上问也没人回答我。最后我
在Delphi 的源码里找到一点思路:最后写成 Pointer(@Bufer[0])^ 就通过了。一个
很奇怪的格式。这点与C不同。

★★★ lpOverLapped 如果在打开端口时没有用 FILE_FLAG_OVERLAPPED 参数就要用 nil 。
如果指定了 FILE_FLAG_OVERLAPPED 。就必须有 LpOverLapped 参数。方法见后。
如指定了 FILE_FLAG_OVERLAPPED ,返回值没用。如没有其它错误用 GetLastError
得到的错误值为: ERROR_IO_PENDING 或 ERROR_IO_INCOMPLETE。

e、写串口的方法;
用 WriteFile 或 WriteFileEx 。
BOOL WriteFile(
HANDLE hFile, // 句柄
LPVOID lpBuffer, // 指向缓冲区
DWORD nNumberOfBytesToWrite, // 想要写入的字节数
LPDWORD lpNumberOfBytesWritten,// 实际写入的字节数
LPOVERLAPPED lpOverlapped // 一个 _OVERLAPPED 结构
);

★★★ lpBuffer 的使用方法与 ReadFile 一样。

f、异步操作方法:
OVERLAPPED 在 Delphi 中的定义如下:
_OVERLAPPED = record
Internal: DWORD;
InternalHigh: DWORD;
Offset: DWORD;
OffsetHigh: DWORD;
hEvent: THandle;
end;

在实际编程时只有:hEvent 要用到。
hEvent 是一个信号量。信号量是一个 Windows 的类型。它有两个状态。一为有信号状态,
一是无信号状态,类似于 BOOL 值,但它于 BOOL 不同的是,这是互斥的。
使用前我先用 CreateEvent 创建,使用完后要用 CloseHandle 释放它。
用 ResetEvent 设为无信号 用 SetEvent 设为有信号

HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 指向一个安全属性 一般为 nil
BOOL bManualReset, // 是否手动重置
BOOL bInitialState, // 初始状态
LPCTSTR lpName // 对象名称 一般为 nil
);

bManualReset ;True : 在手动用 ResetEvent 设置为 无信号状态
false : 不用手动重置,它会自动设置为 无信号状态
bInitialState :最初状态 True 为有信号 false 为无信号。


BOOL ResetEvent(
HANDLE hEvent // handle to event
);

BOOL SetEvent(
HANDLE hEvent // handle to event
);

函数 GetOverlappedResult 返回异步操作结果。
BOOL GetOverlappedResult(
HANDLE hFile, // 句柄
LPOVERLAPPED lpOverlapped, // OVERLAPPED 结构
LPDWORD lpNumberOfBytesTransferred, // 写入或读取的数据
BOOL bWait
);

★★★ bWait : True 直到异步操作完成才返回。
false 函数返回 false ,GetLastError 返回 ERROR_IO_INCOMPLETE。
注:GetOverlappedResult (bWaite = true) 的实质是等到 lpOverlapped.hEvent 变为
有信号时,(异步IO完成后,由系统设置)。函数返回。否则无限等待。
当然这里也可以用 WaitFor函数集 等待 lpOverlapped.hEvent 变为有信号状态。

例子1:
var
m_ovWrite:TOverLapped;
Buf:array of char;
...
m_ovWrite.hEvent := CreateEvent(nil,false,false,nil); // 初始化无信号
...
//ReSetEvent(m_ovWrite.hEvent); //可删去因为初始化无信号

bRtn := WriteFile(m_hCom,
(@Buf[0])^, // <--- Notic This
len,
n,
@m_ovWrite);

ErrCode := GetLastError;
if (ErrCode = ERROR_IO_PENDING) or (ErrCode =ERROR_IO_INCOMPLETE) then // IO 未完成
bRtn := GetOverlappedResult(m_hCom, // 等待 IO 完成
m_ovWrite,
ovWrite, // 实际上写入的字节数
true);

CloseHandle(m_ovWrite.hEvent); // 关闭信号句柄

if not bRtn then
begin
Raise Exception.Create('写端口时出现错误!'+SysErrorMessage(GetLastError));
Exit;
end;
...

例子2:
var
m_ovRead : TOVERRLAPPED;
Buffer:array of char;
...

m_ovRead.hEvent := CreateEvent(nil,false,false,nil);
//ResetEvent(m_ovRead.hEvent );

bRtn := ReadFile(m_hCom,
Pointer(Buffer[0])^,
iCount,
nReaded,
@m_ovRead);

ErrCode := GetLastError;

if (ErrCode = ERROR_IO_PENDING ) or (ErrCode = ERROR_IO_INCOMPLETE) then
bRtn := GetOverlappedResult(m_hCom,
m_ovRead,
ovRead,
true);

CloseHandle(m_ovRead.hEvent);

if not bRtn then
begin
Raise Exception.Create('ReadFile Error!'+ SysErrorMessage(GetLastError));
Exit;
end;

上面的例子是异步时的。同步时与你写的一样。

g、操作通信事件
可以用 SetCommMask 来指定要监视的通信事件。
BOOL SetCommMask(
HANDLE hFile,
DWORD dwEvtMask
);
dwEvtMask 是常数的组合;常数的定义与含义如下:

EV_BREAK 检测到输入的终止
EV_CTS CTS 信号改变
EV_DSR DSR 信号改变
EV_ERR 线路错误
EV_RING 检测到振铃
EV_RLSD RLSD 信号改变
EV_RXCHAE 接收到一个字符并放入缓冲区
EV_RXFLAG 接到事件字符DCB 中的EvtChar 。
EV_TXEMPTY 发送缓冲区空
例:
var
EvtMask :DWORD;
....
EvtMask := EV_CTS or EV_DSR or EV_ERR or EV_EXCHAE or EV_RING;
SetCommMask(m_hComm,EvtMask);
....

h、★★★ 监视通信事件 ★★★
这是关键的地方,函数 WaitCommEvent 的使用。
定义:
BOOL WaitCommEvent(
HANDLE hFile,
LPDWORD lpEvtMask,
LPOVERLAPPED lpOverlapped //异步操作时使用。
);
例:
...
bRtn:=WaitCommEvent(m_hCom,EvtMask,nil);
if (EvtMask and EV_RXCHAR) >0 then
begin
if Assigned(FOnReceive) then FOnReceive(Self);
end;

if (EvtMask and EV_CTS) >0 then
begin
if Assigned(FOnCtsChange) then FOnCtsChange(Self);
end;
....

★★★这个函数不太好用,有很多串口控件都没有用这个函数。但用好了就很方便了。
这个函数难用,就是因为它是一个阻塞函数。就是你一使用这个函数就要一直等到有事件
发生时,它才返回。如果用单线程的程序就不行,因为一运行到这里,它就会像死了一样。
所以就必须写成多线程的。

//-------------------------------------------------------------------------
2、类的框架

把上面的看了之后应该想一下如何写类了。要写一个多线程的。在 Delphi 里可以用
TThread 继承。TThread 是一个抽象类。就是扩展它的 Execute 函数。Execute 就是
一线程体。类的框架如下:
你可以先写一个同步的类,也就是不用 OVERLAPPED,在 Win98 下可以正常工作了,
再加入异步,在2000/XP 测试,如通过,则大功告成了。
(以下是以同步I/O讲解:)

类主要成员说明:
Private
m_PortOpen :Boolean; //表示端口是否处于打开状态,True:打开,False:关闭
m_Baud :Longword; //波特率 缺省值:9600
m_Parity:byte; //是否奇偶校检 0~4 分别为 No,ODD,Even,Mark,Space
m_StopBits :byte; //停止位位数 取值:0、1、2 ,分别为 1,1.5,2
m_hCom:longword; //端口句柄
m_InBufferSize:longword; //输入缓冲大小:
m_OutBufferSize:longword;//输出缓冲大小:
m_ErrorChar:Char; //校检错时的填充字符
m_ByteSize: Byte; //数据据位数 5、6、7、8
m_CommDcb:TDCB; //端口DCB结构
m_TimeOuts:TCOMMTIMEOUTS;//超时结构
m_ErrorNumber:DWORD; //出错时的错误号
FOnReceive:TNotifyEvent; //接收字符时引用的函数句柄 WaitCommEvent
FOnErr:TNotifyEvent; //发生出错事件时函数句柄 WaitCommEvent
FOnCtsChange:TNotifyEvent;//当CTS状态变化时引用的函数句柄
FOnDsrChange:TNotifyEvent;//当DSR状态变化时引用的函数句柄
FOnRing:TNotifyEvent; //当收到响铃消息时引用的函数句柄
FOnCDChange:TNotifyEvent; //当CD状态变化时引用的函数句柄
FOnTxEmpty:TNotifyEvent; //当发送缓冲区空时引用函数句柄
Public
property CommPort:longint read m_CommPort Write SetCommPort;
//设置端口号
property PortOpen:Boolean read m_PortOpen write SetPortOpen;
//读取端口状态,或打开/关闭端口
property Baud:longword read m_Baud write SetBaud;
//设置/读取波特率,端口打开后只读
property ByteSize:byte read m_ByteSize write SetByteSize;
//设置/读取数据位数,端口打开后只读
property Parity:byte read m_Parity write SetParity;
//校验方法 。端口打开后只读
property StopBits:Byte read m_StopBits write SetStop;
//设置停止位
property InBufferSize:longword read m_InBufferSize write m_InBufferSize;
//输入缓冲大小,端口打开后只读
property OutBufferSize:Longword read m_OutBufferSize Write m_OutBufferSize;
//输出缓冲大注,端口打开后只读
property OnReceive:TNotifyEvent write FOnReceive;
//函数句柄
property OnCDChange:TNotifyEvent write FOnCDChange;
property OnCtsChange:TNotifyEvent write FOnCtsChange;
property OnDsrChange:TNotifyEvent write FOnDsrChange;
property OnErr:TNotifyEvent write FOnErr;
property OnRing:TNotifyEvent write FOnRing;
property OnTxEmpty:TNotifyEvent write FOnTxEmpty;
★function Read(Buffer:Pointer;iCount:longint):longint;
//读取
★procedure Write(Buf :PChar;l:longint=0);
//输出
★procedure Open;
★procedure Close;
procedure Execute;override;

constructor Create;
//类的创建过程
destructor Destroy;override;
//类的析构函数

类主要功能实现的方法:
1、先继承 TThread 类的 Execute 方法。在 TThread 中 Execute 是一个抽象过程,
定义为 virtual ; abstract 是个抽象虚过程,需要在子类中实现的。
2、从 TThread 中继承到的 Execute 实际上是线程的主体部份。需要把 WaitCommEvent
放到线程体中,线程体是一个循环结构。直到线程被消毁。但如果端口没有打开时,
则不能执行 WaitCommEvent 。结构如下:
while not Terminated do
begin
if not m_PortOpen then countinue; //循环等待端口被打开
WaitCommEvent(m_hCom,EvtMask,nil); //等待事件发生

if (EvtMask and EV_RXCHAR) > 0 then //是不是EV_RXCHAR事件
begin
if Assigned(FOnReceive) then FOnReceive(Self);
end;
.
.
.
end; //end while
3、在类创建时挂起线程, 因为在线程体Execute中,如果没有打开端口不能执行 WaitCommEvent 。
部份代码如下:
m_PortOpen := false;
inherited Create(true); //开始执行线程,而不是Suspend
.
.
.
4、在打开端口的过程中先用CreateFile打开端口,(我使用同步方式,没有用Overlapped)
再用SetCommState设定端口工作方式,再用SetCommTimeouts设定超时结构,主要是
读超时的设置。在用SetCommMask设置事件掩码。最后才把m_PortOpen 设为True;
这时线程将执行 WaitCommEvent 等待事件发生。
5、在关闭端口时,先设置 m_PortOpen :=False 再用 SetCommMask(m_hCom,0)指令让线程
从 WaitCommEvent 处返回。此处很重要。这样线程才能退出 WaitCommEvent 。不然线
程很容易丢失。
6、在类被消毁时要处理的事:也就是写在 Destroy 过程中的代码。
先判断端口是否是开启状态。如果是。先消毁线程,再用 SetCommMask(m_hCom,0);
这句很重要不然线程永远停在 WaitCommEvent 处。如果端口是关闭了的。直接销毁
线程就可以了。
代码如下:
Terminate;

if m_PortOpen then
begin
SetCommMask(m_hCom,0); //让 线程中的 WaitCommEvent 返回
CloseHandle(m_hCom);
end ;
inherited;

主要代码如下:
//★★★★★★创建时:
constructor TRedCom.Create;
begin
m_PortOpen := false;
inherited Create(true); //挂起线程
m_CommPort := 1;
....
end;


//线程体 ★★★★★★ ( 同步I/O )
procedure TRedCom.Execute;
var
EvtMask:DWORD;
n:DWORD;
bRtn:Bool;
ErrCode:DWORD;
begin
while (not Terminated ) do //线程未被销毁
begin
if not m_PortOpen then continue;

EvtMask :=0;
bRtn:=WaitCommEvent(m_hCom,EvtMask,nil);

if not bRtn then //出现错误
begin
Raise Exception.Create(SysErrorMessage(GetLastError));
end;

if (EvtMask and EV_RXCHAR) >0 then
begin
if Assigned(FOnReceive) then FOnReceive(Self);
end;

if (EvtMask and EV_CTS) >0 then
begin
if Assigned(FOnCtsChange) then FOnCtsChange(Self);
end;
.....
end; // end while
end;


//★★★★★★类销毁时
destructor TRedCom.Destroy;
begin
if Suspended = true then //如果线程挂起时,唤醒它
Resume;

Terminate; // 销毁线程

if m_PortOpen then // 如果端口已打开
begin
SetCommMask(m_hCom,0); // 让线程从 WaitCommEvent 处返回
CloseHandle(m_hCom); // 关闭端口
end ;

WaitFor; // 等待线程结束。这句很重要,不然程序运行不稳定

inherited;
end;


// ★★★★★★打开端口 (同步方式)
procedure TRedCom.Open;
var
sCom:String;
bResult :Boolean;
EvtMask:longword;
begin

if m_PortOpen then Exit;
if not ( m_CommPort in [1..4] ) then Exit;

sCom:='COM'+ IntToStr(m_CommPort);

m_hCom := CreateFile(pChar(sCom),
GENERIC_READ or GENERIC_WRITE ,
0,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL ,
0);

if m_hCom = INVALID_HANDLE_VALUE then
begin
Raise Exception.Create('打开端口时出错!'+
SysErrorMessage(GetLastError));
m_PortOpen := false;
Exit;
end;

SetupParams; //设置参数 自写函数

EvtMask := EvtMask or EV_RXCHAR
or EV_CTS or EV_DSR
or EV_ERR or EV_RING
or EV_RLSD or EV_TXEMPTY;

SetCommMask(m_hCom,EvtMask);

m_PortOpen := true;
Resume; // 唤醒线程,进行监听

end;

//关闭端口
procedure TRedCom.Close;
var
bResult :Bool;
begin
if m_PortOpen = false then Exit;

m_PortOpen := false;

SetCommMask(m_hCom,0);
Suspend; // 挂起线程

CloseHandle(m_hCom);

end;


//读取端口数据
function TRedCom.Read(Buffer:Pointer; iCount: Integer): longint;
var
nReaded:longword;
bRtn:bool;
ovRead:DWORD;
ErrCode :DWORD;
begin
if not m_PortOpen then
begin
Result := 0;
Exit;
end;

bRtn := ReadFile(m_hCom,
Buffer^, // 注意这里的格式
iCount,
nReaded,
nil);

if not bRtn then
begin
Raise Exception.Create('ReadFile Error!'+ SysErrorMessage(GetLastError));
Exit;
end;

Result:= nReaded;
end;


//写端口数据
procedure TRedCom.OutPort(Buf: PChar;l:longint=0);
var
len:Longword;
bRtn:Boolean;
n:DWORD;
ovWrite:DWORD;
ErrCode :DWORD;
begin
if l = 0 then
begin
len := length(buf);
if len = 0 then exit;
end
else begin
len := l;
end;

bRtn := WriteFile(m_hCom,
(@Buf[0])^,
len,
n,
nil);

if not bRtn then
begin
Raise Exception.Create('写端口时出现错误!'+SysErrorMessage(GetLastError));
Exit;
end;

end;

现在,这个类的框架都在这里了,你把它完善一下,就可以了。
还有上次我给你的那个 TComPort 控件是个好东东,多看一下它的源码。会对你有帮助的。
Good Luck!

//--------------------------end of file --------------------------------------

 
后退
顶部