http://download.csdn.net/download/supermantm/5977685
Unit: EzCommPortz.pas
简易高效的串行口驱动单元功能简介:本单元定义了一个串行口基础类 TCommPortz, 可完成对标准UART串行口的底层功能, 包括发送数据,接收数据,设置输出线(DTR,RTS)电平,读取输入线(CTS,DSR,RING)电平,并分别定义相关事件的虚拟方法引发后续的数据处理因为 TCommPortz 是基础的串行口功能对象, 因此从 TObject 继承以达到高效节约资源的目的使用方法:所有的 TCommPortz 实例(包括其子类的实例)共享一个全局的消息窗口(通常为应用程序的主窗口)和一个自定义窗口消息, 用作线程协同, 具体实现如下:每个TCommPortz 实例都包含两个可能的线程分别监视对串行口的读写操作(可通过 EnableRx, UseTxQue属性决定是否创建使用多线程), 线程中捕获事件后, 向指定窗口发送特定的消息(添加到主线程的消息队列)再由主线程调用全局的TCommPortz.HandleMessage 类方法进行分发, 因此, 应用程序通常包含以下段落:const
  CM_COMMS = WM_USER + ??; // 定义串行口通知消息
type
  TMainForm = class(TForm)
  private
    // 串行口消息声明
    procedure DoCmComms(var M: TMessage); message CM_COMMS; 
  end;implementation// 定义自己的串行口
type
  TMyComm = class(TCommPortz)
  protected
    // 处理数据接收(如不需要接收可不重载该方法)
    procedure RxData(nSize: Integer; var Data: TByteArray); override;
    // 处理发送数据(如对发送后不管则可不重载该方法)
    procedure TxData(TxFrmId: Integer; pData: PTxFrmBuf; TxFailed: Boolean); override;
    // 处理输入线电平变化(如串行口只作为数据收发可不重载该方法)
    procedure LineChanged; override;
    // 出错处理(通常可不重载该方法)
    procedure ErrorHandle(ErrCode: Integer); override;
  end;// 主窗口创建后需要对串行口驱动进行初始化:
procedure TMainForm.CreateForm(Sender: TObject);
begin
  // 初始化: 设定消息窗口(为主窗口)和消息号, 全局接收缓冲区设置为4K字节
  TCommPortz.Initialize(Handle, CM_COMMS, 4096);
  MyComm:= TMyComm.Create(Ini.ReadInt(...)); // 创建串行口实例并快速初始化参数(注)
  with MyComm do begin // 逐项配置参数
    PortNo:= 1;        // 串行口号(COM1-COM255)
    Baud:= 1200;       // 波特率(600-115200)
    EnableRx:= True;   // 允许接收和状态捕获(只做发送的串行口不需要)
    UseTxQue:= True;   // 允许采用多线程队列方式发送
                       //   (False则采用直接推送串行口,程序自己考虑发送间隙)
    TxBuffSize:= 8192; // 发送队列缓冲区大小(UseTxQue = False则忽略该参数)
  end;
  MyComm.Open; // 打开串行口
  if MyComm.Connected then Caption:= 'OK'; // 打开成功!
end;// 消息中转:
procedure TMainForm.DoCmComms(var M: TMessage);
begin
  TCommPortz.HandleMessage(M.WParam, M.LParam);
end;
 
// 随后分别实现 数据接收, 数据发送汇报, 口线电平变化, 出错 等通知事件
...由于系统可同时操作多个串行口, TCommPortz 提供 Ports(Index: Integer) 函数提供对每一个实例的访问, 实例中可以通过 MyComm.PortIndex 属性取得自己的端口序号TCommPortz.Create 创建过程带有一个整数参数, 可通过该参数快速初始化该串行口的参数(串行口号,波特率,允许状态监视,允许多线程发送及发送队列缓冲区大小), 该参数可通过 MyComm.GetIntSettings 函数取得, 这通过此特性简单地实现把串行口的配置写入INI文件或者系统注册表, 此参数是一个24位的整数(0 -- 16777215), 应用程序也可以单独通过各个属性的方法进行参数配置属性说明:
~~~~~~~~
PortIndex: Byte
  串行口的内部序号, 创建时自动生成, 可通过 TCommPortz.Ports(PortIndex) 来访问这个串行口
  该属性只读PortNo: Byte
  串行口号, COM1--COM255
  串行口打开后不能写该属性(无效)Baud: Integer
  波特率, 可设置成 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 33600, 38400, 57600, 115200 中的一个, 非法的波特率设置不会写入
  串行口打开后不能写该属性(无效)TxQueSize: Integer
  发送队列缓冲区大小, 此参数在 UseTxQue=True 时有效, 此参数定义域为 256 -- 8192, 以256字节对齐, 也就是说, 设置TxQueSize:= 1 等效于 TxQueSize:= 256
  串行口打开后不能写该属性(无效)EnableRx: Boolean
  允许状态捕获线程, EnableRx=False 导致串行口不创建读线程, 因此也不能接收数据, 不能获取输入线电平状态
  只作为发送功能的串行口可设置 EnableRx:= False 以节省资源
  串行口打开后不能写该属性(无效)UseTxQue: Boolean
  允许创建队列发送线程, UseTxQue = False 时, TxQueSize 属性无效, 此时发送数据不保证会发生重叠操作(数据报文太长, 缓冲区中数据未排空时加载发送新的数据导致不可靠)
  如果应用中明确可预见不发生重叠操作(如通过一个定时器来触发发送而定时器的间隙保证足够数据发出), 可设置 UseTxQue:= False 以节省资源
  串行口打开后不能写该属性(无效)Connected: Boolean
  串行口被成功打开后 Connected 为 True, 关闭后为 FalseTxEmpty: Boolean
  发送状态空闲指示, 串行口未打开时此属性总是 False, Connected 后, UseTxQue=False 时, 程序会(应)通过此属性确保在空闲时调用发送方法以保证不发生重叠操作CTSLevel, DSRLevel, RingLevel, DTRLevel, RTSLevel
  端口线电平, True代表高电平(RS232电平为+12V), False代表低电平(RS232电平为-12V), 其中 CTS/DSR/RING 线为只读, DTR/RTS 线为读写属性, 可输出Levels[1..9]: Boolean
  以RS232标准DB9插座的线序号取得端口线电平, 其中
  Levels[1]..Levels[3]: 外壳地/RX/TX: 读取恒为False, 写入无效
  Levels[4] 为 DTR 线: 可读写
  Levels[5] 为 GND 线: 读取恒为False, 写入无效
  Levels[6] 为 DSR 线: 只读
  Levels[7] 为 RTS 线: 可读写
  Levels[8] 为 CTS 线: 只读
  Levels[9] 为 RING 线: 只读方法说明:
