多线程在串口通信中的应用

时间:2022-10-04 02:06:20

多线程在串口通信中的应用

摘要:Windows具有多线程处理能力,应用程序中可以创建多个线程,每个线程能够独立完成一个子任务。在通信程序中使用多线程技术,可提高程序的执行效率和反应速度。结合应用实例,介绍了VC++环境下基于Win32 API的多线程、串口通信、异步I/O技术的原理和实现方法。

关键词:多线程;串口通信;异步I/O

中图分类号:TP393文献标识码:A文章编号:1009-3044(2009)27-7583-04

Multi Thread Application in Serial Communication

GUO Xiao-mei

(Nanjing Xiaozhuang College, Nanjing 210001, China)

Abstract: Windows system can create multi thread, each thread can do single task which could promote the execute efficiency and response speed.This paper introduces theory and example of multi thread, serial communication and asynchronous I/O technology base on Win32 API in Visual C++ environment.

Key words: multi thread; serial communication; asynchronous I/O

Windows是一个多任务操作系统,进程是应用程序的执行实例,线程是进程内部的一个执行单元,每一个进程至少包含一个由系统创建的主执行线程。根据需要,用户可以在应用程序中创建多个线程,Win32系统中,多个线程可以实现并行处理,这意味着一个程序可以同时完成多个任务。实际上,对于单处理器(CPU)的计算机,操作系统为每个独立线程安排了一些CPU时间片(约20μs),并以特定的方式在各线程之间切换,同一时间,只有一个线程在运行,由于时间片很小,因而这些线程仿佛在同时、并行的工作。一般的,通信程序应具有实现各种I/O操作和及时响应用户请求的能力,为避免可能出现的I/O操作长时间占用CPU时间,影响对其它任务的处理,利用Win32的多线程和异步I/O特性,是设计通信程序的最佳选择。

1 多线程与串口通信

Win32 API函数支持多线程的程序设计。MFC中的CWinThread类对Win32 API多线程函数进行了封装。开发多线程应用程序,既可用Win32 API函数,也可用VC++提供的MFC类库。线程相当于一个函数,包括用户界面线程和工作线程两种。用户界面线程可以处理界面消息,工作线程一般用来处理后台工作,这里主要对工作线程进行讨论。

1.1 创建工作线程

创建工作线程,需先要编写一个线程函数。通过全局函数AfxBeginThread创建线程并启动后,在系统为该线程分配的时间片内,线程函数被自动调用。AfxBeginThread函数原型如下:

CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

参数pfnThreadProc是一个指向线程函数的指针,参数pParam为传递给线程函数的指针,其它几个参数用于设置线程的优先级、线程的堆栈大小、创建时是否立即启动、线程的创建方式及线程的安全属性。调用函数SuspendThread( )或ResumeThread( ),可使已创建的线程被挂起或恢复运行。

线程函数必须设计成全局函数或静态成员函数,其返回值和参数类型应满足如下的函数原型要求:UINT(LPVOID pParam);

1.2 串口通信

编制串口通信程序的一般步骤为:打开串口,配置串口,超时设置和数据读写。

1) 打开串口

Win32系统中,串口和其他通信设备都被作为文件进行处理,使用前必须先将其打开。为保证串口通信数据传输的可靠性,串口打开时要设置为独占模式,串口一旦被打开,其他应用程序将无法打开或使用。打开串口用函数CreateFile,其原型如下:

HANDLE CreateFile(

LPCTSTR lpFileName, // 文件名

DWORD dwDesiredAccess,// 访问模式

DWORD dwShareMode,// 共享模式

LPSECURITY_ATTRIBUTES lpSecurityAttributes, //通常为NULL

DWORD dwCreationDisposition,// 创建方式

DWORD dwFlagsAndAttributes, // 文件属性和标志

HANDLE hTemplateFile// 通常为NULL

);

2) 配置串口

串口进行数据通信前需对其进行配置,串口配置主要包括波特率,数据位数,停止位数,奇偶校验,输入/输出缓冲区的设置等。串口配置时要用到设备控制块DCB( Device Control Block),这是一个结构,其内包含了波特率等信息,调用SetCommState函数,用DCB结构变量作为参数,可进行串口参数设置。调用SetupComm函数,可设置输入/输出缓冲区的大小。

