金 剑
[摘 要]介绍了使用32位Windows API函数编写串口通信程序的方法,采用多线程和事件驱动技术,使所编写的程序运行效率较高,对于CPU任务比较繁重的PC机—单片机工业控制系统的性能提高有很大意义。
[关键词]VC++ Win32 API 串口通信 多线程
[中图分类号][文献标识码]A[文章编号]1007-9416(2009)12-0024-02
1 引言
Windows环境下编程的最大特点就是设备无关性,通过驱动程序将应用程序与外部设备相隔离。Windows封装了通信机制,称为通信API,程序员可以利用它间接控制通信端口,不必对硬件进行直接操作。目前采用Microsoft公司提供的ActiveX控件MSComm实现串口通信的实例相当多,使用也非常简单,但是由于只能在对话框应用程序中使用,应用范围有限,而采用API函数编程就完全避免了这个问题。
线程是操作系统分配CPU时间的基本单位,系统不停地在各个线程之间切换,一个线程只有在分配的时间片内才对CPU有控制权。利用Win32系统的多线程特性,主线程负责与用户进行交互,设置辅助线程专门负责通信I/O,程序员就可以编写出准实时的“两不误”通信程序。
很多工业控制系统中常常通过串口连接外设,而各外设发送数据重复的频率不同,要求应用程序后台实时无差错地捕捉和处理各端口数据,同时前台仍旧可以与用户进行交互,我们可以在VC++环境下,用Win32通信API及多线程技术编写高效率的通信程序来完成这一任务,开发过程如下。
2 打开串口
Win32系统中,串口是作为文件处理的,其打开、关闭、读取和写入所用的函数与文件操作函数完全一致。通信会话以调用CreateFile()函数开始,描述如下。
HANDLE hCom;//定义端口句柄
hCom=CreateFile(“Com1”,//以字符串形式指定要打开的串口名
GENERIC_READ|GENERIC_WRITE,//指定访问类型,允许读和写
0,//指定共享属性。对于串口,必须设置为0,表示独占
NULL,//引用安全属性结构。设为NULL为串口分配缺省的安全属性
OPEN_EXISTING,//告诉Windows打开已经存在的端口
FILE_FLAG_OVERLAPPED,//指定端口I/O可以在后台进行,即异步I/O
NULL//设定指向模板文件的句柄,对于串口,必须设置成NULL);
If(hCom==INVALID_HANDLE_VALUE){…}//未成功打开串口,编写异常处理程序。
3 配置串口
在CreateFile()成功调用打开串口后,Windows将根据上次打开串口时的设置来初始化端口,如果是首次打开,系统将使用缺省的配置。
3.1 为端口分配缓冲区
SetupComm(hCom,//已经打开的端口句柄,下同
1024,//以字节数指定输入缓冲区大小
1024,//以字节数指定输入缓冲区大小);
3.2 清空缓冲区
PurgeComm(hCom,
PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
PurgeComm()函数的第二个参数指定动作,其设定值如表1所示:
3.3 设置驱动事件类型
SetCommMask(hCom,
EV_RXCHAR//设定被监视的通信事件,见表2);
3.4 定义并设置超时结构变量
COMMTIMEOUTS CommTimeOuts;
……//填写超时结构
SetCommTimeOuts(hCom, &CommTimeOuts;);//应用设置好的超时结构
超时结构是Windows针对串口读写引入的数据结构,直接影响读和写的操作行为,定义如下:
typedef struct_COMMTIMEOUTS{
DWORD ReadIntervalTimeout;//以ms为单位指定两个字符到达的最大时间间隔
DWORD ReadTotalTimeoutMultiplier;//以ms为单位指定计算读操作总超时时间的系数
DWORD ReadTotalTimeoutConstant; //以ms为单位指定常数,也用于计算读操作超时时间
DWORD WriteTotalTimeoutMultiplier;//以ms为单位指定计算写操作总超时时间的系数
DWORD WriteTotalTimeoutConstant; //以ms为单位指定常数,也用于计算写操作超时时间
}COMMTIMEOUTS,*LPCOMMTIMEOUTS;
Windows按如下式子计算总超时时间,可根据具体应用情况设定超时结构的参数:
ReadTotalTimeout=ReadTotalTimeoutMultiplier*bytes_to_read+ReadTotalTimeoutConstant;
WriteTotalTimeout=WriteTotalTimeoutMultiplier*bytes_to_write+WriteTotaltimeoutConstant;
3.5 定义并设置设备控制块DCB
DCB dcb;
GetCommState(hCom,&dcb;);//获取当前的DCB状态参数
dcb.BaudRate=9600;//设定波特率
dcb.ByteSize=8;//设定端口使用的数据位数
dcb.Parity=NOPARITY;//设定奇偶校验方法,此处设为无校验
dcb.StopBits=ONESTOPBIT;//设定端口使用的停止位位数,此处设为1位停止位
dcb.fBinary=TRUE;//允许二进制。Win32不支持非二进制传输,此处必须设为TRUE
dcb.fParity=FALSE;//指定是否允许奇偶校验,此处设为禁止奇偶校验
SetCommState(hCom,&dcb;);//应用配置好的参数
4 启动辅助线程
CWinThread*m_pThread;//定义线程类变量
……//启动辅助线程
m_pThread=AfxBeginThread(CommWatchProc,//指定线程处理函数名
this,//设置要传递给线程函数的参数
THREAD_RPIORITY_NORMAL,//设定优先级微调值
0,//设置默认的堆栈大小(1MB)
CREATE_SUPSPENDED,//挂起创建好的线程
NULL//安全防护属性,NULL表示这一属性与其产生者相同);
if(m_pThread= =NULL)CloseHandle(m_hCom);//未成功创建,关闭串口
elsem_pThread ->ResumeThread();//成功创建,恢复线程的运行
//线程函数
UNIT CommWatchProc(HWND hSendWnd)//传入的参数是欲投放消息的目的窗口的句柄
{ OVERLAPPED os;
DWORD dwEvtMask=0;//用于记录事件掩模
COMSTAT ComStat;//COMSTAT结构存放通信设备的当前信息,由ClearCommError函数填写
DWORD dwErrorFlag;
BOOL fReadStat;//记录返回的读状态
while(TRUE){//循环监测串口事件
WaitCommEvent(hCom, &dwEvtMask;,&os;);//等待串口通信事件的发生
//检测返回的dwEvtMask,从而判定发生了何种串口事件
if((dwEvtMask & EV_RXCHAR)==EV_RXCHAR){//缓冲区有数据到达
ClearCommError(hCom, &dwErrorFlag;, &ComStat;);//清除错误条件,确定串口状态
if(ComStat.cbInQue){//如果串口接收到的字节数不为0,就读取数据
fReadStat=ReadFile(hCom,
buffer,//指向一个缓冲区
length,//指定要从串口读取的字节数
&length;,//指向调用该函数读出的字节数
&os;_Read//指向一个OVERLAPPED结构);
if(!fReadStat)//函数返回时I/O操作尚未完成
//等待重叠操作结果,直到完成异步I/O
GetOverlappedResult(hCom,&os;,&length;,TRUE);
else
return(UNIT)-1;
}
}
PostMessage(hSendWnd,WM_COMMNOTIFY,EV_RXCHAR,0);//发送消息
}
}
5 编写发送命令
BOOL fWriteStat;
char szBuffer[SENDBLOCK];
……//将待发送的数据放在szBuffer[ ]中
//将数据写入串口
fWriteStat=WriteFile(hCom,szBuffer, dwBytesToWrite,&dwBytesToWrite;,os_WRITE);
if(!fWriteStat)
if(GetLastError( )==ERROR_IO_PENDING)
//等待重叠操作结果
GetOverlappedResult(hCom,&os;_WRITE,&length;,TRUE);
在整个程序中,OVERLAPPED是个非常重要的结构,用于设置异步I/O。要使用OVERLAPPED结构,CreatFile( )函数的第六个参数必须设置FILE_FLAG_OVERLAPPED标识,同时串口读写函数ReadFile( )和WriteFile( )的第五个参数也必须指定VOERLAPPED结构,否则函数不会正确地报告I/O操作已经完成。
串口使用完毕后必须调用CloseHandle( )函数将其关闭,否则串口会继续保持打开状态,其它程序将无法使用。最后还要编写WM_COMMNOTIFY消息的处理函数,可根据具体的工程应用情况灵活处理,在此就不赘述了。
6 结语
Windows为串口通信提供了完善的接口函数,再利用VC++这个强大的编程开发环境,就使得广大工程技术人员可以轻松地编写出界面友好而高效的通信程序,来解决工业控制领域的许多技术问题。
参考文献
[1] 李现勇.Visual C++串口通信技术与工程实践.人民邮电出版社,2002年5月.
[2] Charles Wright著,邓劲生,张晓明译.Visual C++程序员使用大全.中国水利水电出版社,2001年10月.
[3] 伍红兵.Visual C++编程深入引导.中国水利水电出版社,2002年3月.