用WIN32API函数实现Windows中断方式下的串行通讯

用WIN32API函数实现Windows下的串行通讯
以往的DOS系统是通过DOS中断和BIOS中断向用户提供串行接口的通讯能力。在Windows环
境下,C++的开发工具既没有提供象DOS和BIOS中那样专门的串行通讯控制方法,也不允
许用户直接控制串口的中断。
为了保证资源共享,Windows系统完全接管了各种硬件资源,使用中断来控制端口将破坏
系统的多任务性,使系统的稳定性受到影响。但Windows同时也提供了功能强大的API函
数使用户能间接的控制串行通讯。
1、实现串行通讯的相关API函数
API函数不仅提供了打开和读写通讯端口的操作方法,还提供了名目繁多的函数以支持对
串行通讯的各种操作。常用函数及作用如表5-1所示。
表5-1 常用串行通讯API函数及其作用
      函数名                    作用
CreateFile              打开串口
GetCommState         检测串口设置
SetCommState         设置串口
BuilderCommDCB 用字符串中的值来填充设备控制块
GetCommTimeouts     检测通信超时设置
SetCommTimeouts     设置通信超时参数
SetCommMask 设定被监控事件
WaitCommEvent       等待被监控事件发生
WaitForMultipleObjects 监测多个对象
WriteFile              发送数据
ReadFile               接收数据
GetOverlappedResult     返回最后重叠操作结果
PurgeComm 清空串口缓冲区,退出所有相关操作
ClearCommError 更新串口状态结构体,并清除所有串口硬件错误
CloseHandle            关闭串行口
2、打开端口
函数CreateFile原本用于打开文件,但它同样可用于打开一个通信端口。与系统中其他
对象一样,通信端口也是用句柄来标识的。CreateFile函数返回被操作的通信端口句柄
,其调用方法如下:
HANDLE CreateFile (
LPCTSTR lpFileName, //指向文件名字符串的指针
DWORD dwDesireAccess,     //操作模式
DWORD dwShareMode,      //共享方式
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
//指向安全属性的指针
DWORD dwCreationDistribution,//文件建立方式
DWORD dwFlagsAndAttributes //文件属性
HANDLE hTemplateFile)      //模板文件句柄
lpFileName:指向一个以NULL结束的字符串,该串指定了要创建、打开或截断的文件、
管道、通信源、磁盘设备或控制台的名字。当用CreateFile打开串口时,这个参数可用
“COM1”指定串口1,用“COM2”指定串口2,依此类推。
dwDesireAccess: 指定对文件访问的类型,该参数可以为GENERIC_READ(指定对该文件
的读访问权)或ENERIC_WRITE(指定该文件的写访问权)两个值之一或同时为为这两个值
。用ENERIC_READ|GENERIC_WRITE则指定可对串口进行读写;
dwShareMode:指定此文件可以怎样被共享。因为串行口不支持任何共享模式,所以dwS
hareMode必须设为0;
lpSecurityAttributes定义安全属性,Win 9x下为NULL;
dwCreationDistribution定义文件创建方式, 对串口必须设为OPEN_EXISTING,表示打
开已经存在的文件;
dwFlagsAndAttributes为该文件指定定义文件属性和标志,这个程序中设为FILE_FLAG_
OVERLAPPED,表示异步通信方式;
hTemplateFile 指向一个模板文件的句柄,在 Windows 9x下为NULL。
用异步读写方式打开串口1的函数调用如下:
m_hComm = CreateFile(“COM1”, //打开串口1
   GENERIC_READ | GENERIC_WRITE,//读写方式
    0,   //不能共享
   NULL,   //不用安全结构
   OPEN_EXISTING, //打开已存在的设备
   FILE_FLAG_OVERLAPPED, //异步方式
   0);    //无模板
