http://michelletsang09.blog.163.com/blog/static/122540540200971352958857/
WIN32 通信API基本上是一个串行端口API,不是很适合于局域网(LAN)通信.在网络通信和连接方面,TCP/IP协议要比WIN32通信API更适合一 些.WIN32通信API也不能用于实时通信.不需要许多协议层的交互式,非实时的通信可以采用WIN32通信API来实现,WIN32通信API把串口 操作(以及并口等)和文件操作统一起来了.
Windows串口通信相关API函数
打开和关闭串口
一. 打开串口
在32位的Windows系统中,串口和其他通信设备是作为文件处理的. 串口的打开,关闭,读取和写入所用的函数与操作文件的函数完全一致.
通信会话以调 用CreateFile()开始.CreateFile()为读访问,写访问或读写访问”打开”串口.按照Windows的通常做 法,CreateFile()返回一个句柄,随后在打开的端口的操作中使用.CreateFile()函数非常的复杂,复杂性的原因之一是它是通用的.可 以使用CreateFile打开已存在的文件,创建新文件或打开根本就不是文件的设备,例如串口,并口和调制解调器.
CreateFile()函数声明如下:
HANDLE CreateFile(
LPCTSTR lpFileName, // pointer to name of the file
DWORD dwDesiredAccess, // access (read-write) mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes
DWORD dwCreationDisposition, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to file with attributes to copy
);
CreateFile的参数解释:
lpFileName:指定要打开的串口逻辑名,用字符串表示,如”COM1”和”COM2”分别表示串口1/2
dwDesiredAccess用来指定串口访问的类型.与文件一样,串口也是可以被打开以供读 取.写入或者两者兼有.GENERIC_READ为读取访问打开端口,GENERIC_WRITE为写访问打开端口.我们可以用逻辑操作符将这两个标识连 接起来,为读/写访问权限打开端口.因为大部分串口通信都是双向的,因此常常在设置中将两个标识连接起来使用.
dwShareMode 指定端口的共享属性.对于不能共享的串口,它必须置为0.
lpSecurityAttributes 引用安全性属性结构,将该参数置为NULL将为该端口分配默认的安全性属性.
dwCreationDisposition 指定如果CreateFile正在被已有的文件调用时应采取的动作.因为串口总是存在,所以必须设置为OPEN_EXISTING.该标志告诉 Windows不要企图创建新端口,而是打开已经存在的端口.
dwFlagsAndAttributes 描述端口的各种属性.对于串口而言,唯一有意义的设置是FILE_FLAG_OVERLAPPED.当创建时指定该设置,端口I/O可以在后台进行(后台 I/O也叫异步I/O).
hTemplateFile 指向模板文件的句柄,当端口处于打开状态时,不使用该参数,因而必须设置成0.
例 子:
HANDLE hCom;
DWORD dwError;
hCom=CreateFile("COM3",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL);
if (hCom==INVALID_HANDLE_VALUE)
{
dwError=GetLastError(); //处理错误
}
一旦端口处于打开状态,就可以分配一个发送缓冲区和 接收缓冲区,并且通过调用SetupComm()实现其初始化工作.也可以不调用SetupComm函数,Windows系统也会分配默认的发送和接收缓 冲区,并且初始化端口.但为了保证缓冲区的大小与实际需要的一致,最好还是调用该函数.SetupComm函数的声明如下:
BOOL SetupComm(
HANDLE hFile, // handle to communications device通信设备句柄
DWORD dwInQueue, // size of input buffer输入缓冲区大小
DWORD dwOutQueue // size of output buffer输出缓冲区大小
);
SetupComm
的参数 解释:
hFile
由 CreateFile()返回的指向已打开端口的句柄.
dwInQueue
和dwOutQueue
接收缓冲区的大小和发送缓冲 区的大小.这两个定义并非是实际的缓冲区大小,指定的大小仅仅是”推荐的”大小,而Windows可以随意分配任意大小的缓冲区.windows设备驱动 程序可以获得这两个数据,并不直接分配大小而使用来优化性能和避免缓冲区超限.
注意:当使用CreateFile()函数打开串口时,为实现调制解调器的排它性访问,共 享标识必须设为0,创建标识必须设为OPEN_EXISTING,模板句柄必须设置为空.
二.关闭串口
只需要调用 CloseHandle()函数关闭由CreateFile()函数返回的句柄即可.
CloseHandle()
函数声明如下:
BOOL CloseHandle(
HANDLE hObject // handle to object to close需关闭的设备句柄
);
使用串口后一般要关闭它,如果忘记关闭串口,串口就会始终处于打开状态,其它应用程序就不能打开并 使用串口了.
串口配置和串口属性
在CreateFile函数中打开串口后,系统将根据上次打开串口时设置的值来初始化串口,可以集 成上次打开操作后的数值,包括设备控制块(DCB)和超时控制结构(COMMTIMEOUTS).如果是首次打开串口,Windows操作系统将会使用默 认的配置
一.串口配置
使用GetCommState()函数获取串口的当前配置,使用SetCommState()重新分配串口资源的各个参数.
GetCommState()函数声明:
BOOL GetCommState(
HANDLE hFile, // handle to communications device通信设备句柄
LPDCB lpDCB // pointer to device-control block structure指向DCB结构的指针
);
GetCommState的参数解释:
hFile 由CreateFile()函数返回的指向已打开串口的 句柄
lpDCB 一个非常重要的结构—设备控制块 DCB(Device Control Block).
DCB结构的声明如下:
typedef struct _DCB { // dcb
DWORD DCBlength; // sizeof(DCB)DCB块大小
DWORD BaudRate; // current baud rate现在的数据传输率
DWORD fBinary: 1; // binary mode, no EOF check二进制模式,不检验EOF
DWORD fParity: 1; // enable parity checking允许奇偶校验
DWORD fOutxCtsFlow:1; // CTS output flow controlCTS输出流控制
DWORD fOutxDsrFlow:1; // DSR output flow controlDSR输出流控制
DWORD fDtrControl:2; // DTR flow control type DTR流控制类型
DWORD fDsrSensitivity:1; // DSR sensitivity对DTR信号线是否敏感
DWORD fTXContinueOnXoff:1; // XOFF continues Tx
DWORD fOutX: 1; // XON/XOFF out flow controlXON/XOFF输出流控制
DWORD fInX: 1; // XON/XOFF in flow control XON/XOFF输入流控制
DWORD fErrorChar: 1; // enable error replacement 错误替换
DWORD fNull: 1; // enable null stripping是否丢弃接收到的NULL字符
DWORD fRtsControl:2; // RTS flow control RTS流控制
DWORD fAbortOnError:1;// abort reads/writes on error发送错误,指定是否终止读.写操作
DWORD fDummy2:17; // reserved 保留
WORD wReserved; // not currently used 现在不用
WORD XonLim; // transmit XON threshold XON字符发送之前接收到缓冲区中可允许的最小字节数
WORD XoffLim; // transmit XOFF threshold XOFF字符发送之前接收到缓冲区中可允许的最小可用字节数
BYTE ByteSize; // number of bits/byte, 4-8 端口当前使用的数据位数
BYTE Parity; // 0-4=no,odd,even,mark,space当前使用的奇偶校验方法
BYTE StopBits; // 0,1,2 = 1, 1.5, 2 当前使用的停止位数
char XonChar; // Tx and Rx XON character 发送和接收的XON字符值
char XoffChar; // Tx and Rx XOFF character 发送和接收的XOFF字符值
char ErrorChar;// error replacement character用来代替接收到的奇偶校验发生错误的字符.
char EofChar; // end of input character表示数据的结束
char EvtChar; // received event character事件字符
WORD wReserved1; // reserved; do not use 保留的位
} DCB;
注意:
fBinary
参数是指定 是否允许二进制.Win32API不支持非二进制传输,因此这个参数必须设置为TRUE,如果设置为FALSE则工作不正常.
fOutxCtsFlow
参数:指定CTS是否用于检测发送流控制.当该成员为TRUE,而CTS为OFF时,发送 将被挂起,直到CTS置ON.
fOutxDsrFlow
参 数:指定DSR是否用于检测发送流控制.当该成员为TRUE,而DSR为OFF时,发送将被挂起,直到DSR置ON.
如果 GetCommState()函数调用成功,则返回值不为零.若函数调用失败,则返回值为零,如果希望得到进一步的错误信息,可以调用 GetLastError()函数来获取.
GetLastError()
函数声明如下:
DWORD GetLastError(VOID)
如果应用程序只需要修改一 部分配置的时候,可以通过GetCommState()函数获得当前的DCB结构,然后更改DCB结构中的参数,再调用SetCommState()函数 配置修改过的DCB来配置端口.
SetCommState()
函数声明如下:
BOOL SetCommState(
HANDLE hFile, // handle to communications device已打开的串口的句柄
LPDCB lpDCB // pointer to device-control block structure指向DCB结构的指针
);
若SetCommState()函数调用成功,则返回值不为零,如果 函数失败,则返回值为零.出错时可以调用GetLastError()函数获得进一步的出错信息.如果SetCommState()函数调用的DCB结构 中的XonChar与XoffChar成员值相同,则SetCommState()函数会调用失败.
DCB最经常改变的参数是 数据传输速率,奇偶校验的方法以及数据位和停止位数.Windows为改变这些设置提供了BuildCommDCB函数,函数声明如下:
BOOL BuildCommDCB(
LPCTSTR lpDef, // pointer to device-control string设置的字符串
LPDCB lpDCB // pointer to device-control block指向DCB结构的指针
);
BuildCommDCB参数包含新设置的字符串和一个DCB结构的参数,该设置将提供给DCB结 构.字符串格式如:”
baud=1200 parity=N da
该字符串不包括串口的名称,实际上这个函数并不改变端口的设置, 因此没有必要标识该串口.当然这个串口必须是有效的串口.新的设置只是简单的拷贝到已提供好的DCB结构中,要使新设置生效,还必须调用 SetCommState()函数.默认情况下, BuildCommDCB函数禁止XON/XOFF和硬件流的控制.如果使用硬件流控制,则必须设置DCB结构的各个成员的值.
Win32还提供了 BuildCommDCBAndTimeouts函数来提供设置DCB的功能,该函数可以进一步设置超时结构.
BuildCommDCBAndTimeouts
函数声明如下:
BOOL BuildCommDCBAndTimeouts(
LPCTSTR lpDef, // pointer to device-control string设置的字符串
LPDCB lpDCB, // pointer to device-control block指向DCB结构的指针
LPCOMMTIMEOUTS lpCommTimeouts // pointer to comm time-out structure指向超时结构的指针
);
BuildCommDCBAndTimeouts函数根据lpDef字符串中的值来 设置修改它的超时结构的,如果包含”TO=ON”,则函数根据超时结构LPCOMMTIMEOUTS为串口设置超时值;如果”TO=OFF”则函数不为串 口设置超时值;如果不包括”TO=XXX”的字串,函数将忽略LPCOMMTIMEOUTS的超时结构,该超时结构将不被获取.
二.串口属性
串口的属性可以通过GetCommProperties()函数获得. GetCommProperties函数声明如下:
BOOL GetCommProperties(
HANDLE hFile, // handle to comm deviceCreateFile返回的句柄
LPCOMMPROP lpCommProp // pointer to comm properties structure指向COMMPROP的结构
);
LPCOMMPROP
指向一个COMMPROP的结构,串口的性能从COMMPROP中返回. COMMPROP的结构的内容是静态的,不允许任何应用程序改变该结构中返回的信息.没有任何相应的函数可将COMMPROP结构中的设置写向串口. COMMPROP结构和GetCommProperties并不仅仅用于串口,它们也可以用来返回并口的有关信息.
COMMPROP
结构的类型定义:
typedef struct _COMMPROP {
WORD wPacketLength; // packet size, in bytes整个数据薄的大小
WORD wPacketVersion; // packet version COMMPROP结构的版本号
DWORD dwServiceMask; // services implemented指定一位掩码
DWORD dwReserved1; // reserved保留
DWORD dwMaxTxQueue; // max Tx bufsize, in bytes驱动程序内部发送缓冲区的最大允许长度
DWORD dwMaxRxQueue; // max Rx bufsize, in bytes驱动程序内部接收缓冲区的最大允许长度
DWORD dwMaxBaud; // max baud rate, in bps可用的最大数据传输速率
DWORD dwProvSubType; // specific provider type指定通信设备的类型
DWORD dwProvCapabilities; // capabilities supported标识设备能够实现的功能
DWORD dwSettableParams; // changeable parameters标识可修改的通信参数
DWORD dwSettableBaud; // allowable baud rates标识可以设置的数据传输速率
WORD wSettableData; // allowable byte sizes标识可以设置的数据位数
WORD wSettableStopParity; //stop bits/parity allowed允许的停止位
DWORD dwCurrentTxQueue; // Tx buffer size, in bytes以字节为单位指定当前发送缓冲区大小
DWORD dwCurrentRxQueue; // Rx buffer size, in bytes以字节为单位指定当前接收缓冲区大小
DWORD dwProvSpec1; // provider-specific da
DWORD dwProvSpec2; // provider-specific da
WCHAR wcProvChar[1]; // provider-specific da
} COMMPROP;
三.通用通信设备配置
WIN32 提供了一个函数CommConfigDialog来对通用通信设备进行配置.该函数显示为一个对话框,可以从中改变数据传输速率,数据位,奇偶校验方法, 停止位和流控制方法.该函数声明如下:
BOOL CommConfigDialog(
LPTSTR lpszName, // pointer to device name string要配置的端口名
HWND hWnd, // handle to window拥有此对话框的窗口句柄
LPCOMMCONFIG lpCC // pointer to comm configuration structure指向一个COMMCONFIG结构
);
COMMCONFIG
结构声明如下:
typedef struct _COMM_CONFIG {
DWORD dwSize; // size of structure指明整个结构长度
WORD wVersion; // version of structure结构的版本号
WORD wReserved; // reserved保留
DCB dcb; // device-control block一个DCB结构的参数
DWORD dwProviderSubType; // type of provider-specific da
DWORD dwProviderOffset; // offset of provider-specific da
DWORD dwProviderSize; // size of provider-specific da
WCHAR wcProviderData[1]; // provider-specific da
} COMMCONFIG, *LPCOMMCONFIG;
当CommConfigDialog函数返回时,选定的设置在COMMFIG的DCB参数中返回, 对已打开端口的句柄, CommConfigDialog函数没有作为参数传递,实际上并不改变端口的设置,这使得更改要通过SetCommState函数来改变.
COMMCONFIG lpcc;
lpcc.dwSize=sizeof(COMMCONFIG);
if(!CommConfigDialog("COM3",m_hWnd,&lpcc))
return 0;
四.缓冲区控制
WIN32除了提供SetupComm函数实现初始化的缓冲区操作外,还提供了 PurgeComm()函数和FlushFileBuffers()函数进行缓冲区操作.
PurgeComm()函数的声明如下:
BOOL PurgeComm(
HANDLE hFile, // handle to communications resource返回的句柄
DWORD dwFlags // act
);
PurgeComm
函数可以在读写操作的同时,清空缓冲区.当应用程序在读写操作时调用PurgeComm函数,不能 保证缓冲区内的所有字符都被发送.如果要保证缓冲区的所有字符都被发送,应该调用
FlushFileBuffers() 函数.该函数只受流量控制的支配,不受超时控制的支配.它在所有的写操作完成后才返回.
FlushFileBuffers()函数声明如下:
BOOL FlushFileBuffers(
HANDLE hFile // open handle to file whose buffers are to be
// flushed 函数打开的句柄即CreateFile函数打开的句柄
);
读写串口
利用Win32通信API读写串口时,既可以同步执行也可以重叠(异步)执行.在同步执行时,函 数直到操作完成后才返回.在重叠执行时,即使操作还未完成,调用的函数也会立即返回.
一. 读串口操作
WIN32提供ReadFile()函数或者 ReadFileEx()函数从串口中读取数据.ReadFile函数对同步或异步操作都支持.而ReadFileEx只支持异步操作.这两个函数都受到 函数是否异步操作,超时操作等有关参数的影响和限定.
ReadFile()函数声 明如下:
BOOL ReadFile(
HANDLE hFile, // handle of file to read指向标识的句柄
LPVOID lpBuffer,//pointerto buffer that receives da
DWORD nNumberOfBytesToRead, // number of bytes to read读取的字节数
LPDWORD lpNumberOfBytesRead, // pointer to number of bytes read指向调用该函数读出的字节数
LPOVERLAPPED lpOverlapped // pointer to structure for da
);
主要参数介 绍:
hFile 该句柄必须拥有GENERIC_READ的权限
lpBuffer 用于存放从串口设备中读取的数据
lpNumberOfBytesRead ReadFile()在读 取操作前,首先将其设置为0.当lpOverlapped没有设置时, lpNumberOfBytesRead必须设置, 当lpOverlapped设置时, lpNumberOfBytesRead可以不设置,这是可以调用 GetOverlappedResult()函数获取实际的读取数值.
lpOverlapped 如果hFile以FILE_FLAS_OVERLAPPED方式创建,则需要此结构,否则,不需要此结构.
需要注意的是如果该函数因为超时而返回,那么返回值是TRUE.参数lpOverlapped在 重叠操作时应该指向一个OVERLAPPED结构,参数为NULL时,那么函数将进行同步操作,而不管句柄是否是由 FILE_FLAG_OVERLAPPED标志建立的.当ReadFile返回FALSE时,不一定是操作失败,线程应该调用GetLastError函 数分析返回的结果.例如:在重叠操作时如果操作还未完成函数就返回,那么函数就返回FALSE,而且GetLastError函数返回 ERROR_IO_PENDING.
ReadFileEx和ReadFile的不 同之处在于,它只能异步执行.这个函数是对ReadFile()函数的扩充,它允许应用程序继续其他的操作,异步地报告读操作的完成状态.
ReadFileEx()函数声明如下:
BOOL ReadFileEx(
HANDLE hFile, // handle of file to read指向标识的句柄
LPVOID lpBuffer, // pointer to buffer指向一个缓冲区
DWORD nNumberOfBytesToRead, // number of bytes to read指定要从串口设备读取的字节数
LPOVERLAPPED lpOverlapped,// pointer to offset一个OVERLAPPED的结构,此参数是必须的
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
// pointer to completion routine指明一个I/O操作完成后的常用例子的地址
);
二. 写串口操作
可以使用WIN32 API函数WriteFile或者WriteFileEx()向串口中写数据.WriteFile函数对同步或异步操作都支持.而WriteFileEx 只支持异步操作.这两个函数都受到函数是否异步操作.超时操作等有关参数的影响和限定.
WriteFile()函数声明如下:
BOOL WriteFile(
HANDLE hFile, // handle to file to write to
LPCVOID lpBuffer, // pointer to da
DWORD nNumberOfBytesToWrite, // number of bytes to write指向要向串口设备写入的字节数
LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written
LPOVERLAPPED lpOverlapped // pointer to structure foroverlapped I/O
);
WriteFile
参数详解:
hFile 指向标识的句柄,该句柄必须拥有GENERIC_WRITE的 权限
lpNumberOfBytesWritten指 向调用该函数要写入的字节数WriteFile()在写操作前,首先将其置为0,当lpOverlapped没有设置时,此参数必须设置, 当lpOverlapped设置时,可以不设置,这时可以调用GetOverlappedResult()函数获取实际的读取数值.
lpOverlapped 一个OVERLAPPED结构,如果hFile以FILE_FLAS_OVERLAPPED方式创建,则需要此结构,否则不需要此结构
WriteFileEx() 函数声明如下:
BOOL WriteFileEx(
HANDLE hFile, // handle to output file
LPCVOID lpBuffer, // pointer to input buffer
DWORD nNumberOfBytesToWrite, // number of bytes to write
LPOVERLAPPED lpOverlapped, // pointer to async. i/o da
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // pointer to completion routine指明一个I/O操作完成后的常用例子的地址
);
Windows系统还提供TransmitCommChar()函数,该函数可以在发送时指定一字 符的传递权限级别最高,使该字符可以在缓冲区的其他字符发送之前发送.
TransmitCommChar
的函数声明:
BOOL TransmitCommChar(
HANDLE hFile, // handle to communications device标识通信的端口句柄
char cChar // character to transmit要发送的字符
);
TransmitCommChar
这个函数是同步函数,也受流量控制和写超时支配.因此,当端口没有传输时,不能重复调用 TransmitCommChar函数,当TransmitCommChar函数把一个字符放到输出缓冲区中,在下次调用这个函数前,这个字符必需被输 出,否则TransmitCommChar函数就会出错.
三. 异步I/O操作
异 步(重叠)I/O操作是指应用程序可以在后台读或者写数据,而在前台做其他事情.
要 使用OVERLAPPED结构,CreateFile()函数的dwFlagsAndAttributes参数必须设为 FILE_FLAG_OVERLAPPED标识,读写串口函数必须指定OVERLAPPED结构.异步I/O操作在windows中使用广泛.
OVERLAPPED结构类型声明如下:
typedef struct _OVERLAPPED { // o
DWORD Internal; //
DWORD InternalHigh; //
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
OVERLAPPED结构参数解释:
Internal OS保留,指出一个和系统相关的状态.当GetOverlappedResult()函数返回时,如果将扩展信息设置为 ERROR_IO_PENDING,该参数有效.
InternalHigh OS保留,指出发送或接收的数据长度,当GetOverlappedResult函数返回值不为0时,该参数有效.
Offset指明文件传送的开始位置和字节偏移量的高位字.当进行端口操作时,这两个参数被忽略
hEvent指定一个I/O操作完成后触发的事件(信号).在调用读写函数进行I/O操作 之前,必须设置该参数
在设置了异步I/O操作后,I/O操作和函数返回有两种情况发生
1. 函数返回时I/O操作已经完成,此时结果好象是同步执行的,但实际上这是异步执行的结果
2. 函数返回时I/O操作还没完成,此时一方面,函数返回值为零,并且GetLastError函数返回ERROR_IO_PENDING;另一方面,系统把 OVERLAPPED中的信号事件设为无信号状态.当I/O操作完成时,系统要把它设置为信号状态.
异步I/O操作可以由GetOverlappedResult函数来获取结果,也可以使用 windows信号函数来处理.
GetOverlappedResult()函 数声明如下:
BOOL GetOverlappedResult(
HANDLE hFile, // handle to file, pipe, or comm device
LPOVERLAPPED lpOverlapped, // pointer to overlapped structure
LPDWORD lpNumberOfBytesTransferred, // pointer to actual bytes count
BOOL bWait // wait flag
);
GetOverlappedResult 参数详解;
lpOverlapped 在启动异步操作时指定的 OVERLAPPED结构
lpNumberOfBytesTransferred 指向一个长整型变量,该变 量接收有一个读或写操作实际传递的字节数
bWait指定函数是否等待挂起的异步操作完成.若设为1,则该函数直到I/O操作 完成后才返回,如果为0,同时处于被挂起状态,则函数返回为0,并且GetLastError()函数返回ERROR_IO_INCOMPLETE.
Windows也使用等 待函数来检查事件对象有的当前状态或等待windows状态信号,在waitforsingleobject()函 数,waitforsingleobjectex()函数及 waitformultipleobject(),waitformultipleobjectsex()函数中指定OVERLAPPED结构中的 hEvent,即可获取函数返回事件.
Waitforsingleobject()函数声明如下:
DWORD WaitForSingleObject(
HANDLE hHandle, // handle to object to wait for
DWORD dwMilliseconds // time-out interval in milliseconds
);
主要参数解释:
hHandle 指定一个同步时间的句柄,这里是指OVERLAPPED结构中的 hEvent句柄.
dwMilliseconds 以MS为单位指定超时时间
若该函数调用成功,则返回值标识函数导致该函数返回的事件,否则返回 值为WAIT_FAILED.成功的返回值可以是WAIT_ABANDONED,WAIT_OBJECT_0和WAIT_TIMEOUT.
四. 超时设置
超时结 构直接影响读和写的操作行为.当事先设定的超时间隔消逝时,ReadFile(),ReadFileEx(),WriteFile()和 WriteFileEx()操作仍未结束,那么超时设置将无条件结束读写操作,而不管是否已读出或已写入指定数量的字符.
在读或写操作期间发生的超 时将不按错误处理,即读或写操作返回指定成功的值.对于同步读/写操作,实际传输的字节数由ReadFile()和WriteFile()函数报告.对于 异步操作,则有OVERLAPPED结构来获取.
COMMTIMEOUTS超时结构定义如下:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
主要参数如下:
ReadIntervalTimeout
指定通信线路上两个字符到达之间的最大时间间隔.在ReadFile()操作期间,从接收到第一个 字符时开始计时.如果任意两个字符到达之间的时间间隔超过这个最大值,则ReadFile()操作完成,并返回缓冲数据,如果被置为0,则表示不使用间隔 超时.
ReadTotalTimeoutMultiplier
该系数用来计算读操作的总超时时间
ReadTotalTimeoutConstant
该常数用来计算读操作的总超时时间
WriteTotalTimeoutMultiplier
该系数用来计算写操作的总超时时间
WriteTotalTimeoutConstant
该常数用来计算写操作的总超时时间
超时有两种类型,第一种类 型叫做区间超时(interval timeout)它仅适应于从端口读取数据,它指定在读取两个字符之间要经历多长时间,接收一个字符时,windows就启动一个内部定时器.在下一个字 符到达之前,如果定时器超过了区间超时设定时间,读函数就会放弃.第二种类型的超时叫做总超时(total timeout)它适于读和写端口.当读或写特定字节数需要的总时间超过某一阀值时,该超时即被触发.如果系数和常数都为0,则没有总超时时间.
Windows
使用下面的式子计算总超时时间:
ReadTotalTimeout=( ReadTotalTimeoutMultiplier * bytes_to_read)+ ReadTotalTimeoutConstant
WriteTotalTimeout=( WriteTotalTimeoutMultiplier * bytes_to_write)+ WriteTotalTimeoutConstant
当两个读总超时参数都被设置为0,那么标识只要一读完接收缓冲区而不管得到什么字符都完成读操作, 即使它是空的.当接收中有间隔时,间隔超时将迫使读操作返回.因此使用间隔超时的进程可以设置一个非常短的间隔超时参数,这样它可以实现对一个或一些字符 的小的,孤立的数据作出反应.
在传输某种流量控制被阻塞时和调用SetCommBreak()函数把字符挂起,写操作的超时可能 有用.当打开通信资源时,超时参数将根据上次设备被打开时所设置的值设置.如果资源从未打开或调用SetupComm函数,那么所有的超时参数都设置为 0.
想要 获得当前超时参数,应用程序可以调用GetCommTimeouts()函数.该函数声明如下:
BOOL GetCommTimeouts(
HANDLE hFile, // handle to comm device标识通信设备
LPCOMMTIMEOUTS lpCommTimeouts // pointer to comm time-outs structure指向一个COMMTIMEOUTS结构,返回超时信息.
);
若要设置或改变原来的超时参数,应用程序可以调用SetCommTimesouts()函数,该 函数声明如下:
BOOL SetCommTimeouts(
HANDLE hFile, // handle to comm device
LPCOMMTIMEOUTS lpCommTimeouts // pointer to comm time-out structure
);
五. 通信状态和通信错误
如果在串口通信中发生错误,如发生中断,奇偶错误等,I/O操 作将会终止.如果程序要进一步进行I/O操作,必须调用ClearCommError()函数. ClearCommError函数有两个作用:第一个是清除错误条件,第二个是确定串口通信状态.
ClearCommError()函数的声明如下:
BOOL ClearCommError(
HANDLE hFile, // handle to communications device
LPDWORD lpErrors, // pointer to variable to receive error codes
LPCOMSTAT lpStat // pointer to buffer for communications status
);
参数解释:
lpErrors
指 向用一个指明错误类型的掩码填充的32位变量.
lpStat
指向一个COMSTAT结构,该结构接收设备的状态信息.如果lpStat不 设置,则没有设备状态信息被返回.
在同步操作时,可以调用ClearCommError()函数来确定串口的接收缓冲区处于等待状态 的字节数,而后可以使用ReadFile()或者WriteFile()函数一次写完读完.
COMSTAT结构存放有关通信设备的当前信息.其结构内容由 ClearCommError()函数填写:
COMSTAT结构声明如下:
typedef struct _COMSTAT {
DWORD fCtsHold : 1; // Tx waiting for CTS signal是否等待CRS信号,若为1,则发送等待.
DWORD fDsrHold : 1; // Tx waiting for DSR signal是否等待DSR信号
DWORD fRlsdHold : 1;//Tx waiting for RLSD signal 是否等待RLSD信号
DWORD fXoffHold : 1; // Tx waiting, XOFF char received指明收到XOFF字符后发送是否等待.若为1则发送等待
DWORD fXoffSent : 1; // Tx waiting, XOFF char sent指明发送完XOFF字符后发送是否等待,如果把XOFF字符发送给一系统时,该系统就把下一个字符当成XON,则不管实际字符是什么,此时发送将 停止.
DWORD fEof : 1; // EOF character sentEOF字符送出
DWORD fTxim : 1; // character waiting for Tx指明字符是否正等待被发送,如果为1,则字符正等待被发送.
DWORD fReserved : 25; // reserved
DWORD cbInQue; // bytes in input buffer串行设备接收到的字节数,并不是指ReadFile操作要求读的字节数
DWORD cbOutQue; // bytes in output buffer指明发送缓冲区中尚未发送的字节数,如果不进行重叠操作时值为0.
} COMSTAT, *LPCOMSTAT;
通信事件
Windows
进程中监视发生在通信资源中的一组事件,这样应用程序可以不检查端口状态就可以知道某些条 件何时发生,通过使用事件,应用程序不需要为接收字节而连续不断地检测端口,从而节省CPU时间.
一.
通信事件
Windows
可以利用 GetCommMask()函数和SetCommMask函数来控制通信时间.
二.
操作通信事件
应用程序可以利用 SetCommMask()函数建立事件掩模来监视指定通信资源上的事件.
SetCommMask
函数的声明如下:
BOOL SetCommMask(
HANDLE hFile, // handle to communications device标识通信设备
DWORD dwEvtMask // mask that identifies enabled events事件掩模,标识将被监视的通信事件.如果该参数设置为0,则表示禁止所有事件,如果不为0则可以是各种事件的组合.
);
若想要获取特定通信资源的 当前事件掩模,可以使用GetCommMask()函数.
GetCommMask()函数的声明如下:
BOOL GetCommMask(
HANDLE hFile, // handle to communications device
LPDWORD lpEvtMask // pointer to variable to get event mask事件掩模,标识将被监视的通信事件,一个32位变量.可以是各事件的组合
);
三.
监视通信事件
在用SetCommMask指定了有用的事件后,应用程序可用waitCommEvent()函 数来等待其中一个事件发生. waitCommEvent函数既可以同步使用,也可以异步使用.
waitCommEvent函数声明如下:
BOOL WaitCommEvent(
HANDLE hFile, // handle to communications device
LPDWORD lpEvtMask, // pointer to variable to receive event
LPOVERLAPPED lpOverlapped, // pointer to overlapped structure
);
主要参数解释:
lpEvtMask 指向一个32位变量,接受事件掩模,标识所发生的通信事件属 于何种类型,可以是各种事件的组合
lpOverlapped 指向一个OVERLAPPED结构,如果打开hFile 表示的通信设备时,指定FILE_FLAG_OVERLAPPED标志,则该参数被忽略,如果不需要异步操作,则这个参数不用设置.
如果 lpOverlapped参数不设置或打开hFile标识的通信设备是未指定FILE_FLAG_OVERLAPPED标志,则直到发生了指定时间或出错 时,WaitCommEvent()函数才返回. 如果lpOverlapped参数指向一个OVERLAPPED结构并且打开hFile标识的通信设备是指定了FILE_FLAG_OVERLAPPED 标志,则WaitCommEvent()函数以异步操作实现.这种情况下,OVERLAPPED结构中必须含有一个人工复位事件的句柄.
调用程序可使用等待函数确 定时间对象的状态,然后使用GetOverlappedResult函数确定WaitCommEvent函数的操作结束.
如果一个进程在 WaitCommEvent()操作进行期间使用SetCommMask()试图改变通信设备的事件掩码,则WaitCommEvent()函数将立即返 回,并且由EvtMask参数指向的变量被设置.
注意:WaitCommEvent只检测发生在等待开始后的事件.例如:如果指定 EV_RXCHAR事件,则只有当收到字符并将字符放进接收缓冲区后才能满足等待条件.监视CTS,DSR等信号状态改变的事件 时,WaitCommEvent函数只报告信号的变动,但不报告当前的信号状态,如果要查询这些信号状态,进程可以调用 GetCommModemstatus函数.
设备控制命令
一.控制握手信号
EscapeCommFunction()
函数可以将一个硬件信号置ON或OFF,或者模拟XON或XOFF字符的接收.发送字符时, EscapeCommFunction函数也可以置ON或OFF或清除终止条件.后一种功能与函数SetCommBreak()和 ClearCommBreak()的功能一致.
EscapeCommFunction()
函数的声明如下:
BOOL EscapeCommFunction(
HANDLE hFile, // handle to communications device
DWORD dwFunc //extended function to perform指定要执行的扩展功能的代码
);
注意:如果进程在通信设备 中已配置DTR握手信号有效,再使用EscapeCommFunction函数操作DTR信号线,或者配置RTS握手信号有效,操作RTS信号线会发生冲 突.
二.设备操作
在通信过程中使用 SetCommBreak()和ClearCommBreak()函数实现对通信设备的挂起和通信设备的恢复.
SetCommBreak()
函数实现暂停传输以及通信设备置为间断状态,直到调用了ClearCommBreak() 函数为止.该函数不清除尚未传输的数据.
SetCommBreak()
的函数的声明:
BOOL SetCommBreak(
HANDLE hFile // handle to communications device
);
ClearCommBreak函数的声明
BOOL ClearCommBreak(
HANDLE hFile // handle to communications device
);
因为如果通信设备是通过 SetCommBreak()或者EscapeCommFunction()函数设置成间断状态,进程将暂停字符传输,直到调用 ClearCommBreak函数来获取.
例子见TTY终端仿真程序
补充:
用到的串口通信编 程方法有:使用通信控件、在高级语言中嵌入汇编以及使用API函数。在这几种方法中,使用API函数编写的串口通信程序最为高效、灵活。串口通信编程将用 到三种API函数——串口通信相关API函数、多线程API函数和实现消息机制的API函数.
1 与串口通信有关的API函数
Windows系统通信一般都以 WOSA(Windows Open ServicesArchitecture,即Windows开放式服务体系)模型为基础,在此模型中位于上层的应用程序通过调用各种通信API与位于下 层的设备驱动程序进行数据交换。下面将按一般串口通信程序的流程顺序介绍这些API函数。
1.1 打开串口
Win32操作系统把串口看作一个文件,因此打 开串口我们将要用到CreateFile函数。该函数第一个参数指明要打开的文件名称,对串口操作来说,就是COM1、COM2等。第二个参数为读写模式 设置,因为要对串口进行读写,所以该参数应设为GENERIC_READ|GENERIC_WRITE。第三个参数值必须为0,表示不将串口与其他应用程 序共享。第四个参数指向一个SecurityAttribute结构,通常设为NULL。第五个参数指定如何打开文件,在打开设备(串口是一种设备)时, 此参数必须指定为OPEN_EXISTING。第六个参数指定文件属性及相关标 志,但是对于串行口,唯一有意义的设置是 FILE_FLAG_OVERLAPPED或0;最后一个参数必须为NULL。若该函数打开串口成功,则返回创建的句柄,该句柄供随后对串行口的设置、读 写等操作用;否则返回INVALID_HANDLE_VALUE。
1.2设置串口
串口打开后,即可进行一系列初始化设置。最基本的初始化设置将通过 GetCommState和SetCommState函数来实现。先调用GetCommState函数获取当前串口配置填充设备控制块(DCB),然后将 DCB结构中几个重要参数如波特率、数据位、停止位、校验位改成符合实际设计要求的值,最后用SetCommState将刚刚所做的改动重新设置串口。串 口I/O缓冲区的大小用SetupComm函数设置。通信速率越高,缓冲区应设置得越大,但不能超出设备驱动程序所能 处理的范围。另一个很重要的设置是串口超时设置。通信中因未知原因将出现不可预测的事件,譬如:接收数据过程中突然被中断,或者发送数据突然停止等。如果 不认真对待,这些情况可能会引起I/O线程挂起或者线程被无限阻塞。Windows对于这类问题提供了安全措施,它可通过超时设置来决定通信是否异常并作 相应处理,因此超时设置在串行通信中显得尤为重要。超时设置过程分为两步,首先设置Commtimeouts结构中的五个成员,然后调用 SetCommTimeouts函数设置超时值。Commtimeouts结构的五个成员分别是:读串口间隔超时、读串口总超时乘数、读串口总超时常数 (ms)、写串口总超时乘数、写串口总超时常数(ms)。
1.3读写串口
设置工作完成后,即可用ReadFile和WriteFile对串口进行读写操作。在 调用读写操作函数之前,应先用ClearCommError函数清除错误标志和获取当前串口状态。读写操作分为同步和重叠I/O(异步)。同步执行时,函 数直到操作完成后才返回;重叠I/O操作时,即使操作尚未完成,调用的函数也会立即返回,费时的I/O操作在后台进行。可见,同步操作线程被阻塞,效率 低,只能用在对通信要求比较低的场合,我们一般用到的都是效率较高的重叠I/O操作。前面提到的CreateFile函数第六个参数设置为 FILE_FLAG_OVERLAPPED即可指定ReadFile和WriteFile函数为重叠I/O执行。使用重叠I/O还需为读写函数指定一个 Overlapped结构,该结构有五个数据成员,对串口通信来说,其中的Offset和hEvent成员是很重要的。Offset指示文件指针偏移量, 在重叠I/O操作时系统不能自动维护文件指针,所以要靠Offset在程序中手动调整文件指针。而hEvent标志读写操作是否完成。若操作完成,则将 hEvent置为信号态;否则即置为非信号态。最后要说明的是,在重叠I/O操作时,读写函数返回值是FALSE并不能说明操作失败,应该调用 GetLastError函数分析返回结果。如果 此时GetLastError函数返回值是ERROR_IO_PENDING,则说明操作未完成(并不是操作失败)。我们将用等待函数来等待操作的完成。 典型的两个等待函数有WaitForSingleObject和GetOverlappedResult。函数的相同之处为都是等待读写操作指定的 Overlapped结构hEvent成员置为信号态(即代表操作完成);不同之处是WaitForSingleObject是可设置超时,但无法得到重 叠I/O操作的结果,GetOverlappedResult用来得到重叠I/O操作的结果,但无法设置超时。因此,我们经常两者结合起来使用,在用 WaitForSingleObject等待操作结束后,用GetOverlappedResult得到操作结果。
1.4关闭串口
串行口 是非共享资源,某应用程序打开串行口后,即独占该资源,使其它应用程序无法再访问,直到该应用程序释放串口。所以对串口操作完成后,一定要关闭串口。关闭 串口使用CloseHandle函数,该函数唯一参数即为用CreateFile打开串口时所创建的句柄。
2 多线程API函数
Windows是多线程(multi-threaded)、抢先多任务的(preemptible)。Windows中,一个可执行程序的运行时刻实例称 为进程(process)。一个进程可以有多个线程(thread),Windows是按照线程分配CPU时间片的,而分配的机制就是抢先多任务方式。
对于读写串口这种耗时的工作,使用多线程技术,创建辅助线程来管理串口是一个常用的方 案。这样在进行串口读写的同时,能对读入的数据进行处理。如果使用单线程,就需要等待串口读写操作完成,整个进程都被阻塞。而使用多线程就可以避免这种情 况。
多线程也会带来一些新的问题,其中的一个问题就是线程的同步,如果同步问 题解决不好,程序的稳定性会受到很大的影响。通常用到的几种线程同步的方法有互斥体对象(Mutex)、利用信号(Semaphore)、利用事件对象 (Event)和设置临界区(CriticalSetion)。笔者在实际应用中使用的是事件对象结合Windows消息机制使线程同步,收到了很好的效 果。
创建线程函数为CreateThread,用SuspendThread 和ResumeThread函数来挂起和唤醒线程。创建事件函数为CreateEvent,用SetEvent和ResetEvent函数来将事件置为信 号态和非信号态,以此来同步线程。串口通信的辅助线程管理经常还要用到SetCommMask和WaitCommEvent函数。SetCommMask 用来指定一系列事件监视串口,比如监视串口是否有数据收到;WaitCommEvent则用来等待指定的事件发生。笔者在实际应用中,就是在辅助线程中用 SetCommMask指定串口监视接收数据事件,然后用WaitCommEvent等到串口真的接收到数据时,用PostMessage发出消息通知主 线程,由主线程处理接收到的数据。
3 实现消息机制的API函数Windows是 一个消息驱动操作系统,简单的说 消息就是指通过输入设备向程序发出的指令以要求其执行某个操作。具体的操作由消息处理函数实现。用户可以自定义消息在线程之间传递。把WM_USER(它 的值等于0X0400)当作基数,然后顺序地去加序号,譬如:
WM_COMMNOTIFY equ WM_USER+100h(小于WM_USER的值是Windows系统的保留值,大于该值留给用户来使用)。
4 结束语
利 用多线程、消息机制和重叠I/O的API函数进行串口编程的方法,可实现串口通信的实时高效,为开发Windows系统下串口驱动程序提供了有益参考。
没有评论:
发表评论