转载请注明出处
作者:小马
发送数据
往串口发送数据用writefile, 有如下几个参数:
HANDLE hFile 之前用CreateFile打开的有效的串口句柄. LPCVOID lpBuffer 待发送的数据缓冲区. 把要发送的数据放在这里. DWORD nNumberOfBytesToWrite 这里指定要发送的字节数, 可以比发送缓冲区总字节数小,具体看实际应用中要发多少. LPDWORD lpNumberOfBytesWritten 该值由函数返回, 指明函数返回时,实际发送成功的字节数. 这个值很有用, 有些时候,函数返回TRUE, 但由于一些原因(比如超时), 实际发送的少于nNumberOfBytesToWrite, 这种情况要继续发送. LPOVERLAPPED lpOverlapped 这个参数指出当前发送数据,是用异步,还是用同步. 如果是前者, 传进一个指向OVERLAPPED变量的指针, 如果是后者,该值为NULL.
在同步模式下, 发送数据是比较简单的. 可以用类似下面的形式:
If(!WriteFile(hComm, buffer, strlen(buffer), &dwBytesWritten, NULL)) { //错误处理. }
一般情况下,WriteFile都会返回TRUE,数据就会按指定的长度发送成功. 如果想更安全一些,也可以加入类似下面这样的判断.
If(dwBytesWritten != strlen(buffer)) { //错误处理 }
在同步模式下,WriteFile会一直等待指定的数据发送完毕,直到超时,如果未指定超超时间,数据发送完毕之前,程序会一直挂起.
异步模式下的处理稍复杂一些. 首先, 如果WriteFile返回FALSE,不一定表示发送失败, 可以用GetLastError获取错误码,如果错误码是ERROR_IO_PENDING,表示操作正在进行, 这种不是真正的失败. 在错误码是ERROR_IO_PENDING的情况下, 可以用WaitForSingleObject等待操作完成.
如果你做过windows下的多线程应用,应该比较熟悉这个函数. 通过串口发送数据时, 这个函数类似下面这样用:
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE); switch(dwRes) { // OVERLAPPED structure's event has been signaled. case WAIT_OBJECT_0: //处理 break; default: //错误处理 break; }
第一个参数osWrite.hEvent是要等待的事件, 第二个参数是超时时间. 要注意这个超时时间并不是前面讲到的读写超时时间. 它是WaitForSingleObject等待osWrite.hEvent变为有信号的时间.
举个例子,如果设置的写超超时间是5秒, WaitForSingleObject第二个参数用INFINITE, 如果5秒写操作还没有完成,这时出现写超时, 但WaitForSingleObject返回的是WAIT_OBJECT_0,而不是WAIT_TIMEOUT. 因为对WaitForSingleObject来说, 写操作已经完成了.为了避免混淆,建议WaitForSingleObject的第二个参数用INFINITE.
还有一点要注意, WaitForSingleObject返回WAIT_OBJECT_0, 并不表示发送是成功的, 只说明发送数据的操作完成了. 是否成功, 还要通过另一个函数GetOverlappedResult来判断. 这个函数有必要说明一下, 它有如下几个参数:
HANDLE hFile 串口句柄 LPOVERLAPPED lpOverlapped 指向OVERLAPPED变量的指针 LPDWORD lpNumberOfBytesTransferred 这个值返回实际发送的字节数 BOOL bWait 如果该值是TRUE,这个函数会一直等待操作完成才返回, 如果是FALSE, 该函数立即返回, 如果这个时候操作还没有完成, 函数返回FALSE, 用GetLastError可以获取错误码是ERROR_IO_INCOMPLETE
但GetOverlappedResult可以判读操作是否成功, 方法就是当WaitForSingleObject返回WAIT_OBJECT_0时,调用GetOverlappedResult,如果它返回TRUE就表示操作成功完成, 否则表示操作失败. 调用过程类似下面的形式:
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE); switch(dwRes) { case WAIT_OBJECT_0: if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, FALSE)) //操作失败 else //操作成功完成 break; default: // An error has occurred in WaitForSingleObject. //错误处理 break; }
这样,在异步模式下的写操作, 就可以用类似下面这样的语句来实现:
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite)) { if (GetLastError() != ERROR_IO_PENDING) { //真正的出错情况 fRes = FALSE; } else// 操作还未完成, 等待操作完成 dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE); switch(dwRes) { case WAIT_OBJECT_0:// 写操作完成, 但不确定是否成功? if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, FALSE)) { //操作失败,错误处理 } else //操作成功 break; default: //错误处理 break; } } }
有时候看到一些串口通信的代码并没有上面那样繁琐的判断. 有些只用了WaitForSingleObject,或者只用了GetOverlappedResult, 其实也是可以的, 有些简单的应用场合并没有必要做这么细致的流程设计, 上述流程是MSDN推荐的,应该算是比较完整和健壮的.
接收数据
接收数据的操作跟发送数据差别不大,在接收数据之前, 一般会先用ClearCommError读取接收缓冲区中有多少未读的数据(未用ReadFile读取), 然后读取这个长度的数据. 如果读到的数据长度小于自己想要读的长度,则继续读.
ClearCommError获取接收缓冲区未读数据原理是传出一个指向COMSTAT 的参数变量, COMSTAT中的cbInQ ue成员就是当前未被读出的接收到的数据长度.
这里有一个问题要讨论一下, 系统什么时候置cbInQue不为零呢, 答案是, 它跟前面讲到的读字节超时间隔有关系, 它的机制是几个byte超时时间内没有收到数据,认为这一次接收完成,然后用当前接收到的字节数置cbInQue.
举个例子, 设备返回给主机的字节一共有10个, 每隔100ms返回一个, 如果, ReadIntervalTimeout设置为10ms, 那么, 主机读到一个字节就会置cbInQue为1, 如果ReadIntervalTimeout设置大一些,比如100ms, 那么主机会读完10个字节才会置cbInQue为10.实际应用中,ReadIntervalTimeout应该设置大一些还是小一些要根据不同应用场合. 明白了原理,就可以灵活的应对各种应用
其实对于串口通信, 还有更复杂的高阶应用,在这些应用中会涉及到多线程, 窗体的消息传递与通知(与MFC结合)等, 如果有时间,后续我会接着写相关的文章. 但用上面讲到的那些,设计一个简单实用的串口通信类应该不成问题.
源码下载地址:
https://github.com/pony-maggie/LKE_lke2600_CardReader