串口被成功打开时,返回其句柄,否则返回0XFFFFFFFF。
3、端口设置
第一次打开串口时,串口设置为系统默认值,函数GetCommState和SetCommState可用于
检索和设定端口设置的DCB(设备控制块)结构,该结构中BaudRate、ByteSize、StopBit
s和Parity字段含有串口波特率、数据位数、停止位和奇偶校验控制等信息。程序中可先
用GetCommState检索端口的当前设置,修改其中的部分字段后再用SetCommState进行端
口设定。这样可不必构造一个完整的DCB结构。
下面介绍几个主要的函数和结构体:
(1)GetCommState
BOOL GetCommState( hCommDev, lpdcb);
参数hCommDev标识通信设备,应使用CreateFile返回的句柄。Lpdcb是指向DCB结构的指
针,函数调用后当前串口配置信息将被保存在这个结构内。如果函数成功返回值为TRUE
;否则返回值为FALSE。SetCommState用法与GetCommState相似,在此不再重复。DCB结
构定义如下(只介绍主要的几项):
typedef struct _ DCB{
……
DWORD   BardRate;      //波特率的设置
BYTE     ByteSize;     //数据位的个数
BYTE     Parity;         //是否有奇偶校验位
BYTE     StopBits;       //停止位的个数
……
}DCB;
(2)SetCommTimeouts
BOOL SetCommTimeouts( hCommDev, lpctmo );
Lpctmo指向包含新的超时参数的COMMTIMEOUTS结构。COMMTIMEOUTS结构定义如下:
typedef struct _ COMMTIMEOUTS{
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutconstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutconstant;
}COMMTIMEOUTS, LPCOMMTIMEOUTS;
ReadIntervalTimeout: 以毫秒为单位指定通信线上两个字符到达之间的最大时间。在
ReadFile操作其间,收到第一个字符时开始计算时间。若任意两个字符到达之间的间隔
超过这个最大值,ReadFile操作完成,返回缓冲数据。0值表示不用间隔限时。若该成员
为MAXDWORD,且ReadTotalTimeoutconstant和ReadTotalTimeoutMultiplier成员为零,
则指出读操作要立即返回已接收到的字符,即使未收到字符,读操作也要返回。
ReadTotalTimeoutMultiplier:以毫秒为单位指定一个乘数,该乘数用来计算读操作的
总限时时间。每个读操作的总限时时间等于读操作所需的字节数与该值的乘积。
ReadTotalTimeoutConstant:以毫秒为单位指定一个常数,用于计算读操作的总限时时
间。每个操作的总限时时间等于ReadTotalTimeoutMultiplier成员乘以读操作所需字节
数再加上该值的和。ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant成员的
值为0表示读操作不使用限时时间。
WriteTotalTimeoutMultiplier和WriteTotalTimeoutconstant的意义和作用分别与Read
TotalTimeoutMultiplier和ReadTotalTimeoutConstant相似,不再重复。
(3)BuilderCommDCB
BOOL BuilderCommDCB(lpszDef,lpdcb)
LpszDef:指向一个以NULL结束的字符串,该字符串指定串口的控制信息。比如,“1200
,N,8,1”指定波特率为1200,无奇偶校验位,有8个数据位和1个停止位。
lpdcb:指向被填充的DCB结构。
(4)SetCommMask
BOOL SetCommMask(hCommDev,fdwEvtMask);
fdwEvtMask指向一个32位的屏蔽码,如果指定为EV_RXCHAR | EV_CTS,表示程序监控串
口的收、发事件。
下面以简单的例子说明串口设置的步骤:
m_CommTimeouts.ReadIntervalTimeout = 1000;
m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000;
m_CommTimeouts.ReadTotalTimeoutConstant = 1000;
m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000;
m_CommTimeouts.WriteTotalTimeoutConstant = 1000;
if (SetCommTimeouts(m_hComm, &m_CommTimeouts))
// 串口超时参数设置
if (SetCommMask(m_hComm, dwCommEvents))
// 设置串口事件掩码
   if (GetCommState(m_hComm, &m_dcb))
   // 获取串口当前状态
    if (BuildCommDCB(“1200,N,8,1”, &m_dcb))
    // 建立串口设备控制块
     if (SetCommState(m_hComm, &m_dcb));
     // 设置串口参数
