在WIN32中的串口通讯(Delphi)

来源:互联网 发布:bbc中国纪录片知乎 编辑:程序博客网 时间:2024/05/05 08:43

在WIN32中的串口通讯(Delphi)
由在WIN32操作系统中禁止应用程序象DOS中那样直接访问计算机硬件,因此,无法象以前那样采用中断读写串口。但是在WIN32中我们可发采用两种方法访问串口:1、使用VB中的MSCOMM串口控件;2、采用API函数,本文主要介绍采用API函数实现串口通讯。
由于WM_COMMNOTIFY消息已被取消,故本文自定义了WM_COMMNOTIFY消息,在WIN32中几个常用的串口通信API函数如下:
CreateFile    打开串口
CloseHandle  关闭句柄,用于释放内存
SetComm     设置缓冲区大小
ReadFile     读串口
WriteFile     写串口
SetCommState  设置通信参数
GetCommState  获取通信参数
ClearCommError 清除串口错误并获取读、写缓冲区当前字节数
SetCommMask 设置串口屏蔽事件
PurgeComm  消除串口读写缓冲区的所有字符,用以终止悬而未决的读写操作。
PostMessage 发送消息
WaitCommEvent 等待串口事件发生

由于采用多线程操作串口,还用到以下API函数

CreateEvent  建立同步事件
ResetEvent   同步事件复位
SetEvent     同步事件置位
WaitForSingleObject 等待同步事件
GetOverLappedResult 获取并行操作的结果]
GetLastError 获取错误代码

由于WIN32取消了WM_COMMNOTIFY,所以必须自己创建,如果编写串口控件,那么应该在串口事件发生时即WaitCommEvent返回True时或GetLastError返回结果为Error_IO_Pending,且GetOverLappedResult返回True时触发读串口事件。比如:串口控件为Tcomm,那么在串口事件发生时调用Tcomm.RXChar方法,Tcomm.RXChar定义如下:
Procedure Tcomm.RXChar;
begin
if Assigned(FOnRXChar) then FonRxChar(self);
SetEvent(Post_Event);
End;

Delphi的强大和多线程支持,使得实现串口通信非常方便简单,首先,用CreateFile打开串口,其次,通过GetCommState获取串口参数,并用SetCommState设置串口参数,然后创建线程用以监视串口,此后就可以进行串口通信了,最后用Closehandle 释放所有资源(切记,以免死机)。
在这其中,使用到一个重要的结构DCB,其常用的一些定义如下:
BaudRate:波特率,可直接设为110、300、600、1200、2400、4800、9600、19200等值。
ByteBits:数据位长度,可高为4-8。
Parity:奇偶校验方式,0-4分别为无、偶、奇、空
StopBits:停止位长度,0,1,2分别为1、1.5、2位
其余详细说明请参考Win32.hlp、MSDN、以及Delphi5/Source/RTL/win/Windows.pas。
具体程序见如下,本程序用Delphi 5.0编制,在Windows98编译通过,并两台PC上可稳定运行,源程序绝对完整无删节,绝不象某人所说的可以运行却无发送部分,且无法接收数据。
如要调试本程序,请到163.com上下载一个串口程序来协助调试。
本人欢迎来信讨论,Email:Yanhsiaosan@Cmmail.com

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
Const
Wm_CommNotify=WM_User+12;
type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
  Procedure CommInitialize;
  Procedure MsgComm(Var Msg:Tmessage); Message WM_CommNotify;
  Function  WriteStr(const Str:String):Boolean;
    { Private declarations }
  public
    { Public declarations }
  end;
  TComm=Class(TThread)
  Protected
  Procedure Execute;override;
  end;

var
  Form1: TForm1;
  Hcom,Post_Event:Thandle;
  LpolW,LpolR:Poverlapped;
  RXComm:TComm;
implementation

{$R *.DFM}

Procedure TComm.Execute;
var
dwEvtmask,dwOvres,bb:Dword;
RXFinish:Bool;
begin
  while true do
  begin
    DwEvtMask:=0;
    RXFinish:=WaitCommEvent(hcom,dwevtmask,LpolR);   //等待串口事件EV_RXCHAR
    if not RXFinish then               //如果返回True,已立即完成,否则继续判断
      if GetLastError()=ERROR_IO_PENDING then //正在接收数据
      begin
        bb:=WaitForSingleObject(LpolR^.hEvent,500);//等待500ms
        Case bb of
          Wait_Object_0:  RXFinish:=GetOverLappedResult(hcom,LpolR^,dwOvRes,False);
                           //返回False,出错
          Wait_TimeOut:  RXFinish:=False;//定时溢出
          else RXFinish:=False;   //出错
        end;
      end else RXFinish:=False;
    if RXFinish then
    begin
      if WaitForsingleobject(Post_Event,infinite)=Wait_Object_0 then  //等待同步事件置位
      begin
        resetEvent(Post_Event);      //同步事件复位
        PostMessage(Form1.handle,WM_CommNotify,0,0);  //发送消息