3) 超时设置

读、写串口时,要进行超时设置。如果在规定时间内没有完成对串口的读、写,那么操作就会结束。超时设置通过改变COMMTIMEOUTS结构的成员变量值来实现,调用SetCommTimeouts函数,用COMMTIMEOUTS结构变量作为参数,可进行串口超时设置。

4) 数据读/写

用ReadFile和WriteFile函数可实现对串口的读、写操作。这两个函数的参数和返回值很相似,以下是ReadFile的函数原型:

BOOL ReadFile(

HANDLE hFile,// 文件句柄,可表示串口

LPVOID lpBuffer, // 数据读入后放入该指针指向的缓冲区

DWORD nNumberOfBytesToRead,// 要求读入的字节数

LPDWORD lpNumberOfBytesRead, // 实际读入的字节数

LPOVERLAPPED lpOverlapped// 指向OVERLAPPED结构的指针

);

对于WriteFile函数,写往串口的数据放在lpBuffer指向的缓冲区内。

1.3 异步I/O

调用ReadFile和WriteFile时,如果最后一个参数lpOverlapped设置为NULL,则函数将进行同步操作,这意味着线程会阻塞在这里,直到读、写操作完成后,函数才会返回。在执行I/O这样费时的操作时,很多时间往往浪费在等待函数的返回上,这将导致程序效率的下降。利用Win32的异步I/O特性,可很好的解决这一问题。异步执行时,即使读、写操作还没完成,调用的函数也会立即返回,费时的I/O操作在后台执行,线程可继续处理其它事务。

用异步方式操作串口,在打开串口时,CreateFile函数的dwFlagsAndAttributes参数必须被设置为FILE_FLAG_OVERLAPPED 标志,同时ReadFile和WriteFile函数的lpOverlapped参数要指向一个OVERLAPPED结构。函数调用前,先为其创建一个OVERLAPPED结构变量,用以接收函数调用后的结果信息。异步操作时,ReadFile和WriteFile函数返回并不一定代表读、写操作完成,那么操作完成与否该如何判断呢?当系统完成I/O操作后,会设置OVERLAPPED结构变量,通过在程序的适当位置调用WaitForSingleObject函数来等待这个I/O完成通知,在得到通知信号后,再调用GetOverlappedResult函数来查询I/O操作结果,并进行相关处理。

2 VC++环境下基于API的串口通信程序

以下结合本人设计的串口通信程序,讨论多线程在串口通信编程中的实现方法。该程序可发送或接收数据,实现两个串行口之间的数据通信。主菜单中的“串口通信”用于连接和断开串口,选择“功能设置”中的“数据发送”命令后,随机敲击键盘,程序会将来自键盘的数据向串口发送;选择“功能设置”中的“数据接收”命令,程序将从串口接收数据,为产生直观效果,接收到的数据经处理后以图形方式显示。

程序在同一台微机的两个串行口上测试。连接COM1、COM2两个串口后(用串口连接线或虚拟串口软件),两次启动程序,分别选择“数据发送”和“数据接收”命令,从发送界面随机键入数据(如图1),此时在接收界面可见到同步接收的数据(如图2)。

2.1 设计思想

程序包含一个主线程和一个工作线程。分析发送端,程序通过键盘向串口发送数据,其特点为数据传送没有规律,速度不快,发送的数据量也不大;再分析接收端,接收的数据源不确定,大批数据快速涌入串口的情况可能发生。根据这一特点,在程序中增加一个工作线程,用于监视串口的数据接收情况。由于写入串口的数据量不大,因而可在主线程中接收键盘输入并写入串口,不必再创建另一个线程。

程序采用文档/视图结构。文档类CTRACOMDoc负责串口通信任务,主要包括打开/关闭串口、配置串口,超时设置、创建和终止工作线程、用工作线程监视串行口等,串口数据的读、写函数也在文档类中实现。视图类CTRACOMView由CView类派生,其主要任务是响应用户键盘输入、写数据至串行口、接收串口数据处理后作图。