……
以上任何一个if语句的判断条件为假时都将调用GetLastError函数获取错误信息,进行
错误处理。
4、读写串行口数据
Win32API函数ReadFile和WriteFile支持对串行口 的读写操作。这些函数的行为还受是
否使用异步I/O(Overlapped)及通信超时设置的影响。串行口读写的同步、异步方式是
在打开端口的同时给dwGlagsAndAttributes参数传入适当的值而设定的。
在同步方式下,调用ReadFile或WriteFile后,当实际读写操作完成或发生超时时才返回
调用程序。
而异步方式函数在启动接收或发送过程后立即返回,程序继续向下执行。程序在调用Re
adFile和WriteFile时必须提供一个Overlapped数据结构指针,该结构中包含一个手动的
事件同步对象,其后的程序必须借助于该事件同步对象,完成数据的接收和发送过程。

通信端口的超时设置对读写的处理方式也会产生影响,如果调用读写函数时发生端口超
时,则读写函数立即返回并返回已传输的数据字节数。
下面介绍主要的用于串口读写的函数:
(1)ReadFile 函数的调用方法如下:
BOOL ReadFile (
   HANDLE hFile, //用CreateFile 获得的文件句柄
   LPVOID lpBuffer, //输入缓冲区首址
   DWORD nNumberOfBytesToRead,//设定读入字节数
   LPDWORD lpNumberOfByteRead, //实际读入字节数
   LPOVERLAPPED lpOverlapped //重叠操作方式数据结构地址
   );
(2)WriteFile函数的调用方法如下:
BOOL WriteFile(
       HANDLE hFile, //用CreateFile 获得的文件句柄
       LPCVOID lpBuffer, //输出缓冲区首址
       DWORD nNumberOfBytesToWrite, //要求输出的字节数
       LPDWORD lpNumberOfBytesWritten,//实际输出字节数
       LPOVERLAPPED lpOverlapped): //重叠操作方式数据结构地址
(3)GetOverlappedResult函数调用方法如下:
BOOL GetOverlappedResult(
       HANDLE hFile, //用CreateFile 获得的文件句柄
       LPOVERLAPPED lpOverlapped,//指向一个在启动重叠操作时指定的
     OVERLAPPED结构(即读写函数中指定的OverLapped结构)
       LPDWORD lpNumberOfBytesTransferred,//实际传输的字节数
   BOOL bWait, //是否等待悬挂的重叠操作完成,若为
     //TRUE,则此函数直到操作完成后才返回。
     );
OVERLAPPED结构定义如下:
typedef struct _OVERLAPPED {
       DWORD Internal;
       DWORD InternalHigh;
       DWORD Offset;
       DWORD OffsetHigh;
       HANDLE hEvent;
   } OVERLAPPED;
如果采用异步方式,则在调用ReadFile或WriteFile函数时必需指定一个Overlapped结构
,调用后程序可继续执行其它操作,在合适的地方再调用函数GetOverlappedResult判断
异步重叠操作是否完成(判断OVERLAPPED结构中的hEvent是否被置位)。
(4)WaitCommEvent函数的调用法如下:
BOOL WaitCommEvent(
       HANDLE hCommDev, //串口句柄
       LPDWORD lpfdwEvtMask, //见SetCommEvent函数说明
       LPOVERLAPPED lpo, //重叠操作方式数据结构地址
      );
