1、串行通信与重叠 I/OWin 32 系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都是用 API 函数 CreateFile 来打开或创建的。该函数的声明为: HANDLE CreateFile( LPCTSTR lpFileName, / 文件名 DWORD dwDesiredAccess, / 访问模式 DWORD dwShareMode, / 共享模式 LPSECURITY_ATTRIBUTES lpSecurityAttributes, / 通常为 NULL DWORD dwCreationDistribution, / 创建方式 DWORD d
2、wFlagsAndAttributes, / 文件属性和标志 HANDLE hTemplateFile / 临时文件的句柄,通常为 NULL ); 如果调用成功,那么该函数返回文件的句柄,如果调用失败,则函数返回INVALID_HANDLE_VALUE。 在打开通信设备句柄后,常常需要对串行口进行一些初始化工作。这需要通过一个 DCB结构来进行。DCB 结构包含了诸如波特率、每个字符的数据位数、奇偶校验和停止位数等信息。在查询或配置置串行口的属性时,都要用 DCB 结构来作为缓冲区。 调用 GetCommState 函数可以获得串口的配置,该函数把当前配置填充到一个 DCB 结构中。一般在用
3、CreateFile 打开串行口后,可以调用 GetCommState 函数来获取串行口的初始配置。要修改串行口的配置,应该先修改 DCB 结构,然后再调用 SetCommState 函数用指定的 DCB 结构来设置串行口。 除了在 DCB 中的设置外,程序一般还需要设置 I/O 缓冲区的大小和超时。Windows用 I/O 缓冲区来暂存串行口输入和输出的数据,如果通信的速率较高,则应该设置较大的缓冲区。调用 SetupComm 函数可以设置串行口的输入和输出缓冲区的大小。 在用 ReadFile 和 WriteFile 读写串行口时,需要考虑超时问题。如果在指定的时间内没有读出或写入指定数量
4、的字符,那么 ReadFile 或 WriteFile 的操作就会结束。要查询当前的超时设置应调用 GetCommTimeouts 函数,该函数会填充一个 COMMTIMEOUTS 结构。调用 SetCommTimeouts 可以用某一个 COMMTIMEOUTS 结构的内容来设置超时。 有两种超时:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延,总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。用 COMMTIMEOUTS 结构可以规定读/写操作的超时,该结构的定义为: typedef struct _COMMTIMEOUTS DWORD Rea
5、dIntervalTimeout; / 读间隔超时 DWORD ReadTotalTimeoutMultiplier; / 读时间系数 DWORD ReadTotalTimeoutConstant; / 读时间常量 DWORD WriteTotalTimeoutMultiplier; / 写时间系数 DWORD WriteTotalTimeoutConstant; / 写时间常量 COMMTIMEOUTS,*LPCOMMTIMEOUTS; COMMTIMEOUTS 结构的成员都以毫秒为单位。总超时的计算公式是: 总超时=时间系数 要求读/写的字符数 + 时间常量 例如,如果要读入 10 个字符
6、,那么读操作的总超时的计算公式为: 读总超时ReadTotalTimeoutMultiplier10 + ReadTotalTimeoutConstant 可以看出,间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。 如果所有写超时参数均为 0,那么就不使用写超时。如果 ReadIntervalTimeout 为 0,那么就不使用读间隔超时,如果 ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都为 0,则不使用读总超时。如果读间隔超时被设置成 MAXDWORD 并且两个读总超时为0,那么在读一次输入缓冲区中的内
7、容后读操作就立即完成,而不管是否读入了要求的字符。在用重叠方式读写串行口时,虽然 ReadFile 和 WriteFile 在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是 ReadFile和 WriteFile 的返回时间。 在用 ReadFile 和 WriteFile 读写串行口时,既可以同步执行,也可以重叠(异步)执行。在同步执行时,函数直到操作完成后才返回。这意味着在同步执行时线程会被阻塞,从而导致效率下降。在重叠执行时,即使操作还未完成,调用的函数也会立即返回。费时的 I/O操作在后台进行,这样线程就可以干别的事情。例如,线程可以在不
8、同的句柄上同时执行I/O 操作,甚至可以在同一句柄上同时进行读写操作。 “重叠 ”一词的含义就在于此。 ReadFile 函数只要在串行口输入缓冲区中读入指定数量的字符,就算完成操作。而WriteFile 函数不但要把指定数量的字符拷入到输出缓冲中,而且要等这些字符从串行口送出去后才算完成操作。 ReadFile 和 WriteFile 函数是否为执行重叠操作是由 CreateFile 函数决定的。如果在调用 CreateFile 创建句柄时指定了 FILE_FLAG_OVERLAPPED 标志,那么调用 ReadFile 和WriteFile 对该句柄进行的读写操作就是重叠的,如果未指定重叠
9、标志,则读写操作是同步的。 函数 ReadFile 和 WriteFile 的参数和返回值很相似。这里仅列出 ReadFile 函数的声明:BOOL ReadFile( HANDLE hFile, / 文件句柄 LPVOID lpBuffer, / 读缓冲区 DWORD nNumberOfBytesToRead, / 要求读入的字节数 LPDWORD lpNumberOfBytesRead, / 实际读入的字节数 LPOVERLAPPED lpOverlapped / 指向一个 OVERLAPPED 结构 ); /若返回 TRUE 则表明操作成功 需要注意的是如果该函数因为超时而返回,那么返回
10、值是 TRUE。参数 lpOverlapped在重叠操作时应该指向一个 OVERLAPPED 结构,如果该参数为 NULL,那么函数将进行同步操作,而不管句柄是否是由 FILE_FLAG_OVERLAPPED 标志建立的。 当 ReadFile 和 WriteFile 返回 FALSE 时,不一定就是操作失败,线程应该调用GetLastError 函数分析返回的结果。例如,在重叠操作时如果操作还未完成函数就返回,那么函数就返回 FALSE,而且 GetLastError 函数返回 ERROR_IO_PENDING。 在使用重叠 I/O 时,线程需要创建 OVERLAPPED 结构以供读写函数使
11、用。OVERLAPPED 结构最重要的成员是 hEvent,hEvent 是一个事件对象句柄,线程应该用CreateEvent 函数为 hEvent 成员创建一个手工重置事件, hEvent 成员将作为线程的同步对象使用。如果读写函数未完成操作就返回,就那么把 hEvent 成员设置成无信号的。操作完成后(包括超时) ,hEvent 会变成有信号的。 如果 GetLastError 函数返回 ERROR_IO_PENDING,则说明重叠操作还为完成,线程可以等待操作完成。有两种等待办法:一种办法是用象 WaitForSingleObject 这样的等待函数来等待 OVERLAPPED 结构的
12、hEvent 成员,可以规定等待的时间,在等待函数返回后,调用 GetOverlappedResult。另一种办法是调用 GetOverlappedResult 函数等待,如果指定该函数的 bWait 参数为 TRUE,那么该函数将等待 OVERLAPPED 结构的 hEvent 事件。GetOverlappedResult 可以返回一个 OVERLAPPED 结构来报告包括实际传输字节在内的重叠操作结果。 如果规定了读/写操作的超时,那么当超过规定时间后,hEvent 成员会变成有信号的。因此,在超时发生后,WaitForSingleObject 和 GetOverlappedResult
13、都会结束等待。WaitForSingleObject 的 dwMilliseconds 参数会规定一个等待超时,该函数实际等待的时间是两个超时的最小值。注意 GetOverlappedResult 不能设置等待的时限,因此如果 hEvent 成员无信号,则该函数将一直等待下去。 在调用 ReadFile 和 WriteFile 之前,线程应该调用 ClearCommError 函数清除错误标志。该函数负责报告指定的错误和设备的当前状态。 调用 PurgeComm 函数可以终止正在进行的读写操作,该函数还会清除输入或输出缓冲区中的内容。/.2 调用 Win32 API 函数实现串行通信编程Win
14、dows 操作系统对系统底层操作采取了屏蔽的策略,禁止应用程序直接访问计算机I/O 端口,而由设备驱动程序统一管理,Windows 封装了 Windows 的通信机制,这种方式称为通信应用程序接口 API(Application Programming Interfaces)。Windows 9x/NT/2000 提供的 API 一般都支持 32 位的操作,又称为 Win32 API,程序员可以利用 Win32 API 的通信函数进行编程,不用对硬件直接进行操作,使得应用程序的编制更加方便。2.2.1 Win32 API 常用通信函数在进行串口通信时,经常需要用到下列一些 API 函数:Cre
15、ateFile():用于打开一个文件访问串口;GetCommState():获取串口的当前配置,放入设备控制块 DCB 中;SetCommState():根据 DCB 重新配置串口参数;SetCommTimeouts():设置串口读写操作的溢出时间;ReadFile():从串口的输入缓冲区读取数据;WriteFile():向串口的输出缓冲区写入数据;SetCommMask():监视指定通信资源上的事件;WaitCommEvent():等待通信事件发生;CloseHandle():关闭由 CreateFile 函数打开的串口。以上这些函数的原形可在参考文献1中找到。1-在 C+ Builder
16、6.0 下基于 api 函数编写串口通信程序简介: 在 dos/win95/win98 的年代,操作系统对串口是不保护的,也就是说将串口的的资源完全开放给用户,用户可以用直接操作硬件的函数(比如说 TC2.0 下的 inport()和 outport()函数) 跟串口直接打交道,这时候用户使用直接操作串口的函数怎样“折磨“ 串口都是没有问题的,操作系统根本就不管不问,对串口操作所造成的一切后果都是用户一个 人承担的,这时候用户对串口具有高度自由的支配权;但是,这种情况好景不长,从 win2000 操作系统开始,微软为了“照顾好 “计算机上的硬件,开始实施 了对硬件的保护策略,也就是说任何用户在
17、他的操作系统下企图操纵串口时必须经过他的同意方可进行,其实也就是变相的将用户往必须使用他的通信 api 函数才 能操作串口这条“羊肠小路“ 上赶(当然也有别的方法操作串口,但那些并非我等普通用户能研究明白的),形象一点说就好像你想怎样操作串口的意图必须经过 win2000 的翻译(其实是 win2000 的设备驱动程序)才能转达给串口一样,基于这一点我们说(其实是很多资料上说的 )win2000 下通过 api 函数操作串口是具有“设备无关性的“ ,什么意思呢?就是说你想怎样操作串口就用相应的 api 函数告诉操作系统你想对串口干什么,然后操作系统就把你的意思 转告给串口让其做出相应的动作,相
18、对于dos/win95/win98 下来说,据我理解也就相当于你原来写的直接操作串口的函数在 win2000下他替你 完成了,但是你必须用 win2000 通信 api 函数清楚地向操作系统表达清楚你到底想干什么,所以说在这种情况下要想写好串口驱动程序你就必须至少弄明白 win2000 下的通信 api 函数都是干什么的方可,啰里啰唆唠叨了这么多. .sorry, 还没完呢,至少还有一件事我想说,原来在 dos/win95/win98 系统下有好多高手用 c/c+对串口进行直接操作是非常熟练的,尤其是 dos 时代 的 turbo 2.0 操作串口的高手他们写的串口驱动程序直到 win98的时
19、候还用的非常洋洋得意,但是到了 win2000 的时候,他们的程序突然不好使了,而他们有的 可能还会因为知识结构上的滞后始终弄不明白怎么回事儿,兄弟们,你们该明白了吧?闲话少叙,下面介绍笔者写串口通信函数时用到的各个 api 函数- - 2-CreateFile() 用途:打开串口 原型:HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDistribution, D
20、WORD dwFlagsAndAttributes, HANDLE hTemplateFile); 参数说明: -lpFileName:要打开的文件名称。对串口通信来说就是 COM1 或 COM2。 -dwDesiredAccess:读写模式设置。此处应该用 GENERIC_READ 及 GENERIC_WRITE。 -dwShareMode:串口共享模式。此处不允许其他应用程序共享,应为 0。 -lpSecurityAttributes:串口的安全属性,应为 0,表示该串口不可被子程序继承。 -dwCreationDistribution:创建文件的性质,此处为 OPEN_EXISTING.
21、 -dwFlagsAndAttributes:属性及相关标志,这里使用异步方式应该用FILE_FLAG_OVERLAPPED。 -hTemplateFile:此处为 0。 操作说明:若文件打开成功,串口即可使用了,该函数返回串口的句柄,以后对串口操作时 即可使用该句柄。 举例:HANDLE hComm; hComm=CreateFile(“COM1“, /串口号 GENERIC_READ|GENERIC_WRITE, /允许读写 0, /通讯设备必须以独占方式打开 NULL, /无安全属性 OPEN_EXISTING, /通讯设备已存在 FILE_FLAG_OVERLAPPED, /异步 I/
22、O 0); /通讯设备不能用模板打开 hComm 即为函数返回的串口 1 的句柄。3-CloseHandle() 用途:关闭串口 原型:BOOL CloseHandle(HANDLE hObjedt) 参数说明: -hObjedt:串口句柄 操作说明:成功关闭串口时返回 true,否则返回 false 举例:CloseHandle(hComm); 4-GetCommState() 用途:取得串口当前状态 原型:BOOL GetCommState(HANDLE hFile, LPDCB lpDCB); 参数说明: -hFile:串口句柄 -lpDCB:设备控制块(Device Control B
23、lock)结构地址。此结构中含有和设备相关的 参数。此处是与串口相关的参数。由于参数非常多,当需要设置串口参数 时,通常是先取得串口的参数结构,修改部分参数后再将参数结构写入。 在此仅介绍少数的几个常用的参数: DWORD BaudRate:串口波特率 DWORD fParity:为 1 的话激活奇偶校验检查 DWORD Parity:校验方式,值 04 分别对应无校验、奇校验、偶校验、校验 置位、校验清零 DWORD ByteSize:一个字节的数据位个数,范围是 58 DWORD StopBits:停止位个数,02 分别对应 1 位、1.5 位、2 位停止位 操作举例:DCB ComDCB
24、; /串口设备控制块 GetCommState(hComm, 5-SetCommState() 用途:设置串口状态,包括常用的更改串口号、波特率、奇偶校验方式、数据位数等 原型:BOOL SetCommState(HANDLE hFile, LPDCB lpDCB); 参数说明: -hFile:串口句柄 -lpDCB:设备控制块(Device Control Block)结构地址。要更改的串口参数包含在此结构中。操作举例:DCB ComDCB; GetCommState(hComm,/取得当前串口状态 ComDCB.BaudRate=9600;/更改为 9600bps,该值即为你要修改后的波特
25、率 SetCommState(hComm,/将更改后的参数写入串口 6-WriteFile() 用途:向串口写数据 原型:BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped); 参数说明: -hFile:串口句柄 -lpBuffer:待写入数据的首地址 -nNumberOfBytesToWrite:待写入数据的字节数长度 -lpNumberOfBytesWritten:函数返回的实
26、际写入串口的数据个数的地址,利用此变量可判断 实际写入的字节数和准备写入的字节数是否相同。 -lpOverlapped:重叠 I/O 结构的指针 操作举例:DWORD BytesSent=0; unsigned char SendBytes5=1,2,3,4,5; OVERLAPPED ov_Write; ov_Write.Offset=0; ov_Write.OffsetHigh=0; WriteFile(hComm, /调用成功返回非零,失败返回零 SendBytes, /输出缓冲区 5, /准备发送的字符长度 /重叠结构 如果函数执行成功的话检查 BytesSent 的值应该为 5,此函
27、数是 WriteFile 函数执行完毕后 自行填充的,利用此变量的填充值可以用来检查该函数是否将所有的数据成功写入串口7-ReadFile() 用途:读串口数据 原型:BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped); 参数说明: -hFile:串口句柄 -lpBuffer:存储被读出数据的首地址 -nNumberOfBytesToRead:准备读出的字节个数 -NumberOfBytesRead:实际读出的字节个数 -lpOve
28、rlapped:异步 I/O 结构, 操作举例:unsigned char ucRxBuff20; COMSTAT ComStat; DWORD dwError=0; DWORD BytesRead=0; OVERLAPPED ov_Read; ov_Read.hEvent=CreateEvent(NULL, true, false, NULL);/必须创建有效事件 ClearCommError(hComm,/检查串口接收缓冲区中的数据个数 bResult=ReadFile(hComm, /串口句柄 ucRxBuff, /输入缓冲区地址 ComStat.cbInQue, /想读入的字符数 /重
29、叠结构指针 假如当前串口中有 5 个字节数据的话,那么执行完 ClearCommError()函数后,ComStat 结构中的 ComStat.cbInQue 将被填充为 5,此值在 ReadFile 函数中可被直接利用。 8-ClearCommError() 用途:清除串口错误或者读取串口现在的状态 原型:BOOL ClearCommError(HANDLE hFile, LPDWORD lpErrors, LPCOMATAT lpStat ); 参数说明: -hFile:串口句柄 -lpErrors:返回错误数值,错误常数如下: 1-CE_BREAK:检测到中断信号。意思是说检测到某个字节
30、数据缺少合法的停止位。 2-CE_FRAME:硬件检测到帧错误。 3-CE_IOE:通信设备发生输入/ 输出错误。 4-CE_MODE:设置模式错误,或是 hFile 值错误。 5-CE_OVERRUN:溢出错误,缓冲区容量不足,数据将丢失。 6-CE_RXOVER:溢出错误。 7-CE_RXPARITY:硬件检查到校验位错误。 8-CE_TXFULL:发送缓冲区已满。 -lpStat:指向通信端口状态的结构变量,原型如下: typedef struct _COMSTAT . . DWORD cbInQue; /输入缓冲区中的字节数 DWORD cbOutQue;/输出缓冲区中的字节数 COM
31、STAT,*LPCOMSTAT; 该结构中对我们很重要的只有上面两个参数,其他的我们可以不用管。 操作举例:COMSTAT ComStat; DWORD dwError=0; ClearCommError(hComm, 上式执行完后,ComStat.cbInQue 就是串口中当前含有的数据字节个数,我们利用此 数值就可以用 ReadFile()函数去读串口中的数据了。9-PurgeComm() 用途:清除串口缓冲区 原型:BOOL PurgeComm(HANDLE hFile, DWORD dwFlags ); 参数说明: -hFile:串口句柄 -dwFlags:指定串口执行的动作,由以下参
32、数组成: -PURGE_TXABORT:停止目前所有的传输工作立即返回不管是否完成传输动作。 -PURGE_RXABORT:停止目前所有的读取工作立即返回不管是否完成读取动作。 -PURGE_TXCLEAR:清除发送缓冲区的所有数据。 -PURGE_RXCLEAR:清除接收缓冲区的所有数据。 操作举例:PurgeComm(hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT); 清除串口的所有操作。 10-SetCommMask() 用途:设置串口通信事件。 原型:BOOL SetCommMask(HANDLE hFile
33、, DWORD dwEvtMask ); 参数说明: -hFile:串口句柄 -dwEvtMask:准备监视的串口事件掩码 注:在用 api 函数撰写串口通信函数时大体上有两种方法,一种是查寻法,另外一种是事件通知法。 这两种方法的区别在于收串口数据时,前一种方法是主动的周期性的查询串口中当前有没有 数据;后一种方法是事先设置好需要监视的串口通信事件,然后依靠单独开设的辅助线程进行 监视该事件是否已发生,如果没有发生的话该线程就一直不停的等待直到该事件发生后,将 该串口事件以消息的方式通知主窗体,然后主窗体收到该消息后依据不同的事件性质进行处理。 比如说当主窗体收到监视线程发来的 RX_CHA
34、R(串口中有数据)的消息后,就可以用ReadFile() 函数去读串口。该参数有如下信息掩码位值: EV_BREAK:收到 BREAK 信号 EV_CTS:CTS(clear to send)线路发生变化 EV_DSR:DST(Data Set Ready)线路发生变化 EV_ERR:线路状态错误,包括了 CE_FRAMECE_OVERRUNCE_RXPARITY 3 钟错误。 EV_RING:检测到振铃信号。 EV_RLSD:CD(Carrier Detect)线路信号发生变化。 EV_RXCHAR:输入缓冲区中已收到数据。 EV_RXFLAG:使用 SetCommState()函数设置的
35、DCB 结构中的等待字符已被传入输入缓冲区中。 EV_TXEMPTY:输出缓冲区中的数据已被完全送出。 操作举例:SetCommMask(hComm,EV_RXCHAR|EV_TXEMPTY); 上面函数执行完毕后将监视串口中有无数据和发送缓冲区中的数据是否全部发送完毕。 11-WaitCommEvent() 用途:用来判断用 SetCommMask()函数设置的串口通信事件是否已发生。 原型:BOOL WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped ); 参数说明: -hFile:串口句柄 -l
36、pEvtMask:函数执行完后如果检测到串口通信事件的话就将其写入该参数中。 -lpOverlapped:异步结构,用来保存异步操作结果。 操作举例:OVERLAPPED os; DWORD dwMask,dwTrans,dwError=0,err; memset( os.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); if(!WaitCommEvent(hComm, switch(dwMask) case EV_RXCHAR: PostMessage(Parent,WM_COMM_RXCHAR,0,0); break; case EV_TXEMPTY: P
37、ostMessage(Parent,WM_COMM_TXEMPTY,0,0); break; case EV_ERR: switch(dwError) case CE_FRAME: err=0; break; case CE_OVERRUN: err=1; break; case CE_RXPARITY: err=2; break; default:break; PostMessage(Parent,WM_COMM_ERR,(WPARAM)0,(LPARAM)err); break; case EV_BREAK: PostMessage(Parent,WM_COMM_BREAK,0,0); b
38、reak; case .:/其他用 SetCommMask()函数设置的被监视的串口通信事件。 . . break; default:break; /Win32 串口编程金贝贝 一、基本知识 Win32 下串口通信与 16 位串口通信有很大的区别。在 Win32 下,可以使用两种编程方式实现串口通信,其一是调用的 Windows 的 API 函数,其二是使用 ActiveX 控件。使用API 调用,可以清楚地掌握串口通信的机制,熟悉各种配置和自由灵活采用不同的流控进行串口通信。下面介绍串口操作的基本知识。 打开串口:使用 CreateFile()函数,可以打开串口。有两种方法可以打开串口,一种
39、是同步方式(NonOverlapped),另外一种异步方式(Overlapped) 。使用 Overlapped 打开时,适当的方法是: HANDLE hComm;hComm = CreateFile( gszPort, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);if (hComm = INVALID_HANDLE_VALUE)/ error opening port; abort配置串口: 1.DCB 配置 DCB(Device Control Block)结构定义了串口通信设备的控制设
40、置。许多重要设置都是在 DCB 结构中设置的,有三种方式可以初始化 DCB。 (1)通过 GetCommState()函数得 DCB 的初始值,其使用方式为: DCB dcb = 0;if (!GetCommState(hComm, dcb)/ Error getting current DCB settingselse/ DCB is ready for use.(2)用 BuildCommDCB()函数初始化 DCB 结构,该函数填充 DCB 的波特率、奇偶校验类型、数据位、停止位。对于流控成员函数设置了缺省值。其用法是: DCB dcb;FillMemory(dcb, sizeof(dc
41、b), 0);dcb.DCBlength = sizeof(dcb);if (!BuildCommDCB(“9600,n,8,1“, dcb) / Couldnt build the DCB. Usually a problem/ with the communications specification string.return FALSE;else/ DCB is ready for use.(3)用 SetCommState()函数手动设置 DCB 初值。用法如下: DCB dcb;FillMemory(dcb, sizeof(dcb), 0);if (!GetCommState(hC
42、omm, dcb) / get current DCB/ Error in GetCommStatereturn FALSE;/ Update DCB rate.dcb.BaudRate = CBR_9600 ;/ Set new state.if (!SetCommState(hComm, dcb)/ Error in SetCommState. Possibly a problem with the communications / port handle or a problem with the DCB structure itself.手动设置 DCB 值时,DCB 的结构的各成员的
43、含义,可以参看 MSDN 帮助。 2.流控设置 硬件流控:串口通信中的硬件流控有两种,DTE/DSR 方式和 RTS/CTS 方式,这与DCB 结构的初始化有关系,DCB 结构中的 OutxCtsFlow、 fOutxDsrFlow、fDsrSensitivity、fRtsControl、fDtrControl 几个成员的初始值很关键,不同的值代表不同流控,也可以自己设置流控,但建议采用标准流行的流控方式。采用硬件流控时,DTE、DSR、RTS、CTS 的逻辑位直接影响到数据的读写及收发数据的缓冲区控制。软件流控:串口通信中采用特殊字符 XON 和 XOFF 作为控制串口数据的收发。与此相关的
44、 DCB 成员是:fOut、fInX 、XoffChar 、XonChar、 XoffLim 和 XonLim。具体含义参见 MSDN 帮助。 串口读写操作:串口读写有两种方式:同步方式(NonOverlapped)和异步方式(Overlapped) 。同步方式是指必须完成了读写操作,函数才返回,这可能造成程序死掉,因为如果在读写时发生了错误,永远不返回就会出错,可能线程将永远等待在那儿。而异步方式则灵活得多,一旦读写不成功,就将读写挂起,函数直接返回,可以通过GetLastError 函数得知读写未成功的原因,所以常常采用异步方式操作。 读操作:ReadFile()函数用于完成读操作。异步方
45、式的读操作为: DWORD dwRead;BOOL fWaitingOnRead = FALSE;OVERLAPPED osReader = 0;/ Create the overlapped event. Must be closed before exiting/ to avoid a handle leak.osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);if (osReader.hEvent = NULL)/ Error creating overlapped event; abort.if (!fWaitingOnRead
46、) / Issue read operation.if (!ReadFile(hComm, lpBuf, READ_BUF_SIZE,dwRead, osReader) if (GetLastError() != ERROR_IO_PENDING)/ read not delayed?/ Error in communications; report it.elsefWaitingOnRead = TRUE;else / read completed immediatelyHandleASuccessfulRead(lpBuf, dwRead);如果读操作被挂起,可以调用 WaitForSin
47、gleObject()函数或 WaitForMuntilpleObjects()函数等待读操作完成或者超时发生,再调用 GetOverlappedResult()得到想要的信息。 写操作:与读操作相似,故不详述,调用的 API 函数是: WriteFile 函数。 串口状态: (1)通信事件:用 SetCommMask()函数设置想要得到的通信事件的掩码,再调用WaitCommEvent()函数检测通信事件的发生。可设置的通信事件标志(即 SetCommMask()函数所设置的掩码)可以有 EV_BREAK、EV_CTS、EV_DSR、 EV_ERR、EV_RING 、EV_RLSD 、EV_
48、RXCHAR、EV_RXFLAG、EV_TXEMPTY。 注意:1 对于 EV_RING 标志的设置,WIN95 是不会返回 EV_RING 事件的,因为WIN95 不检测该事件。 2 设置 EV_RXCHAR,可以检测到字符到达,但是在绑定此事件和ReadFile()函数一起读取串口接收数据时,可能会出现错误,造成少读字节数,具体原因查看 MSDN 帮助。可以采用循环读的办法,另外一个比较好的解决办法是调用ClearCommError()函数,确定在一次读操作中在缓冲区中等待被读的字节数。 (2)错误处理和通信状态:在串口通信中,可能会产生很多的错误,使用ClearCommError()函数
49、可以检测错误并且清除错误条件。 (3)Modem 状态:用 SetcommMask()可以包含很多事件标志,但是这些事件标志只指示在串口线路上的电压变化情况。而调用 GetCommModemStatus()函数可以获得线路上真正的电压状态。 扩展函数:如果应用程序想用自己的流控,可以使用 EscapeCommFunction()函数设置 DTR 和 RTS 线路的电平。 通信超时:在通信中,超时是个很重要的考虑因素,因为如果在数据接收过程中由于某种原因突然中断或停止,如果不采取超时控制机制,将会使得 I/O 线程被挂起或无限阻塞。串口通信中的超时设置分为两步,首先设置 COMMTIMEOUTS 结构的五个变量,然后调用 SetcommTimeouts()设置超时值。对于使用异步方式读写的操作,如果操作挂起后,异步成功完成了