2.2 关键代码分析

首先为文档类CTRACOMDoc添加串口通信所需的成员变量,其中m_sPort为串口名称,发送数据时,其值设定为“COM1”,接收数据时设定为“COM2”。

class CTRACOMDoc : public CDocument

{…………

public:

CWinThread *m_pThread;//指向新增的工作线程

volatileBOOL m_bConnect;//标记串口是否连接

CStringm_sPort; //定义串口名称

volatileHANDLE m_hCom;//串口句柄

//以下为串口通信参数定义,包括波特率、奇偶校验等

…………};

1) 打开串口与创建线程

执行“连接串口”命令后,文档类的成员函数OpenConnection( )被调用。该函数用CreateFile打开指定串口,为了以异步方式执行I/O操作,将它的dwFlagsAndAttributes参数置为FILE_FLAG_OVERLAPPED;调用自定义的ConfigConnection函数设置波特率等串口通信参数;调用全局函数AfxBeginThread创建工作线程,为了实现工作线程与主线程之间的通讯,将传递给线程函数的pParam参数置为this;结构体变量TimeOuts用于超时设置,SetCommMask函数用于设置指定串口发生的事件,它的第2个参数为EV_RXCHAR时,设定事件为输入缓冲区收到了字符,工作线程中的WaitCommEvent函数将监视此事件的发生。OpenConnection( )函数的主要代码如下:

BOOL CTRACOMDoc::OpenConnection( )

{ COMMTIMEOUTS TimeOuts;

…………

m_hCom=CreateFile(m_sPort,GENERIC_READ|GENERIC_WRITE,0,NULL,

OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL);//以异步方式打开串口

if(m_hCom==INVALID_HANDLE_VALUE)//打开失败时的处理

return FALSE;

SetupComm(m_hCom,2048,2048);//设置输入/输出缓冲区的大小

SetCommMask(m_hCom,EV_RXCHAR);//设置串口事件

//为TimeOuts的成员变量赋值

…………

SetCommTimeouts(m_hCom, &TimeOuts); //设置超时,

if(ConfigConnection( ))

{m_pThread=AfxBeginThread(CommProc,this,THREAD_PRIORITY_NORMAL,0,

CREATE_SUSPENDED,NULL);//创建工作线程,并将其挂起

if(m_pThread==NULL)

{CloseHandle(m_hCom); //如果线程创建失败,则关闭串口

return FALSE; }

else

{m_bConnect=TRUE;

m_pThread->ResumeThread( );//线程开始运行}

}

…………

return TRUE;

}

2) 线程函数

线程创建成功后就开始工作,它负责监视串行口。事件发生时(输入缓冲区中收到字符)向视图发送WM_COMMNOTIFY消息,由视图处理该消息;当串口事件再次发生时,线程等待前一事件的完成信号(该信号由视图发出),并再次发出WM_COMMNOTIFY消息,这是一个循环过程。兼顾到线程的效率和可靠性,用了两种方法对串行口进行监视,调用ClearCommError函数查询输入缓冲区中是否有数据,如果有,则产生一个WM_COMMNOTIFY消息并向视图发送,但前提是上一个WM_COMMNOTIFY消息已处理完毕;如缓冲区没有数据,就调用WaitCommEvent函数监视EV_RXCHAR通信事件,该函数执行异步操作,即不管事件发生与否,线程不会在此阻塞,函数立即返回,接下来调用GetOverlappedResult函数等待通信事件发生,如果串口收到字符并放入输入缓冲区,则函数结束等待,并返回一个OVERLAPPED结构来报告异步操作结果。以下是线程函数CommProc的主要代码:

UINT CommProc(LPVOID pParam)