当由SetCommMask函数所指定的事件产生时这个函数将返回TRUE。
下面是一个串口读写的流程图。
图5-1 串口操作流程图
5、关闭串口
如果不再使用某一端口,须将该端口关闭,以便其他程序可以使用该端口。如果不显式
关闭某端口,当程序退出时打开的端口也将被自动关闭。但为了安全起见,最好是显式
的关闭它。关闭串口的语句为CloseHandle(hCommDev);其中hCommDev为用CreateFile建
立的串口句柄。
6、一个完整的例子
//串口控制类,基本用WINAPI实现,可以不用做修改的用在BCB程序中
//读数据采用事件驱动方式,用一个线程处理相应事件,收到的字符用消息法往父窗口
//由于我编的软件中写串口的数据不多,所以采用直接写的方式,而没用线程
//以下代码在BCB5/Win98下编译通过,在Win95/Win98/Win2000下运行良好
//--------------------------------------------------------------------------
-
头文件Com.h
//--------------------------------------------------------------------------
-
//--------------------------------------------------------------------------
-
//定义串口控制类
#ifndef ComH
#define ComH
//--------------------------------------------------------------------------
-
#define WM_COMM_RXCHAR WM_USER+1 //自定义消息:串口接收到一个字符
//--------------------------------------------------------------------------
-
class TSerialPort
{
public:
TSerialPort(); //构造函数
~TSerialPort(); //析构函数
//串口初始化,缺省参数为:COM1,19200Baud,无奇偶校验,8数据为,1停止位,监控读写事

bool InitPort(TForm* pPortOwner,unsigned uPortNo=1,unsigned uBaud=19200,
char cParity='N',unsigned uDataBits=8,unsigned uStopBits=1,
DWORD dwCommEvents=EV_RXCHAR);
void __fastcall StartMonitoring(); //启动串口监控线程
void __fastcall StopMonitoring(); //挂起口监控线程
void __fastcall WriteToPort(unsigned char ucTxData); //向串口写一个字符
private:
void __fastcall Clear();   //清除占用的资源
void __fastcall ProcessErrorMessage(char* ErrorText); //处理出错信息
static DWORD WINAPI CommThread(LPVOID lpvParam); //串口线程的实现函数
static void __fastcall ReceiveChar(TSerialPort* Port); //接收一个字符
bool WriteChar(unsigned char ucChar); //向串口写一个字符
HANDLE m_hComm;       //串口句柄
HANDLE m_hThread;     //串口操作线程
HANDLE m_hShutdownEvent; //关闭串口事件句柄
//事件数组,每个元素代表一个事件,每个串口线程监控两个事件,
//即读和关闭串口事件,读事件即重叠结果中的hEvent事件
HANDLE m_hEventArray[2];
OVERLAPPED m_OverLapped; //重叠结果
COMMTIMEOUTS m_CommTimeouts; //超时参数
DCB m_DCB;            //串口设备控制块
DWORD m_dwCommEvents; //串口要监控的事件
TForm* m_pOwner; //父窗口句柄
unsigned m_uPortNo; //串口号
bool m_bThreadAlive; //串口线程存在标志
};
//--------------------------------------------------------------------------
-
//全局变量
extern int g_iSerialPort;                 //端口号:COM1,COM2,COM3,COM4
void __fastcall ComError(AnsiString AppendMsg); //显示串口出错信息
//--------------------------------------------------------------------------
-
#endif
//--------------------------------------------------------------------------
-
//--------------------------------------------------------------------------
-
实现代码Com.cpp
//--------------------------------------------------------------------------
-
//--------------------------------------------------------------------------
-
//串口控制
//基本用WINAPI实现,采用事件响应方式,用一个线程处理各种消息
#include <vcl.h>
#pragma hdrstop
#include "stdio.h"
#include "Com.h"
#include "Macro.h"
#include "Progress.h"
//--------------------------------------------------------------------------
-
int g_iSerialPort;                         //端口号:COM1,COM2,COM3,COM4
//CRITICAL_SECTION m_csCommunicationSync;
//--------------------------------------------------------------------------
-
//构造函数,初始化,把各数据成员置0
TSerialPort::TSerialPort()
{
m_hComm =INVALID_HANDLE_VALUE;
m_hThread=NULL;
m_hShutdownEvent = NULL;
m_OverLapped.hEvent = NULL;
m_OverLapped.Offset = 0;
m_OverLapped.OffsetHigh = 0;
m_hEventArray[0]=NULL;
m_hEventArray[1]=NULL;
m_bThreadAlive = false;
m_pOwner=NULL;
m_uPortNo=1;
}
//----------------------------------------------------------------------
//析构函数
TSerialPort::~TSerialPort()
{
Clear(); //清除资源
}
//----------------------------------------------------------------------
//释放资源
void __fastcall TSerialPort::Clear()
{
if(m_hThread!=NULL)
{
//检查串口线程是否被挂起,如果是则恢复它
int iSuspendCount=ResumeThread(m_hThread);
while(iSuspendCount>1)
   iSuspendCount=ResumeThread(m_hThread);
int OldTime,NewTime; //用于判断超时
OldTime=GetTickCount(); //从WINDOWS启动到当前的毫秒值
//终止串口监控线程
while (m_bThreadAlive) //串口线程仍存在
{
   NewTime=GetTickCount();
   if((NewTime-OldTime)>3000)   //超时(3妙)强制终止串口线程
   {
    TerminateThread(m_hThread,200);
    mErrorMsg("串口线程异常终止!");
    break;
   }
   SetEvent(m_hShutdownEvent); //设置关闭串口事件
   SetEvent(m_OverLapped.hEvent);
}
m_bThreadAlive=false;
CloseHandle(m_hThread);
m_hThread=NULL;
}
//释放对象,如果句柄有效,则关闭
if (m_hComm!=INVALID_HANDLE_VALUE)
{
CloseHandle(m_hComm);
m_hComm=INVALID_HANDLE_VALUE;
}
if(m_hShutdownEvent!=NULL)
{
CloseHandle(m_hShutdownEvent);
m_hShutdownEvent=NULL;
}
if(m_OverLapped.hEvent!=NULL)
{
CloseHandle(m_OverLapped.hEvent);
m_OverLapped.hEvent=NULL;
}
}
//----------------------------------------------------------------------
//串口初始化,可用于串口1到4,参数意义如下:
//pPortOwner-父窗口,uPortNo-串口号,uBaud-波特率,cParity-奇偶校验,uDatabits-数
据位数,
//uStopbits-停止位数,dwCommEvents-需要监控的串口事件
bool TSerialPort::InitPort(TForm* pPortOwner,unsigned uPortNo,unsigned uBaud
,
char cParity,unsigned uDataBits,unsigned uStopBits,DWORD dwCommEvents)
{
char sTemp[100];
if(uPortNo<1||uPortNo>4)
{
sprintf(sTemp,"无法打开串口COM%d,串口号只能是COM1、COM2、COM3或COM4中的一个
.",
   uPortNo);
mErrorMsg(sTemp);
return false;
}
if(pPortOwner==NULL)
{
mErrorMsg("串口的父窗口无效,串口监控线程无法正常工作.");
return false;
}
Clear(); //清除没有被释放的资源
//创建新对象
m_OverLapped.hEvent = CreateEvent(NULL, //安全属性
    true,    //手工复位
    false,   //初始为无事件状态
    NULL); //事件名称
m_hShutdownEvent=CreateEvent(NULL, true, false, NULL);
//初始化事件数组
m_hEventArray[0] = m_hShutdownEvent; //关闭串口事件,优先级最高
m_hEventArray[1] = m_OverLapped.hEvent; //重叠结果中的事件
m_pOwner = pPortOwner;    //父窗口,即控制串口的对象
m_uPortNo =uPortNo;   //指定串口号
m_dwCommEvents = dwCommEvents; //串口要监控的事件
sprintf(sTemp,"COM%d",uPortNo); //合成串口号字符串
//打开串口,获取串口句柄
m_hComm = CreateFile(sTemp, //串口号
   GENERIC_READ|GENERIC_WRITE,//读写方式
   0,         //通讯设备必须以独占方式打开
   NULL,          //无安全属性
   OPEN_EXISTING,        //通讯设备已存在
   FILE_FLAG_OVERLAPPED, //异步I/O
   0);          //通讯设备不能用模板打开
if (m_hComm==INVALID_HANDLE_VALUE)    //句柄无效,打开串口失败
        {
sprintf(sTemp,"无法打开串口%d(COM%d),请检查串口是否正确安装,或是否被已经被
占用.",
   uPortNo,uPortNo);
mErrorMsg(sTemp);
return false;
        }
//设置超时参数,总时间=Multiplier*字符数+Constant
//Interval为读入的字符串中任意两个字符间的最大间隔
m_CommTimeouts.ReadIntervalTimeout=1000;
m_CommTimeouts.ReadTotalTimeoutMultiplier=1000;
m_CommTimeouts.ReadTotalTimeoutConstant=1000;
m_CommTimeouts.WriteTotalTimeoutMultiplier=1000;
m_CommTimeouts.WriteTotalTimeoutConstant=1000;
sprintf(sTemp,"baud=%d parity=%c data=%d stop=%d",uBaud,
cParity,uDataBits,uStopBits); //合成串口参数字符串
//配置串口
if (SetCommTimeouts(m_hComm,&m_CommTimeouts)) //超时参数
{
if (SetCommMask(m_hComm,m_dwCommEvents)) //需要监控的事件
{
   if (GetCommState(m_hComm,&m_DCB)) //获取原始DCB
   {
    //设置串口设备控制块(DCB)
    m_DCB.fRtsControl=RTS_CONTROL_ENABLE;
    if (BuildCommDCB(sTemp,&m_DCB))
    {
     if (!SetCommState(m_hComm,&m_DCB))
      ProcessErrorMessage("设置串口");
    }
    else
     ProcessErrorMessage("建立串口设备控制块");
   }
   else
       ProcessErrorMessage("获取串口状态");
}
else
   ProcessErrorMessage("设置串口事件掩码");
}
else
ProcessErrorMessage("串口超时参数设置");
//清空串口缓冲区,退出所有相关操作
PurgeComm(m_hComm,PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);

DWORD dwThreadId;
//创建串口监控线程
m_hThread=CreateThread( NULL, //无安全属性
   0, //用主线程的堆栈
   CommThread, //线程的执行函数
   this,        //传给线程执行函数的参数
   CREATE_SUSPENDED,//初始为挂起状态
   &dwThreadId);   //线程标识
if(m_hThread==NULL) //无法创建线程
{
ProcessErrorMessage("创建线程");
return false;
}
return true;
}
//----------------------------------------------------------------------
//启动串口监控线程
void __fastcall TSerialPort::StartMonitoring()
{
        int iSuspendCount=ResumeThread(m_hThread);
        if(iSuspendCount==-1)
         ProcessErrorMessage("启动串口监控线程");;
        while(iSuspendCount>1)
                iSuspendCount=ResumeThread(m_hThread);
}
//----------------------------------------------------------------------
//挂起线程
void __fastcall TSerialPort::StopMonitoring()
{
        int iSuspendCount=SuspendThread(m_hThread);
        if(iSuspendCount==-1)
         ProcessErrorMessage("挂起串口监控线程");
}
//----------------------------------------------------------------------
//串口事件处理线程
DWORD WINAPI TSerialPort::CommThread(LPVOID lpvParam)
{
//将void型的pParam转化为TSerialPort类型的指针
TSerialPort *Port = (TSerialPort*)lpvParam;
Port->m_bThreadAlive=true; //标志串口线程处于活动态
DWORD Event=0;
DWORD CommEvent=0;
DWORD dwError=0;
COMSTAT ComStat;        //串口状态
bool bResult;
if(Port->m_hComm==INVALID_HANDLE_VALUE) //串口句柄有效
{
mErrorMsg("串口句柄无效,串口监控线程无法工作.");
Port->m_bThreadAlive=false;
ExitThread(0);
}
PurgeComm(Port->m_hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|
   PURGE_RXABORT|PURGE_TXABORT);
//进入无限循环,直到关闭串口事件变为有信号.
while(1)
{
//调用WaitCommEvent().这一调用将立即返回,因为我用异步方式
//(FILE_FLAG_OVERLAPPED)打开串口,并且指定重叠操作结构,
//如果有一个字符到达串口,重叠结构中的事件将被置为有信号态
bResult=WaitCommEvent(Port->m_hComm,&Event,&Port->m_OverLapped);
if (!bResult)
{
   //如果WaitCommEvent()返回FALSE,调用GetLastError()判断原因
   switch (dwError=GetLastError())
   {
    case ERROR_IO_PENDING:      //重叠操作正在后台进行
    {
     //如果串口这时无字符,这是正常返回值,继续
     break;
    }
    case 87:
    {
     //在WIN NT下这是一个可能结果,但我没有找到
     //它出现的原因,我什么也不做,继续
     break;
    }
    default:
    {
     //所有其它错误代码均显示串口出错,显示出错信息
     Port->ProcessErrorMessage("等待串口事件");
     Port->m_bThreadAlive=false;
     ExitThread(0);
    }
   }
}
else
{
   //如果WaitCommEvent()返回true,检查输入缓冲区是否确实
   //有字节可读,若没有,则继续下一循环
   //ClearCommError()将更新串口状态结构并清除所有串口硬件错误
   ClearCommError(Port->m_hComm,&dwError,&ComStat);
   if (ComStat.cbInQue==0)   //输入缓冲队列长为0,无字符
    continue;
}
//以下为主等待函数,线程将被阻塞直到m_hEventArray中指定的某一
//事件产生时函数返回,返回值为这一事件的序号
Event=WaitForMultipleObjects(2, //等待的事件的个数
     Port->m_hEventArray, //事件数组
     false,   //任一事件产生时返回
     4000);   //等待4秒
//判断使等待终止的事件
switch (Event)
{
   case 0: //关闭串口事件,优先处理
   {
    Port->m_bThreadAlive = false; //串口活动标志
    ExitThread(100);        //退出线程
   }
   case 1: //读串口事件
   {
    //获取串口事件掩码
    GetCommMask(Port->m_hComm,&CommEvent);
    if (CommEvent & EV_RXCHAR) //收到一个字符
         ReceiveChar(Port);//读入一个字符
    break;
   }
}
}
}
//----------------------------------------------------------------------
//接收一个字符
void __fastcall TSerialPort::ReceiveChar(TSerialPort* Port)
{
COMSTAT ComStat;
DWORD dwError=0;
bool bRead=true;
bool bResult;
DWORD BytesRead=0;
unsigned char ucRxBuff;
//开始无限循环,因为我不知到需要循环多少次.我的解决方法是开始一个无限循环,
//当我已处理了所有的有效数据后才退出.使用这种方法应小心的保证您的程序能
//正常退出循环.即便如此,我仍觉得这是最有效的解决办法
while(1)
{
//ClearCommError()将更新串口状态结构并清除所有串口硬件错误
ClearCommError(Port->m_hComm,&dwError,&ComStat);
if (ComStat.cbInQue==0)
{
   //缓冲区中已无数据,退出循环
   break;
}
//读串口
bResult=ReadFile(Port->m_hComm, //串口句柄
    &ucRxBuff, //输入缓冲区地址
    1, //想读入的字符数
    &BytesRead, //实际被读入的字符数
    &Port->m_OverLapped); //重叠结构指针
//读串口失败,调用GetLastError()判断原因
if(!bResult)
{
   switch (dwError=GetLastError())
   {
    case ERROR_IO_PENDING: //重叠操作在后台进行
    {
     bRead=false;
     BytesRead=0;
     break;
    }
    default:
    {
     //其它情况说明出错
     Port->ProcessErrorMessage("读串口");
     return;
    }
   }
}
if (!bRead) //没有读到字符
{
   //等待重叠操作结果
   bResult=GetOverlappedResult(Port->m_hComm, //串口句柄
      &Port->m_OverLapped, //重叠结构
      &BytesRead, //实际读入字符数
      true);   //等待直到串口操作超时
   if (!bResult) //重叠操作失败
   {
    Port->ProcessErrorMessage("(读串口时)获取重叠操作结果");
    return;
   }
   bRead = true;
}
//将收到的字符当作消息的参数投递到父窗口
if(BytesRead!=1) //没有读入要求的字符数
   continue;
PostMessage((Port->m_pOwner)->Handle,WM_COMM_RXCHAR,
   (WPARAM)ucRxBuff,(LPARAM)Port->m_uPortNo);
}
}
//----------------------------------------------------------------------
//向串口写一个字符(内部实现)
bool TSerialPort::WriteChar(unsigned char ucChar)
{
bool bWrite=true;
bool bResult;
DWORD BytesSent=0;
OVERLAPPED ov_Write;
ov_Write.hEvent=CreateEvent(NULL, true, false, NULL);
//初始化重叠结构
ov_Write.Offset=0;
ov_Write.OffsetHigh=0;
PurgeComm(m_hComm,PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);

//写串口
bResult = WriteFile(m_hComm, //串口句柄
   &ucChar, //输出缓冲区
   1,    //每次只发送一个字符
   &BytesSent,     //实际读入的字符数
   &ov_Write); //重叠结构
if(!bResult) //写串口失败
{
DWORD dwError=GetLastError();
switch(dwError)
{
   case ERROR_IO_PENDING: //串口操作正在后台进行
   {
    BytesSent=0;
    bWrite=false;
    break;
   }
   default: //失败
    return false;
}
}
if(!bWrite)
{
//等待重叠结果
bResult=GetOverlappedResult(m_hComm,
    &ov_Write,
    &BytesSent, //实际发送字符数
    true);
if (!bResult) //重叠操作失败
   return false;
}
if(ov_Write.hEvent!=NULL) //释放事件资源
{
CloseHandle(ov_Write.hEvent);
ov_Write.hEvent=NULL;
}
//检查实际发送的字符数是否与要求相符
if (BytesSent!=1)
{
return false;
}
return true;
}
//----------------------------------------------------------------------
//向串口写一个字符(外部接口)
void __fastcall TSerialPort::WriteToPort(unsigned char ucTxData)
{
if(m_hComm==INVALID_HANDLE_VALUE)
{
mErrorMsg("串口句柄无效,无法发送数据.");
return;
}
int i=3;
while(i)   //重发次数不超过3次
{
if(WriteChar(ucTxData)) //成功发送
   break;
i--;
Application->ProcessMessages();
}
if(i==0)
{
char sTemp[100];
sprintf(sTemp,"数据'%d'未被成功发送.",ucTxData);
mErrorMsg(sTemp);
}
}
//--------------------------------------------------------------------------
-
//错误处理,显示原因
void __fastcall TSerialPort::ProcessErrorMessage(char* ErrorText)
{
char ErrorMsg[400];
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
   NULL,GetLastError(),     //获取错误信息标识
   MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),//使用系统缺省语言
   (LPTSTR)&lpMsgBuf, //消息缓冲区
   0,
   NULL);
