• 串口通信协议


    一、串口API

    1.  打开串口

      使用CreateFile函数可以打开串口。通常有两种方式可以打开,一种是同步方式(NonOverlapped),另外一种异步方式(Overlapped)。

            HANDLE hComm;

            hComm = CreateFile(   gszPort,                                                    //串口名

                                               GENERIC_READ|GENERIC_WRITE          //读写

                                               0,                        //注意:串口为不可共享设备,本参数须为0

                                               0,

                                               OPEN_EXISTING,

                                               FILE_FLAG_OVERLAPPED,                      //异步方式

                                               0);

            if(hComm == INVALID_HANDLE_VALUE)     //打开串口失败处理

                   ······

    2.   配置串口

      DCB(Device Control Block)结构定义了串口通信设备的控制设置,有3种方式可以初始化DCB。


    通过GetCommState函数得到DCB的初始值:

          DCB dcb;

                   memset(&dcb, 0, sizeof(dcb));

                   if(!GetCommState(hComm, &dcb))     ……        //错误处理

                   else ……                                                         //已准备就绪


    用BuildCommDCB函数初始化DCB结构:

          DCB dcb;

          memset(&dcb, 0, sizeof(dcb));

          dcb.DCBlength = sizeof(dcb);

          if(!BuildCommDCB(“9600,n,8,1”,  &dcb))       ……     //参数配置错误

          else ……                                                                //已准备就绪


    用SetCommState函数手动设置DCB初值:

          DCB dcb;

          memset(&dcb, 0, sizeof(dcb));

          if(!GetCommState(hComm, &dcb))     return FALSE;

          dcb.BaudRate = CBR_9600;

    3.  流控设备

      流控制有如下两种设置:


    硬件流控制:硬件流控有两种,DTE/DSR方式和RTS/CTS方式。这与DCB结构的初始化有关系,建议采用标准流行的流控方式,采用硬件流控时,DTE、DSR、RTS、CTS的逻辑位直接影响到数据的读写及收发数据的缓冲区控制。
    软件流控制:串口通信中采用特殊字符XON和XOFF作为控制串口数据的收发。

    注意:在不设置流控制方式或软件流控的情况下,基本上不会出现什么问题,但在硬件流控下,规范的RTS_CONTROL_HANDSHAKE流控方式的含义本来是当缓冲区快满的时候RTS会自动OFF通知对方暂停发送,当缓冲区重新空出来的时候,RTS会自动ON,但很多时候当RTS变OFF以后即使已经清空了缓冲区,RTS也不会自动的ON,造成对方停在那里不发送了。所以,如果要用硬件流控制的话,还要在接收后最好加上检测缓冲区大小的判断,具体做法是使用ClearCommError后返回COMSTAT.cbInQue,当缓冲区已经空出来的时候,要使用invoke(EscapeCommFunction,hComm,SETRTS)重新将RTS设置为ON。

    4.  串口读写操作

      串口读写有两种方式:同步方式(NonOverlapped)和异步方式(Overlapped)。同步方式指必须完成了读写操作,函数才返回,这可能会使程序无响应,因为如果在读写时发生了错误,永远不返回就会出错,可能线程将停在原地。而异步方式则灵活的多,一旦读写不成功,就将读写挂起,函数直接返回,可以通过GetLastError函数得知读写未成功的原因,所以串口读写常常采用异步方式操作。

    ReadFile()函数用于完成读操作,异步方式的读操作为:

      DWORD dwRead;

      BOOL fWaitingOnRead = FALSE;

      OVERLAPPED osReader;

      memset(&osReader, 0, sizeof(osReader));

      osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

      if(osReader.hEvent == NULL)      ……       //错误处理

      if(!fWaitingOnRead)

      {

              if(!ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader))              //读串口

              {

                     if(GetLastError() != ERROR_IO_PENDING)     ……       //报告错误

                     else fWaitingOnRead = TRUE;

         }

      }

      else

      {

        //读取完成,不必在调用GetOverlappedResults函数

        HandleASuccessfulRead(lpBuf, dwRead);

            }

      //如果读操作被挂起,可以调用WaitForSingleObject()函数或

      //WaitForMuntilpleObjects()等待读操作完成或者超时发生,

      //再调用GetOverlappedResult()得到想要的信息。

      if(fWaitingOnRead)

      {

        dwRes = WaitForSingleObject(osReader.hEvent, READ_TIMEOUT);

        switch(dwRes)

        {

        case WAIT_OBJECT_0:        //完成读操作

               if(!GetOverlappedResult(hComm, &osReader, &dwRead, FALSE))   …… //错误

               else ……        //全部读取成功

               HandleASuccessfulRead(lpBuf, dwRead);

                 fWaitintOnRead = FALSE;

                 break;

        case WAIT_TIMEOUT:         //操作尚未完成

               …….                           //处理其他任务

               break;

        default:

               ……              //出现错误

               break;

        }

      }

      注意上述代码在处理多线程串口在windows系列下存在一些问题,修改完成后代码参考1.4节。

    5.  关闭串口

      程序结束或需要释放串口资源时,必须正确关闭串口。调用CloseHandle函数关闭串口的句柄即可,

             CloseHandle(hComm);

      值得注意的是,在关闭串口前必须保证读写串口线程已经退出,否则会引起误操作,一般采用的办法是使用事件驱动机制,启动一事件,通知串口读写线程强制退出。

    6. 其他问题

      串口通信中其他必须处理的问题主要有如下几个:


    检测通信事件:用SetCommMask()设置想要得到的通信事件的掩码,再调用WaitCommEvent()检测通信事件的发生。可设置事件标志有EV_BREAK EV_VTS EV_DSR EV_ERR EV_RING EV_RLSD EV_RXCHAR EV_RXFLAG EV_TXEMPTY。
    处理通信超时:在通信中,超时是一个很重要的考虑因素,因为数据接收过程中由于某种原因突然中断或停止,如果不采取超时控制机制,将会使得I/O线程被挂起或无限阻塞。超时设置分两步,首先设置COMMTIMEOUTS结构的5个变量,然后调用SetcommTimeouts()设置超时值,对于使用异步方式读写的操作,如果操作挂起后,异步成功完成了读写,WaitForSingleObject()或WaitForMultipleObjects()将返回WAIT_OBJECT_0,GetOverlappedResult()返回TRUE。其实还可以用GetCommTimeouts()得到系统初始值。
    错误处理和通信状态:在串口通信中,可以会产生很多的错误,使用ClearCommError()可以检测错误并且清除错误条件。
    WaitCommEvent()返回时,只是指出了如CTS等等状态有变化。但要了解具体变化情况必须使用GetCommModemStatus()获得串口线路状态更详细的信息。

    二、串口操作方式

    1.  同步方式

      同步(NonOverlapped)方式是比较简单的一种方式,编写代码长度明显少于异步(Overlapped)方式。同步方式中,读串口的函数试图在串口的接收缓冲区中读取规定数据的数据,直到规定数据的数据全部被读出或设定超时时间已到时才返回。例如:

           COMMTIMEOUTS timeOver;

           memset(&timeOver, 0, sizeof(timeOver));

           DWORD timeMultiplier, timeConstant;

           ……

           timeOver.ReadTotalTimeoutMultiplier = timeMultiplier;

           timeOver.ReadTotalTimeoutConstant = timeConstant;

           SetCommTimeouts(hComm, &timeOver);

           ……

           ReadFile(hComm, inBuffer, nWantRead, &nRealRead, NULL); //NULL指采用同步文件读写

      如果所规定的待读取数据的数目nWantRead较大且设定的超时时间较长,而接收缓冲区中数据较少,则可能引起线程阻塞。解决这一问题的方法是检查COSTAT结构的cbInQue成员,该成员的大小即为接收缓冲区中处于等待状态的实际个数。如果令nWantRead的值等于COMSTAT.cbInQue,就能很好的防止线程阻塞。                       

    2. 异步方式

      在异步方式中,利用Windows的多线程结构,可以让串口的读写操作在后台进行,而应用程序的其他部分在前台执行。例如:

           OVERLAPPED wrOverlapped;

           COMMTIMEOUTS timeOVer;

           memset(&timeOver, 0, sizeof(timeOver));

           DWORD timeMultiplier, timeConstant;

           ……       //给timeMultiplier, timeConstant赋值

           timeOver.ReadTotalTimeoutMultiplier = timeMultiplier;

           timeOver.ReadTotalTimeoutConstant = timeConstant;

           SetCommTimeouts(hComm, &timeOver);

           wrOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

           ……

           ReadFile(hComm, inBuffer, nWantRead, &nRealRead,  &wrOverlapped);

           GetOverlappedResult(hComm, &wrOverlapped, &nRealRead,TRUE);

           ……

           ResetEvent(wrOverlapped.hEvent);

      上面代码中的ReadFile由于采用了异步方式,所以只返回数据是否已经开始读入的状态,并不返回实际的读入数据,即ReadFile中的nRealRead无效。实际读入的数据由GetOverlappedResult返回的,该函数的最后一个参数值为TRUE,表示它等待异步操作结束后才返回到应用程序,此时,GetOverlappedResult与WaitForSingleObject函数无效。

    3. 查询方式

      即一个进程中的某一线程定时地查询串口的接收缓冲区,如果缓冲区中有数据,就读取数据;若缓冲区没有数据,该线程将继续执行,因此会占用大量的CPU时间,它实际上是同步方式的一种派生。例如:

                  COMMTIMEOUTS timeOver,

                  Memset(&timeOver, 0, sizeof(timeOver));

                  timeOver.ReadIntervalTimeout = MAXWORD;

                  SetCommTimeouts(hComm, &timeOver);

                  ……ReadFile(hComm, inBuffer, nWantRead, &nRealRead, NULL);

      除了COMMTIMEOUTS结构的变量timeOver设置不同外,查询方式与同步方式在程序代码方面很类似,但二者的工作方式却差别很大。尽管ReadFile采用的也是同步文件读写方式,但由于timeOver的区间超过时间设置为MAXWORD,所以ReadFile每次将读出接收队列中的所有处于等待状态的数据,一次最多可读出nWantRead个字节的数据。

    4.  事件驱动方式

      若对端口数据的响应时间要求较严格,可采用事件驱动方式。事件驱动方式通过设置事件通知,当所希望的事件发生时,Windows发出该事件已经发生的通知。Windows定义了9中串口通信事件,常用的有以下3中:


    EV_RXCHAR:接收到一个字节,并放入输入缓冲区。
    EV_TXEMPTY:输出缓冲区中的最后一个字符,发送出去。
    EV_RXFLAG:接收到事件字符(DCB结构中的EvtChar成员),放入输入缓冲区。

      在用SetCommMask()制定了有用的事件后,应用程序可调用WaitCommEvent()来等待事件的发生。SetCommMask可使WaitCommEvent()中止。例如:

                  COMSTAT comStat;

                  DWORD dwEvent;

                  SetCommMask(hComm, EV_RXCHAR);

                  ……

                  if(WaitCommEvent(hComm, &dwEvent, NULL))

                         if((dwEvent & EV_RXCHAR) && comstat.cbInQue)

                                ReadFile(hComm, inBuffer, comstat.cbInQue, &nRealRead, NULL);

    5. 总结

      一般要求情况下,查询方式是一种最直接的读串口的方式。但定时查询存在一个致命的弱点,即查询是定时发生的,可能发生的过早或过晚。在数据变化较快的情况下,特别是主控计算机的串口通过扩展板扩展多个时,需定时对所有串口轮流查询,容易发生数据的丢失。虽然定时间隔越小,数据的实时性越高,但系统的资源也被占用越多。

      Windows中提出文件读写的异步方式,主要是针对文件IO相对较慢的速度而进行的改进,它利用了系统的多线程结构,虽然在Windows中没有实现任何对文件IO的异步操作,但它却能对串口进行异步操作。采用异步方式,可以提高系统整体性能,在对系统强壮性要求高的场合,建议采用这种方式。

      事件驱动方式是一种高效的串口读方式。这种方式实时性较高,特别对扩展了多个串口的情况,并不要求像查询方式那样定时地对所有串口轮询,而像中断方式那样,只有当设定的事件发生时,应用程序得到windows操作系统发出的消息后,才进行相应处理,以免数据丢失。

  • 相关阅读:
    HDU 1207 汉诺塔II (递推)
    HDU 3172 Virtual Friends (map+并查集)
    HDU 1272 小希的迷宫(并查集)
    hihoCoder #1037 : 数字三角形 (动态规划)
    51Nod 1256 乘法逆元
    AtCoder Regular Contest 077 D
    AtCoder Regular Contest 077 C
    AtCoder Beginner Contest 066 B
    AtCoder Beginner Contest 045 C
    AtCoder Beginner Contest 045 B
  • 原文地址:https://www.cnblogs.com/cyyljw/p/6861652.html
Copyright © 2020-2023  润新知