{OVERLAPPED os;// os结构变量用于保存异步I/O操作结果

DWORD dwMask,dwTrans;

COMSTAT ComStat;// 结构变量ComStat用于保存通信串口的当前状态

DWORD dwErrorFlags;

…………

while(pDoc->m_bConnect)

{ClearCommError(pDoc->m_hCom,&dwErrorFlags,&ComStat);

if(ComStat.cbInQue) //当缓冲区有字符时

{ //等待前一个WM_COMMNOTIFY消息处理完毕

WaitForSingleObject(pDoc->m_hPostMsgEvent,INFINITE);

ResetEvent(pDoc->m_hPostMsgEvent);

//向视图发送WM_COMMNOTIFY消息

PostMessage(pDoc->m_hTermWnd,WM_COMMNOTIFY,EV_RXCHAR,0);

continue;}

dwMask=0;

if(!WaitCommEvent(pDoc->m_hCom,&dwMask,&os)) //异步执行

{if(GetLastError()==ERROR_IO_PENDING)

//等待异步操作结束

GetOverlappedResult(pDoc->m_hCom,&os,&dwTrans,TRUE);

……………… }

}

return 0;

}

3) 数据发送

选择“数据发送”并从键盘输入数据时,视图类的成员函数OnChar被调用,该函数接收键盘输入,并调用文档类的WriteComm函数,将键入的数据向串口输出。函数通过调用WriteFile 向串口输出数据,由于规定用异步方式操作串口,函数WriteFile的lpOverlapped参数设置为指向一个OVERLAPPED结构的事件对象。以下是WriteComm函数代码:

DWORD CTRACOMDoc::WriteComm(char *buf, DWORD dwLength)

{BOOL fState;

DWORD length=dwLength;

COMSTAT ComStat;

DWORD dwErrorFlags;

ClearCommError(m_hCom,&dwErrorFlags,&ComStat);

fState=WriteFile(m_hCom,buf,length,&length,&m_osWrite);//异步方式写串口

if(!fState)

{if(GetLastError()==ERROR_IO_PENDING)

GetOverlappedResult(m_hCom,&m_osWrite,&length,TRUE); //等待异步操作结果

elselength=0; }

return length;

}

4) 数据接收

执行“数据接收”命令,当线程函数监测到输入缓冲区有数据时,向视图发送WM_COMMNOTIFY消息,此时视图类的OnCommNotify成员函数被调用,它调用文档类的ReadComm函数并通过ReadFile从串口读取数据,然后对读取的数据进行处理,完成后调用SetEvent函数,允许线程发送下一个WM_COMMNOTIFY消息。以下为OnCommNotify函数的主要代码:

LRESULT CTRACOMView::OnCommNotify(WPARAM wParam,LPARAM lParam)

{char buf[500];

CString str;

int nLength;

CTRACOMDoc *pDoc=GetDocument( );

………………

nLength=pDoc->ReadComm(buf,50);

if(nLength) //对收到的数据进行处理

{for(int i=0;i

{point.x+=10;

point.y=350-2*buf[i];

pDoc->AddLine(m_ptOld,point);//加入线段到指针数组

m_ptOld=point; }

ff=2;

Invalidate( );}

SetEvent(pDoc->m_hPostMsgEvent); //处理完毕,允许发送下一个WM_COMMNOTIFY消息

return 0L;

}

3 结束语

多线程技术应用于串口通信,可提高程序的执行效率,使程序并行工作。当系统既要进行费时的I/O操作,又同时

有其它任务要处理时(如及时响应用户请求),线程是最好的工具。可以创建新的线程,结合异步I/O操作,来完成耗时的串口读/写任务,主线程则可以继续处理其它事务。本文结合应用实例,对多线程、串口通信、异步I/O技术原理和实现方法进行了分析讨论。

参考文献:

[1] 葛子昂.Windows核心编程[M].5版.北京:清华大学出版社,2008.

[2] 侯俊杰.深入浅出MFC[M].2版.武汉:华中科技大学出版社,2001.

[3] 龚建伟.Visual C++ /Turbo C串口通信编程实践[M].北京:电子工业出版社,2008.

[4] 辛长安.Visual C++权威剖析[M].北京:清华大学出版社,2008.

上一篇:软件配置管理在高校科研管理系统开发中的应用 下一篇:利用Windows Server 2003自身安全策略加强操作...