摘要:首先介绍了多线程技术的基本原理,然后讨论了多线程技术在串口通信中的应用,并给出了实现的方法和步骤。 关键词:多线程;串口通信;事件对象;delphi
1 引言 串口是计算机与外部串行设备之间常用的数据传输通道,在很多工业控制系统中,通常要求系统具有实时计算能力,能够满足高效中断处理、多任务和通信的需要。而将Windows多线程技术应用于串口通讯中,使得应用程序能同时执行不同的任务,降低了数据丢失率,提高了系统可靠性,并且提高了资源的利用率和系统的整体性能。
2 Windows的多线程技术 在串口通信过程中,采用基于Windows多任务环境下的多线程技术,操作系统会将CPU时间划分成许多时间片段,并按一定的优先级将时间片段分配给各个线程,各线程在各自的时间片段内共享CPU,从而实现了微观上轮次执行,宏观上并发运行的多任务机制。 多线程应用系统的设计包含创建线程、线程同步、终止线程3部分,其中关键是要处理好线程之间的同步问题,以避免线程之间出现资源竞争而引起几个线程甚至整个系统的死锁。现在的操作系统提供了许多安全、高级的线程同步控制方法,以Win32系统为例,系统提供了同步对象来协调多线程的执行,同步对象有:事件对象(Event Object),冲突区(Critical Section),信号量(Semaphore)和互斥(Mutex)。 其中事件对象用来标志某个事件是否发生,从而确定是否执行某个线程。当一个线程在执行某项任务之前,需要等待某一事件发生的时候,使用事件对象非常方便。它是靠自身是否处于有信号状态以表明共享数据是否可访问,从而达到多线程间同步的目的。 本文采用事件对象来实现各个线程的同步。在没有预定事件时,输出处理线程、输入处理线程和其他处理线程挂起以消耗尽量少的资源,监视线程检测到有预定事件时,用一事件对象通知主线程,请求相应的处理。主线程接收到发送来的消息,自己处理或者唤醒相应的线程处理程序,使信号得到实时处理。
3 串口通信的原理和机制 3.1 串口的打开和关闭 串口的打开是通过CreateFile完成的,该函数用于创建或打开文件、通道、通信资源。 要打开重叠I/O,则应该指定FILE_FLAG_OVERLAPPED属性。使用重叠方式的时候,费时的I/O操作在后台进行,主线程则完成别的事情而不用担心效率会降低。 通过调用CloseHandle函数来关闭串口占用的内存句柄,释放相应的串口资源。 3.2 串口的初始化和配置 Windows用I/O缓冲区来暂存串口输入和输出的数据。一旦串口处于打开状态,Windows 就可以给串口分配接收和发送缓冲区。缓冲区的大小既可以缺省,也可以指定(调用SetupComm函数)。如果通信速率较高,则应该设置较大的缓冲区大小。 配置串口需要设置串口通讯中特定事件的掩码(调用SetCommMask),只要串口中出现特定的消息,相应的事件掩码就会返回。 配置串口还需要设置串口通讯参数(调用SetCommState),例如波特率、数据位、校验位等。 在用ReadFile和WriteFile读写串口之前,还需要考虑超时问题,一定要进行超时设置(调用SetCommTimeOuts)。如果在指定的时间内没有读出或写入指定数量的字符,那么ReadFile和WriteFile将立即返回。因为在读、写过程中可能发生许多不可预见的事件,通过设置超时,能在通讯过程中避免意外发生,保障通讯的安全性。调用GetCommTimeouts可以获取当前的超时设置。 3.3 串口的读写操作 串口的读写是通过ReadFile和WriteFile 来实现的。在读写串口前需要清理串口通信中的错误信息,并返回当前串口设备的状态。当串口是通过重叠机制打开时,串口就具备了异步I/O的功能,使得应用程序可以在后台读、写串口, 在前台处理其他任务,这对节省CPU的运行时间,提高系统的工作效率很有好处。 3.4 事件驱动响应 由于Windows是一个基于消息驱动的操作系统,它的许多事件来源于硬件设备,Windows设备驱动程序只是将这些事件进行处理,转换成相应的消息放到Windows消息队列中去,用户进程需要自觉查询串口状态,并对相应事件作出响应。可通过两个API函数来完成这一功能:SetCommMask(hcomm,dwMask)设置串口通讯中特定事件的掩码,不同的掩码对应不同的事件消息。WaitCommEvent(hComm,&dwEvent,&overlapped),在用SetCommMask指定特定事件以后,就可以调用该函数来等待事件发生,一旦串口有事件发生,参数dwEvent会返回相应事件的掩码,用户进程就可以作出相应的处理。在异步模式下,WaitCommEven在后台监视串口的状态,直到有事件发生返回。在实际的应用中,这一过程是用专有的监视线程来完成的。监视线程在捕获到特定串口事件以后,应通知主线程作出相应的处理。 3.5 事件驱动的多线程串口通信程序模型的设计[1,2] 根据多线程程序的开发方法,该串口通信软件由负责人机交互的主线程和对串口进行处理的后台辅助线程组成。主线程是串口通信程序的管理者,用来初始化串口(通过调用Win32 API函数),自定义通信事件消息,创建、删除辅助线程,进行人机交互的操作及协调好各线程运行。 后台辅助线程是串口通信软件的核心,包括串口监视线程、读线程、写线程。由于既要实时监控外部串口的状态,又要及时读写串口的数据,还不能使进程一直等待,所以要产生一个串口监视线程来实时监控串口,通过串口监视线程在后台对串口进行实时监视,当监测到预定义的事件时,立即调用相应的线程进行处理并向主线程发相应的消息,如接收到数据就调用读线程自动接收数据并进行处理,同时向主线程发送接收到数据的消息(WM_COMMRECV),串口监视线程发送完此消息后就执行后面的程序代码,继续对串口进行监视,做到了处理消息与监视串口两不误,既保证了通讯的实时性,又避免了资源的浪费。 为保证各线程间的协调运行,使用了事件对象,事件对象是最简单的同步对象,它包括有信号和无信号两种状态。事件对象可以用CreateEvent函数创建。 如果事件创建成功,则该函数返回事件的句柄,否则返回NULL。如果不再使用事件句柄,可以调用CloseHandle函数。为了把事件句柄状态设为发信号状态,可以使用SetEvent函数。 CreateEvent函数可以指定事件对象的类型和初始状态。如果是手工重置, 则它总是保持有信号状态,直到用ResetEvent函数重置成无信号的事件;如果是自动重置,则它的状态在单个等待线程释放后会自动变为无信号的。用SetEvent可以把事件对象设置成有信号状态。串口监视线程需要等待主线程完成串口数据的读取后才能运行,此时该线程需要暂时挂起, 以减少对CPU的占用时间,提高程序的执行效率。当主线程完成了串口数据的读取后,将同步事件对象置为有信号,从而激活串口监视线程监测串口事件的发生,保证串口监视线程与主线程之间的同步,避免错误的发生。 在读取串口数据的时候使用了ReadFile,该函数的返回值是BOOL值。若该函数返回TRUE, 则表示操作成功。但是需要注意,该函数因为超时也将返回TRUE。另外如果返回值为FALSE, 也不表示操作就失败了,应该调用GetLastError函数分析返回的结果。如果GetLastError 返回ERROR_IO_PENDING,则说明重叠操作还未完成,需要等待操作结束。 Win32 API 提供了一组能使线程阻塞其自身执行的等待函数,这些函数只有在作为其参数的一个或多个同步对象产生信号时才返回。在超过规定的等待时间后,函数将立即返回, 而不管有无信号。在等待函数未返回时,线程处于等待状态,此时线程只消耗很少的CPU时间。比如常用的等待函数WaitForMultipleObjects 可以同时监测多个同步对象。 在调用ReadFile和WriteFile之前,线程应该调用ClearCommError函数清除错误标志,该函数负责报告指定的错误和设备的当前状态。调用PurgeComm 函数可以终止正在进行的读写操作,该函数还会清除输入或输出缓冲区中的内容。
4 程序实现过程[3,4] 4.1 程序初始化部分 ………… ReadEvent:=CreateEvent(nil,true,true,nil);//创建读事件 If ReadEvent=null then begin MessageBox(0,’ReadEvent Create Error’,’Notice’,MB_OK); Exit; end; ………… //创建写事件、关闭线程事件 hcomm:=createfile(‘com1’,……,file_flag_overlapped,0);//异步(重叠)I/O方式 if (hcomm=invalid_handle_value) then MessageBox(0,’打开串口错误’,’’,MB_OK); EvWait:=EV_RXCHAR+EV_TXEMPTY+EV_ERR; if not SetCommMask(hcomm,EvWait) then //设置事件掩码 MessageBox(0,’设置错误’,’’,MB_OK); Comm_Thead:=CreateThread(nil,0,@CommWatch,nil,0,ThreadID);//创建辅助线程 4.2 后台辅助线程处理部分 …… fillchar(lpol,sizeof(Toverlapped),0);// lpol:Poverlapped; while true do begin wait:=waitcommevent(hcomm,Evwait,lpol);// 等待串行口事件 if not wait then begin err:=GetLastError();// 错误处理 ………; end else ClearCommError (hcomm ,errorflag , @comstat) ; Evwait:=WaitforMultipleObjects(3,eventarray ,false,infinite); case Evwait of 0 : ...;// 结束辅助线程 1 : ...;// 读线程 2 : …;// 写线程 end; end; ……
5 结束语 本文将多线程技术应用于串口通讯程序设计中,提出了一个完整的程序实现模型,提高了系统的效率和可靠性,在实践中取得了很好的效果。
参考文献 [1] 吴先亮.基于多线程的串口通讯软件设计与实现[J].控制工程,2004,11(2):171-173. [2] 朱有芹.新编windowsAPI参考大全[M].北京:电子工业出版社,2000.
|