Windows
95下串行通信编程技术及其实现
郭峰林 朱才连
摘 要 本文首先简单讨论MSDOS、16位Windows和Windows95下的通信编程差别,然后着重讲述32位Windows95
环境下的通信编程技术,最后给出利用该技术实现串行通信的实例。 关键词 API函数,串行通信,中断,查询,线程,同步,异步,阻塞
TECHNIQUE OF SERIAL COMMUNICATION PROGRAMMING AND ITS
REALIZATION IN WINDOWS95
Guo Fenglin Zhu
Cailian Institute of Geodesy & Geophysics, Chinese Academy
of Sciences, Hubei.Wuhan 430077
Abstract This paper discusses the
difference of communication programming in the operating system of
MSDOS,16 bit Windows and Windows95.Then tells of some problems about
serial communication programming technique in 32 bit OS of windows95.
Finally, an example of the application of this technique in windows95is
provided. Keywords API function, Serial
communication, Interrupt, Poll, Thread, Synchronization,
Asynchronization
1 前言 Windows95以其形象的图形界面设计、操作简单、功能强大、可靠性高等优点,赢得了越来越多的用户,开发Windows95应用程序已经成为当今的主流。在诸多的应用开发中,与外部硬件设备通信是常见的应用,而串行通信以其简单的硬件连接方式常常成为应用开发者的首选。然而串行通信编程从MSDOS、Windows3.1到Windows95各不相同,虽然在功能上越来越强,但是编程的复杂度也相应增大。 笔者最近在Windows95环境下开发一套“公安110智能报警系统”,该系统需要对报警电话进行实时监控,以便能实时地进行接警和处警。报警电话的监控是通过检测从电话交换机中馈送的RS232标准的串行通信信号,其中串行口通信采用3线方式。该系统采用Windows95下的Visual
C++
5.0编写,由于有关Windows95的串行口通信编程方面的资料少,串行通信编程的实例也不多见,笔者在成功开发“公安110智能报警系统”的基础上,取得了一些经验,现在将有关串行口通信方面的一些关键技术写出来,供广大的编程者借鉴、参考。
2 下串行通信编程特征 MSDOS下的串行通信编程较简单,通信编程可以直接对串口的物理地址进行编程操作同时配合BIOS调用,即可实现串行口数据读写。 在Windows下,串行口作为系统资源,由设备驱动程序统一管理,用户不能象在MSDOS下一样直接对串行口硬件端口进行编程。16位的Windows3.1操作系统提供了专门的串行通信的API函数:OpenComm()、CloseComm()、ReadComm()、
WriteComm()等,通过这些专用API(Application Programming
Interfaces)函数来设置和读、写串行口。而Windows95将串行口和其它通信设备如Modern、传真机等统一视作文件,对串行口的打开、关闭、读写等操作与操作普通文件的API函数相同,如CreateFile()、CloseHandel()、ReadFile()、WriteFile(),正是由于这些函数的“多态性”,
同时还由于需要结合Windows95的线程编程、事件驱动等新技术,因而使得Windows95下的串行口通信编程比较复杂。
3 Windows95下串行通信API函数 在Windows95中将串行口与文件的统一了起来,对它们的打开、读、写、关闭等操作都使用相同的API函数,但是它们之间又有差别,譬如串行口不能象文件一样可以被删除,这些差别体现在API函数中部分参数的设置上。 弄清串行通信API函数的用法是掌握串行通信编程技术的关键。下面介绍几个与串行通信编程密切相关的API函数,着重说明这些API函数在进行串行通信时参数设置需要注意的地方。其它没有提及的函数及参数可以参考Windows95
API函数手册。 3.1 打开串行口API函数 Windows95通信会话以调用CreateFile()函数打开串行口开始。调用CreateFile()打开串口成功,返回一个操作句柄。该句柄供随后对串行口的设置、读写等操作用。 CreateFile()函数原型: HANDLE
CreateFile(LPCTSTR szDevice, DWORD dwAccess, DWORD dwShareMode,
LPSECURITY—ATTRIBUTES lpSA, DWORD dwCreate, DWORD
dwFlagsAndAttributes, HANDLE hTemplateFile
); 调用此函数要注意这几个参数的设置:dwShareMode指定该端口的共享属性。该参数是为文件共享提供的,串行口不能作为共享设备。故参数值必须为0,这是文件与通信设备之间的主要差异之一;dwCreate必须为OPEN—EXISTING。因为CreateFile()只能打开存在的端口,而不能象创建新文件一样创建物理上不存在的新串口;dwFlagsAndAttributes描述了该端口的各种属性。对于文件来说,具有多种属性(只读、隐藏、系统)是可能的,但是对于串行口,唯一有意义的设置是FILE—FLAG—OVERLAPPED;参数hTemplateFile必须为NULL。 返回值:若成功,返回创建的句柄;否则返回,INVALID—HANDLE—VALUE. 举例:打开串行口1 HANDLE
hComm; //定义句柄变量 hComm = CreateFile( "COM1",
GENERIC—READ|GENERIC—WRITE,NULL,NULL, OPEN—EXISTING,FILE—FLAG—OVERLAPPED,NULL); if
(hComm == INVALID—HANDLE—VALUE) {……. //
打开串口错误的处理}
3.2 配置串行口API函数 串行口打开成功,接下来可以配置串行口通信参数如波特率、数据位数、停止位、校验位等。修改这些参数时要和设备控制块DCB(Device
Control
Block)打交道,DCB有近30个数据成员,是一个很复杂的数据结构,全部弄清楚它们的含义相当费时。而对于采用3线方式的串行通信来说,DCB结构中绝大多数参数可以不予考虑,因为只要设置好波特率、数据位、停止位、校验位等几个关键参数就行。这里介绍一种简捷的方法可以做到不了解DCB的详细内容也可以设置好串行通信参数。 通过下面的程序来说明串行通信参数的设置方法。例程中利用BuildCommDCB函数来设置DCB,然后用函数SetCommState()配置串行通信口。 DCB
dcb
; //定义设备控制块 GetCommState(hComm,&dcb); //取出系统缺省设备控制块 BuildCommDCB("COM2:9600,N,8,1",&dcb);
//设置DCB主要参数 SetCommState(hComm,&dcb); 3.3 超时设置API函数 编写通信应用程序的一个很关键的问题就是如何处理通信中的不可预测的事件。譬如接收数据过程中突然被中断,或者发送数据突然停止等等。如果不认真对待,这些情况可能会引起I/O线程挂起或者线程被无限阻塞。Windows95对于这类问题提供了安全措施,它让你通过超时设置来决定通信是否异常并作相应处理。因此超时设置在串行通信中显得尤为重要。 超时设置过程分为两步,首先设置COMMTIMEOUTS结构中的五个变量,然后调用SetCommTimeouts()函数设置超时值。COMMTIMEOUTS结构的定义如下: typedef
struct — COMMTIMEOUTS { DWORD ReadIntervalTimeout;
//读端口间隔超时 DWORD
ReadTotalTimeoutMultiplier; //读端口总超时乘数 DWORD
ReadTotalTimeoutConstant; //读端口总超时常数(ms) DWORD
WriteTotalTimeoutMultiplier; //写端口总超时乘数 DWORD
WriteTotalTimeoutConstant; //写端口总超时常数(ms) }
COMMTIMEOUTS,*LPCOMMTIMEOUTS; 3.4 读串口API函数 串行口打开后,可以对它进行读写操作。读串行口的函数原型: BOOL
ReadFile (HANDLE hFile, LPVOID lpBuffer, DWORD
nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED
lpOverlapped
data); 其中,第一个参数hFile是由CreateFile()返回的句柄。参数lpBuffer是读取的数据缓冲区指针,要注意给该数据缓冲区分配足够的空间;参数nNumberOfBytesToRead是要读取的字节数;参数lpNumberOfBytesRead是实际读取的字节数;最后一个参数lpOverlapped
是指向一个可重叠I/O(异步)的数据结构指针。如果lpOverlapped设置为NULL,则ReadFile()工作在同步方式;如果lpOverlapped指向一个重叠结构,则工作在异步方式。 3.5 写串口API函数 BOOL WriteFile (HANDLE
hFile, // 由CreateFile()返回的句柄 LPCVOID
lpBuffer, // 写缓冲区指针 DWORD
nNumberOfBytesToWrite, // 要写的字节数 LPDWORD
lpNumberOfBytesWritten, // 实际写的字节数 LPOVERLAPPED
lpOverlapped //
指向一个可重叠I/O的数据结构); WriteFile()函数的工作方式选择与ReadFile()的相同,在此不重复。 3.6 关闭串口API函数 串行口是非共享资源,某应用程序打开串行口后,即独占该资源,使其它应用程序无法再访问,直到该应用程序释放串口。所以打开串口后,一定要关闭串口。关闭串口函数较简单。函数原型:BOOL
CloseHandle( HANDLE hObject
);其中hObject参数为CreateFile()返回的端口句柄。返回值非0,则调用成功。
4 Windows95的串行通信工作方式 串行通信会话以调用CreateFile()函数打开串行口开始,接着设置串行口波特率、数据位、校验位、停止位等参数以及超时参数,最后选择一种工作方式读、写串行口。在Windows95中,串行通信有两种工作方式可供选择:查询方式和事件驱动方式。这两种工作方式各有优缺点,用户可以根据应用程序的实际需要选择其中的一种工作方式,下面对这两种工作方式分别介绍。 4.1 查询方式 对于从串口读取数据来说,查询是最为直接、易于理解的技术。但是查询会占用大量的CPU时间,效率较低。利用查询方式读取串口数据时通常要建立一个线程,建立线程使用CreateThread()函数。循环查询在线程里进行。举例:(假设端口已经打开) DWORD
ReadThread(LPDWORD lpdwParam) { BYTE Buff[100];
//读数据缓冲区 DWORD nBytesRead;
//实际读取的字节数 COMMTIMEOUTS
Timeouts; //超时设置 Memset(&Timeouts,0,sizeof(COMMTIMEOUTS)); Timeouts.ReadIntervalTimeout
=
MAXDWORD; SetCommTimeouts(hComm,&Timeouts); While(bReading){ if(!ReadFile(hComm,Buff,100,&nBytesRead,NULL)) {……… //读取数据出错处理} else{……… //正确读取数据的处理} } PurgeComm(hComm,PURGE—RXCLEAR); return
0; } 例程中,线程的退出由bReading标志控制,当bReading为TRUE时,循环串口;当breading为FALSE时,线程退出。 4.2 事件驱动方式 事件驱动I/O方式是指线程通过监视通信资源中的一组事件来进行I/O操作,这种方式类似于MSDOS下的中断工作方式,效率高。可被监视的事件列表如下: |