//在这里可以触发串口接收事件
      end;
    end;
  end;
end;

Procedure TForm1.CommInitialize;
Var
Lpdcb:TDCB;
begin
  hcom:=createFile('com1',   //串口名,可为com1-com4
                 generic_read or Generic_write,//访问模式
                 0,           //共享模式,必须为0
                 nil,           //安全属性指针
                    open_existing,   ///找开方式必须为open_existing
                    File_Flag_Overlapped,//文件属性,本文设为交迭标志
                    0);                 //临时文件句柄,必须为0  
  if hcom<>invalid_Handle_Value then
  begin
     SetupComm(hcom,4096,4096);          //设置缓冲区长度
     getCommState(hcom,lpdcb);           //设置串口
     lpdcb.baudrate:=9600;
     lpdcb.stopbits:=0;
     lpdcb.bytesize:=8;
     lpdcb.parity:=0;
     setCommState(hcom,lpdcb);
     SetCommMask(Hcom,ev_Rxchar);         //设置串口事件屏蔽
  end else showMessage('无法打开串口!');
end;

Function TForm1.WriteStr(const Str:String):Boolean;            //发送数据
var
DwCharsWritten,DwRes:Dword;
S_DATA:String;
BRes:boolean;
Begin
  BRes:=False;
  S_Data:=Str;
  if hcom<>INVALID_HANDLE_VALUE then
  begin
    DwCharsWritten:=0;
    BRes:=WriteFile(Hcom,PChar(S_Data)^,Length(S_Data),
              DwCharsWritten,LpolW);     //返回True,数据立即发送完成
    if not BRes then
    begin
     if GetLastError()=Error_IO_Pending then
       begin   //正在发送数据
          DwRes:=WaitForSingleObject(LpolW^.hEvent,Infinite);
          if DwRes=Wait_Object_0 then  // 如果不相等,出错
             BRes:=GetOverLappedResult(hcom,LpolW^,DwCharsWritten,False)  //返回False,出错
          else BRes:=true;   //数据发送完成
       end;
    end;
  end;
  Result:=Bres;
end;

Procedure TForm1.MsgComm(Var Msg:Tmessage);      //接收数据
var
 clear:boolean;
 coms:TComStat;
 cbNum,Cbread,lpErrors:Dword;
 s:string;
begin
 clear:=clearCommerror(hcom,lperrors,@Coms);
 if clear then
 begin
   cbnum:=Coms.cbInQue;    //获取接收缓冲区待接收字节数
   setlength(s,cbnum+1);     //分配内存
   ReadFile(hcom,PChar(S)^,cbnum,Cbread,LpolR);   //读串口
   setlength(s,cbread);      //分配
   SetEvent(Post_Event);     //同步事件置位
   Memo1.Lines.Add(S);
 end;
end;


procedure TForm1.Button1Click(Sender: TObject);  //发送HEX码EB90EB90
Var
S_DATA:String;
begin
  S_Data:=Chr($eb)+Chr($90)+Chr($eb)+Chr($90);
  If  not WriteStr(S_Data) then  ShowMessage('无法发送数据')
  else ShowMessage('发送成功');
end;

procedure TForm1.FormDestroy(Sender: TObject);   //释放内存
begin
 CloseHandle(LpolW^.hEvent);
 CloseHandle(LpolR^.hEvent);
 dispose(lpolW);
 dispose(lpolR);
 LpolW:=Nil;
 LpolR:=Nil;
 RXComm.Terminate;
 SetEvent(Post_Event);
 CloseHandle(Post_Event);
 CloseHandle(hcom);
end;

procedure TForm1.FormCreate(Sender: TObject);    //初始化内存及串口
begin
  Comminitialize;
  New(lpolW);
  New(lpolR);
  LpolW^.Internal:=0;
  LpolW^.InternalHigh:=0;
  LpolW^.Offset:=0;
  LpolW^.OffsetHigh:=0;
  LpolW^.hEvent:=Createevent(nil,true,False,nil);
  Lpolr^.Internal:=0;
  Lpolr^.InternalHigh:=0;
  Lpolr^.Offset:=0;
  Lpolr^.OffsetHigh:=0;
  Lpolr^.hEvent:=Createevent(nil,true,False,nil);
  PurgeComm(Hcom,Purge_TxAbort or Purge_RxAbort or Purge_Txclear or Purge_Rxclear);
  Post_Event:=Createevent(nil,true,true,nil);
  RXComm:=Tcomm.Create(false);
end;

end.