首先,你采用的通讯方式是查询方法,和windowsIO通讯。
由于当你和一个外部单片机进行串口数据交换时,就会发生以上现象。
首先,单片机内部的程序都是采用了中断查询方式进行通讯(一般C51的高手都采用这种方式)。
比如一个数据帧50个字节,是动态的每次发送5个,10个,2个字节(或更多、更少)发送的,分多次发送完毕。
而当你用查询方式也就是普通的Createfile打开WindowsIO时,就会出现了以上你所描述的问题。
普通Createfile的方式
result := CreateFile(
PChar(ComNames),
GENERIC_READ or GENERIC_WRITE,
0,
nil,
OPEN_EXISTING,
0,//FILE_FLAG_OVERLAPPED, 关键就在这里
0);
采用查询的方式,由于Windows95,Windows98,Windows2000(XP)的工作方式及返回数据的时间都不是同种方式,所以造成在98下工作正常的程序在2000(XP)下工作就不正常了。顺便说一句,加延时根本无法解决问题。
然而,当你想让自己的数据在各种windows操作系统里都采取同样的方式收发数据,就需要采用异步的方式,FILE_FLAG_OVERLAPPED;这种方式就相当于单片机的中断查询方式;当PC和单片机都采用同种工作方式工作,数据传输自然就安全了。
当你采用FILE_FLAG_OVERLAPPED方式打开串口IO之后,就要有更多的事情需要打理清楚。
1首先,对于windows消息机制要了解清楚。
这里有两种消息,1你的数据的自定义消息;2windows内部的消息;两者之间的处理是FILE_FLAG_OVERLAPPED工作方式的关键之处。
下面有一个简单的流程你可以参考一下。
定义一个全局的 Read_os: Toverlapped;结构
让串口打开后就一直处于接收状态,当你发送数据时再改为发送状态,当发送完毕继续保持接收状态;
1hNewCommFile := CreateFile(PChar(ComNames),
Generic_read or Generic_WRITE,0,nil,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
然后,SetCommMask(Thandle: Cardinal,EV_RXCHAR); //设置EV_RXCHAR事件
然后,就可以建立Read_OS这个状态结构了
if read_Os.hEvent = 0 then
begin
fillchar(Read_OS,sizeof(REad_OS),0);
self.ListBox1.Items.Add('格式化 REad_OS');
read_OS.Offset := 0;
read_OS.OffsetHigh := 0;
Read_OS.hEvent := CreateEvent(nil,true,false,nil);
self.ListBox1.Items.Add('建立 Read_OS.hEvent');
if read_OS.hEvent = null then
begin
closeHandle(hnewCommFile);
closehandle(Read_OS.hEvent);
Messagebox(0,'CreateEvent read error','',MB_OK);
exit;
end;
Com_Thread := CreateThread(nil,0,@CommWatch,nil,0,ThreadID);
self.ListBox1.Items.Add('建立读取线程 '+ inttostr(Com_Thread));
if (Com_Thread = 0 ) then
messagebox(handle,'CreateThread 函数不起作用','',MB_OK);
EscapeCommFunction(hNewCommFile,SetDTR);
self.StatusBar1.Panels[0].Text := '正在接受数据';
end;
end;
注意上文中的,@CommWatch,这是一个本地消息指针的特殊用法,并不符合面向对象的要求,虽然它完成了我的意图,这里可以有各种线程的控制方法,我采用的是互斥方法。
procedure CommWatch(ptr: Pointer); stdcall;
var
dwEvtMask,dwTranser: Dword;
Ok: boolean;
Os: Toverlapped;
begin
Receive := true;
Fillchar(os,sizeof(os),0);
os.hEvent := CreateEvent(nil,true,false,nil);
assert((os.hEvent <> null),'OS.event create error');
assert((receive <> not receive),'receive := false');
while(receive) do
begin
dwEvtMask := 0;
if not WaitCommevent(hNewCommFile,dwEvtMask,@os) then
begin
if ERROR_IO_PENDING = GetLastError then
GetOverlappedResult(hNewcommFile,os,dwTranser,true);
end;
if ((dwEvtMask and EV_Rxchar) = EV_Rxchar) then
begin
WaitForSingleObject(Post_Event,INFINITE); // INFINITE 无限制的等待
ResetEvent(Post_Event);
OK := PostMessage(form1.Handle,WM_COMMNOTIFY,hNewCommFile,0);
assert(( ok), 'PostMessage Error');
end;
end;
closeHandle(os.hEvent);
end;
这样就可以一直保持接收了,当你发送数据时
SetCommMask(hNewCommFile,ev_TXEMPTY);
self.ListBox1.Items.Add('设置串口事件 ev_TXEMPTY');
fillchar(Write_OS,sizeof(Write_OS),0);
self.ListBox1.Items.Add('格式化 Write_OS:Toverlapped, 用 0 ');
Write_OS.hEvent := CreateEvent(Nil,True,False,Nil);
self.ListBox1.Items.Add('建立 Write_OS:Toverlapped');
然后就可以Writefile了
发送是检查串口状态
ErrorFlag := GetLastError;
if ErrorFlag <> 0 then
begin
if ErrorFlag = ERROR_IO_PENDING then
begin
self.ListBox1.Items.Add('发送不成功,错误提示显示当前正在接收状态');
WaitForSingleObject(Write_OS.hEvent,infinite);
GetOVerlappedResult(hNewCommFile,Write_OS,dwNumberofBytesWritten,false);
self.ListBox1.Items.Add('等待查询 Write_OS');
end
当你发送完成之后记住要把串口事件改为SetCommMask(hNewCOmmfile,EV_RXCHAR);
以上就是一个简单的模型
这样当你把数据帧发个单片机后,单片机就用中断方式接收。
当单片机用中断方式发送,你也采用同样的机制接收,通讯上就不会有诸塞了。
具体你可以参看以下文章
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp
2你要把以上两者的消息机制,和你的数据接口,和WindowsAPI封装在自己的组件内。