一串口通讯程序,在2K中运行正常,而在XP中运行不太良好!(API函数支持问题?) (100分)

  • 主题发起人 主题发起人 best999
  • 开始时间 开始时间
B

best999

Unregistered / Unconfirmed
GUEST, unregistred user!
这个程序是通过串口向外部设备发送字节数据,然后外部设备返回数据给PC,通讯过程大概就是这样的。
现在碰到一个问题的是:此程序在Win2K中运行良好,而在XP中偶尔会出现通讯中断的现象(经过多次测试得出此结论)。
开发环境是:Win2K+Delphi6的环境。用到了一个Com通讯控件,控件的源码中用到了如下API函数:
在打开通讯口时:CreateFile
送出数据时:WriteFile
读取数据时:ReadFile
我想知道的是:是不是API函数对XP支持不太好才导致程序在XP中运行出现错误?因为我是在2K环境下开发的。
如果是的话,怎么样解决?谢谢!!!
 
是什么控件?

因为所有的串口控件都是用这几个 API 的。
通讯中断是什么样子的?

就一般情况而言,这几个API 在2k 与 XP 下的执行是一样的。
 
应该是createfile函数的参数的问题。我们公司的程序也是这样的,以前在98下很好,后来发现在nt和2000下有时候没有通信,有时候一下子把前面好几分钟的数据送过来了。后来改了一下createfile函数的参数就可以了。不过具体怎么改我不太清楚,因为那个程序是用c++builder做的。你可以找本delphi串口通信的书看看
 
底层通讯就用到三个API:
在打开通讯口时:CreateFile
送出数据时:WriteFile
读取数据时:ReadFile
 
首先,你采用的通讯方式是查询方法,和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封装在自己的组件内。
 
还没有解决!
因程序特殊性,不能用异步方式.
 
说具体点
 
问题点: 在2K下运行正常的串口通讯程序,到XP下运行偶尔出现类似通讯阻塞("死机")的现象。
开发环境是:Win2K+Delphi6;
PC向下机发送二进制字节,下机返回相同的内容到PC,用到了如下API函数:
在打开通讯口时:CreateFile
送出数据时:WriteFile
读取数据时:ReadFile

部分代码:
//打开通讯口
procedure TTestCom.OpenComm;
var
hNewCommFile: THandle;
ComStr:String;
begin
ComStr:='COM' + IntToStr(1+ord(FCommPort));
hNewCommFile := CreateFile( PChar(ComStr),
GENERIC_READ or GENERIC_WRITE,
0,
nil,
OPEN_EXISTING,
0,
0);
if hNewCommFile = INVALID_HANDLE_VALUE then
raise ECommError.Create( 'Error opening serial port' );
if not SetupComm( hNewCommFile, INPUTBUFFERSIZE, INPUTBUFFERSIZE ) then
begin
CloseHandle( hComm );
raise ECommError.Create( 'Cannot setup comm buffer' );
end;
hComm := hNewCommFile;
// 清除湲冲区
PurgeComm( hComm, PURGE_TXABORT or PURGE_RXABORT or
PURGE_TXCLEAR or PURGE_RXCLEAR ) ;
// 通信端口组态
_SetCommState;
FPortOpen:=True;
end;

//传送二进制的数据
function TTestCom.WriteByte(const ByteData: array of Byte ): Boolean;
var
lrc: LongWord;
i: Integer;
begin
if hComm=0 then
begin
MessageDlg('COM Port is not opened yet!', mtError, [mbOK], 0);
Result := False;
exit;
end;
// 送出数据
for i:=Low(ByteData) to High(ByteData) do
WriteFile(hComm,ByteData,1,lrc, nil);
Result := True;
end;

Procedure TTestCom.ReadProcess;
var
nBytesRead: DWORD;
dwCommError: DWORD;
CS: TCOMSTAT;
i,ReadLen: DWORD;
begin
ClearCommError(hComm,dwCommError,@CS); //取得状态
FCommError:=dwCommError; //错误数值
if cs.cbInQue <>0 then //若缓冲区有数据,则读取
begin
if InputLen=0 then //指定读取的数据数
ReadLen:=cs.cbInQue
else
ReadLen:=InputLen;
if cs.cbInQue > sizeof(szInputBuffer) then
PurgeComm(hComm, PURGE_RXCLEAR) // 清除COM 数据
else
begin
//读取数据
if ReadFile(hComm, szInputBuffer,ReadLen,nBytesRead,nil) then // 接收COM 的数据
begin
FInputData:=Copy(szInputBuffer,1,ReadLen);
SetLength(FInputByteData,ReadLen);
//将数据搬到数组中
for i:=0 to ReadLen-1 do
FInputByteData:=Byte(szInputBuffer);
end; //ReadFile Loop
end;//else Loop
end; //cs.binQue Loop
end;

//读取返回数据
procedure TTestCom.ReadByte(var bytebuff:array of Byte;var bufflength:dword);
var i,j:integer;
begin
if hComm=0 then
begin
ECommError.Create('COM Port is not opened yet!');
end;
ReadProcess;//执行读取函数
j:=low(bytebuff);
for i:=low(FInputByteData) to high(FInputByteData) do
begin
bytebuff[j]:=FInputByteData; j:=j+1;
if j>high(bytebuff) then Break;
end;
bufflength:=length(FInputByteData);//取得数据数组的最高索引值
end;

-->
我这里是用同步方式进行通讯的,后改用异步通讯方式,连通讯都不成功!
hNewCommFile := CreateFile( PChar(ComStr),
GENERIC_READ or GENERIC_WRITE,
0,
nil,
OPEN_EXISTING,
0,//改为File_Flag_OverLapped则是异步方式(当然ReadFile,WriteFile也相应改为异步方式)
0);

程序不能用异步方式,因为PC发送一内容到下机后,要等到下机返回相同的内容到PC,PC比较内容一致后,才发送第二内容。
 
尝试用MOXA提供的PComm.dll进行开发,MOXA是专业做串口的,这个动态链接库还是很好用的,至少我在98,2K,XP上都用的还不错,你可以试试。
 
1.可以用成熟的控件
2。采用延时或事件触发
 
谢谢各位,问题找出来了。结贴!
 

Similar threads

S
回复
0
查看
1K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
896
SUNSTONE的Delphi笔记
S
后退
顶部