Windows串口通信(下)


转载请注明出处

作者:小马

 

发送数据

 往串口发送数据用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也要传进一个指向OVERLAPPED变量的指针, 加上对bWait参数的描述, 很明显,这个函数跟WaitForSingleObject功能基本相同, 是的,在串口应用中,它们两个的用法基本相同, 当bWait为TRUE时, 跟WaitForSingleObject的第二个参数用INFINITE效果是一样的. 


但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


你可能感兴趣的:(Windows串口通信(下))