//串口控制类,基本用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);
bool StartMonitoring(); //启动串口监控线程
int RestartMonitoring(); //恢复串口监控线程
int 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; //串口线程存在标志
};
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
实现代码Com.cpp
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "stdio.h"
#include "Com.h"
#include "Macro.h" //我自定义的一些宏,简化对MessageBox的调用
//---------------------------------------------------------------------------
//构造函数,初始化,把各数据成员置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) //句柄无效,打开串口失败
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);
return true;
}
//----------------------------------------------------------------------
//启动串口监控线程
bool TSerialPort::StartMonitoring()
{
DWORD dwThreadId;
//创建线程
m_hThread=CreateThread( NULL, //无安全属性
0, //用主线程的堆栈
CommThread, //线程的执行函数
this, //传给线程执行函数的参数
0, //初始为激活状态
&dwThreadId); //线程标识
if(m_hThread==NULL) //无法创建线程
{
ProcessErrorMessage("创建线程");
return false;
}
return true;
}
//----------------------------------------------------------------------
//挂起线程,返回以前的挂起记数
int TSerialPort::StopMonitoring()
{
return(SuspendThread(m_hThread));
}
//----------------------------------------------------------------------
//重新启动线程,返回以前的挂起记数
int TSerialPort::RestartMonitoring()
{
return(ResumeThread(m_hThread));
}
//----------------------------------------------------------------------
//串口事件处理线程
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;
ov.hEvent=CreateEvent(NULL, true, false, NULL);
//初始化重叠结构
ov.Offset=0;
ov.OffsetHigh=0;
PurgeComm(m_hComm,PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);
//写串口
bResult = WriteFile(m_hComm, //串口句柄
&ucChar, //输出缓冲区
1, //每次只发送一个字符
&BytesSent, //实际读入的字符数
&ov); //重叠结构
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,
&BytesSent, //实际发送字符数
true);
if (!bResult) //重叠操作失败
return false;
}
//检查实际发送的字符数是否与要求相符
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:
rocessErrorMessage(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);
}
//----------------------------------------------------------------------
//----------------------------------------------------------------------
这个类的使用方法如下:
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;
………………
}
//---------------------------------------------------------------------------
(2001.4.13)