安徽博微长安电子有限公司 龙 灏 丁旭玲
随着监控系统的发展,监控系统与各个外围设备数据传输量巨大,通讯方式越来越多样,CPU处理任务越来越繁重。串口通讯的数据收发处理是监控程序的重要一部分,串口通信中如何结合异步通信、多线程等技术,编写出高质量、高效率的串口通信程序,具有一定的现实意义。
在Windows平台下,串口被视为一种特殊的文件,串口的操作可以被视为一种文件操作。在实际的编程中,可以使用文件相关的API函数,例如CreateFile()、ReadFile()以及WriteFile()等对串口进行关联或者操作。
串口的操作模式分为同步模式和异步模式,同步模式是指程序中的代码按顺序执行,只有前面的程序执行完毕并返回,才能继续执行后面的程序,该模式容易造成程序的假死。异步模式下,调用的函数会立即返回,不会造成线程的阻塞,线程可以在不同的句柄上同时执行I/O操作,甚至可以在同一句柄上同时进行读写操作,效率更高。
程序将串口操作函数及相应的变量封装为串口类,方便主程序调用。主程序可定义串口类的对象调用各种函数,同时对多串口进行监听及读写操作。串口类中的重要函数如表1所示:
表1 串口类包含的函数及功能实现
为了提高程序的效率,串口类中由事件驱动数据发送、接收和线程的退出。类中定义了3个事件:
m_hShutDownEvent(串口关闭事件)、m_hWriteEvent(数据发送事件)、Overlapped结构的m_ov(数据接收事件)。配合线程中的WaitForMultipleObjects()函数,实现线程的调度。为了实现对不同串口初始化,需要使用临界区对象,以保证在某一时刻只进行一个串口的初始化。部分初始化代码如下:
If(m_ov.hEvent!=NULL)//事件的建立
ResetEvent(m_ov.hEvent);
m_ov.hEvent=CreatEvent(NULL,TRUE,FALSE,NULL);//事件被定义为手动复位
……
m_h Event Array[0]=m_h Shut Dow n Ev ent;//m_hEventArray为事件数组
m_hEventArray[1]=m_ov.hEvent;
m_hEventArray[2]=m_hWriteEvent;
InitializeCriticalSection(&m_csCommunication);//初始化临界区对象
……
串口的初始化首先需要使用API函数CreateFile()打开串口并创建与该串口相关联的文件。注意将文件相关属性设置为FILE_FLAG_OVERLAPPED,才可以对串口进行异步操作。打开串口代码如下:
HANDLE hCom; //定义串口句柄
hCom=CreateFile(szPort,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,NULL);//创建异步模式文件并关联串口,返回其句柄
表2 串口配置函数
在获得通讯设备句柄后,可使用句柄进行串口参数设置。在串口API函数编程时,波特率、校验方式等参数均被封装到一个结构体DCB中。在查询或配置串口的参数时,都要使用该结构体。配置串口使用的函数如表2所示。
串口初始化完成后,可在线程中等待通信事件的发生。线程函数中使用WaitForMultipleObjects()函数对m_hEventArray数组中的3个事件同时进行监测处理。当判断某个事件为有信号时,执行相应的操作。线程函数部分代码如下:
for (;;) //循环
{
……
Event = Wait For Multip leObjects(3, por t->m_hEventArray, FALSE, INFINITE);
//无限等待事件的发生
switch (Event)
{
case 0://线程关闭事件为有信号状态
{
port->m_bThreadAlive = FALSE;
AfxEndThread(0);//退出线程
}
case 1: // 读事件为有信号状态
{
GetCommMask(port->m_hComm, &CommEvent);//得到串口事件
if (CommEvent & EV_RXCHAR)//判断为接收事件
if(!port->m_bBlockRead)
ReceiveChar(port, comstat);//调用ReadFile()函数读取数据,每接收一个字节数据向主程序送WM_COMM_RXCHAR消息和接收到的数据
break;
}
case 2: // 写事件为有信号状态
{
WriteChar(port);// 调用WriteFile()发送数据
break;
}
}
将串口类的源程序及头文件导入到主程序工程中,即可在主程序中定义该串口类的对象,通过对象引用串口初始化InitPort()、串口监视函数StartMonitoring()等函数。当需要向串口发送数据时,可调用WritePort()函数。当串口类的线程收到一个字节的数据,会向主程序发送WM_COM_RXchar消息和接收到的数据,因此在主程序中需自定义WM_COMM_RxCHAR消息,并关联消息处理函数以处理接收的数据。主程序消息处理函数部分代码如下所示:
OnRecvByte(UINT wParam,LONG lParam)
{
Unsigned char JustRecv=(unsigned char)wParam;
Int PortNum=(int)lParam;
If(PortNum==1)
{
RecvData[Cnt]=JustRecv;//将接收的字节保存
Cnt++;
}
…
}
上面的代码为自定义消息WM_COMM_RxCHAR的处理函数OnRecvByte(),在消息处理函数中将不同串口的数据存储到指定的数组中,供主程序使用。
本文介绍了基于API函数的串口类的实现,综合使用了异步模式、多线程、事件等方法,并给出VC开发环境下实现串口通信类的关键代码。在实际监控项目中,该串口类运行稳定可靠,实现多串口数据的高效率读写,具备较强的实践价值和参考意义。
[1]阚能琪.VC++串口通信中多线程技术的应用研究[J].西华大学学报,2005,24(4):84-85.
[2]梁伟,等.Visual C++网络编程案例实战[M].北京:清华大学出版社,2014,10:35-54.
[3]李琳娜,等.Visual C++编程实战宝典[M].北京:清华大学出版社,2014,9:379-388.
[4]赵永发,由大伟,杨丽,等.Visual C++开发宝典[M].北京:机械工业出版社,2012,4:510-519.