sprintf(ErrorMsg, "COM%d: /"%s/" 由于以下错误而失败: /n/n%s",
m_uPortNo,ErrorText,lpMsgBuf);
Application->MessageBox(ErrorMsg, "串口错误", MB_ICONSTOP);
LocalFree(lpMsgBuf);
}
//----------------------------------------------------------------------
//----------------------------------------------------------------------
void __fastcall ComError(AnsiString AppendMsg)
{
        if(g_bProcessEndByUser) //是用户终止了通信过程
                return;
AnsiString Msg=AppendMsg+"/n/n未连接好压力计?或通讯口配置错?/n/
请检查压力计连接和通讯口配置情况后再试.";
Application->MessageBox(Msg.c_str(),"串口错误",MB_ICONSTOP);
}
//----------------------------------------------------------------------
//----------------------------------------------------------------------
这个类的使用方法如下:
1、建立类的实例,如:
SerialPort=new TSerialPort(); //实例化一个串口控制类
SerialPort->InitPort(this,g_iSerialPort,9600);
//初始化串口,Baud:9600,DataBits:8,StopBits:1,无校验位
SerialPort->StartMonitoring(); //创建串口监控线程,开始监控
2、映射串口消息,在创建以上实例的窗口类的声明中加入:
void __fastcall OnComRx(TMessage &Message);
BEGIN_MESSAGE_MAP//指定消息WM_COMM_RXCHAR的处理函数为OnComRx
MESSAGE_HANDLER(WM_COMM_RXCHAR, TMessage, OnComRx)
END_MESSAGE_MAP(TForm)
3、实现函数OnComRx(),如:
//--------------------------------------------------------------------------
-
//串口消息(接收到一个字符)处理函数
//消息的参数WParam就是接收到的字符
void __fastcall TDlgProgram::OnComRx(TMessage &Message)
{
unsigned char ucRxData=(unsigned char)Message.WParam;
………………
}
//--------------------------------------------------------------------------
-

 

转自:http://hi.baidu.com/minyuanyang/blog/item/56e1a7dec2d56959cdbf1a25.html

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值