~~~~~~~~
function GetTxBuff(nSize: Integer): PTxFrmBuf
  分配一个缓冲区用于发送, 当 UseTxQue=False 时该方法无效(返回nil), UseTxQue=True 时返回一个指向发送缓冲区结构的指针, TTxFrmBuf 结构定义如下:
  TTxFrmBuf = packed record
    Size: Integer;
    Data: array [0..255] of Byte;
  end;
  PTxFrmBuf = ^TTxFrmBuf;
  nSize 的取值不可大于 TxQueSize 的一半
  分配的缓冲区不需要也不可以释放(FreeMem)!, 该段内存存在于一个环形的缓冲区中, 系统自动重新进行分配function SendData(pData: PTxFrmBuf): Integer
  发送数据, pData为上述的指针(设置好Size), pData 并不必须通过 GetTxBuff 取得, 调用者可以自行管理内存, 但必须注意, 当 UseTxQue=True 时, 发送不一定立刻被加载, 因此调用者必须确保该数据持续有效
  当 UseTxQue=False 时, 串行口对象不创建发送队列线程, 数据立刻被加载发送, 因此这段数据可以是过程内部的变量(在堆栈中分配)procedure Open;
  打开串行口procedure Close;
  关闭串行口function GetIntSettings: Integer;
  取得串行口的配置参数, 该参数可以在创建串行口实例时直接配置protected 部分:
~~~~~~~~~~~~~~
function Openned: Boolean
  指示串行口文件是否已经打开
  该属性与Connected的区别是 Connected = Openned and (not WantClose), 因为串行口采用了多线程操作, 在关闭串行口时需要等待线程退出拆构, 此时 Connected = False 而 Openned = Truefunction zThreadsFinished: Boolean
  指示串行口对象的读写线程都已停止
  子类可根据此值指示进行自己的中止拆构程序将由子类重载实现的处理:
~~~~~~~~~~~~~~~~~~~~~~
procedure RxData(nSize: Integer; var Data: TByteArray); virtual;
  串行口接收到数据时触发该过程, nSize 为本次数据字节数, Data 为数据, 注意, 串行口不保证数据的帧处理, 在不同性能的机器和串行口硬件下每次接收到的数据字节数并不一样也不可假定, 用户程序应启用自己的数据帧的处理
  EnableRx = False 时此过程不会被触发procedure TxData(TxFrmId: Integer; pData: PTxFrmBuf; TxFailed: Boolean); virtual;
  数据发送结果通知, TxFrmId 为发送帧序号, 该序号等同于该数据在 SendData 函数的返回值, 用户程序可通过此序号跟踪发送的数据, TxFailed 指示发送是否失败, 如果SendData的参数不是通过GetTxBuff函数分配, pData 指向发送数据(SendData 函数的引入值), 否则, pData = nil
  UseTxQue=False 时此过程不会被触发
  应用程序可重载该方法以跟踪发送过程procedure LineChanged; virtual;
  输入端口线电平变化通知, 当串行口 Connected 后 CTS/DSR/RING 线电平发生变化时该过程被触发
  EnableRx = False 时此过程不会被触发procedure ErrorHandle(ErrCode: Integer); virtual;
  串行口发生错误时(包括发送或接收)此过程被触发系统初始化参数说明:
~~~~~~~~~~~~~~~~~~
TCommPortz.Initialize(WindowsHandle, MessageNumber, RxRingBufferSize);
  WindowsHandle 为消息中转窗口的 Handle
  MessageNumber 为消息号
  RxRingBufferSize 为全局接收环型缓冲区的配置大小:
  每一个 TCommPortz (含子类)实例在接收到数据时将从全局缓冲区中分配内存并把数据读进此段内存再通过RxData过程提交, 全局接收缓冲区采用环形重复分配方法以避免全局内存碎片化, 通常把该参数设置在4K以上通常足够, 当系统同时打开多个串行口且数据量较大速率较高的话, 这个数值应设置得更大一些, 但通常不要大于16K(16384)
  RxRingBufferSize 以256字节为单位分配, 最小为256字节, 最大为 32768 字节多线程对象应用