zoukankan      html  css  js  c++  java
  • Win32串口编程

                                                              转自csdn大牛johnny

      在工业控制中,工控机(一般都是基于windows平台)与智能仪表一般通过RS485进行通信。RS485的通信方式是半双工的,每次通信都是由PC机通过串口向智能控制单元发布命令,智能控制单元在接收到正确的命令后做出应答。

      在win32(32位的windows环境)下,可以通到使用ActiveX控件或者Windows的API函数这两种编程方式实现串口通信。  

      串口有两种操作方式:同步操作和异步操作(重叠操作)。同步操作时,API函数会阻塞直至操作完成以后才返回,而异步操作,API函数会立即返回,操作在后台运行,避免线程的阻塞。

      两种方式,都要通过四个步骤来完成:

          1. 打开串口 2. 配置串口 3.读写串口 4.关闭串口

      1.打开串口

      win32系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都用API函数CreateFile来打开和创建。

      HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,

                DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)

      lpFileName  将要打开的串口逻辑名,如"COM1"

      dwDesiredAccess  指定串口访问的类型,可以使读取、写入或者二者并列

      dwShareMode  指定共享属性,由于串口不能共享,该参数必须为0

      lpSecurityAttributes   引用安全性属性结构,缺省为NULL

      dwCreationDistribution  创建标志,对串口操作必须设置为OPEN_EXSITING

      dwFlagsAndAtttibutes  属性描述,用于指定该串口是否进行异步操作,该值为FILE_FLAG_OVERLAPPED,表示使用异步的I/O;

                                            该值为0,表示同步I/O操作。

      hTemplateFile  对串口而言该参数必须置为NULL

      同步I/O方式打开串口示例代码:

      HANDLE hCom = CreateFile("COM1", GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_EXSITING, 0);

      if(hCom == (HANDLE)-1) {AfxMessageBox("打开COM失败!"); return FALSE;} return TRUE;

      异步I/O方式打开串口示例代码: 

      HANDLE hCom = CreateFile("COM1", GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_EXSITING,  FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED);

      if(hCom == INVALID_HANDLE_VALUE) {AfxMessageBox("打开COM失败!"); return FALSE;} return TRUE;

      2.配置串口

      在打开设备句柄后,常常要对串口进行一些初始化配置工作。这需要通过一个DCB结构来进行。DCB结构包含了诸如波特率、数据位数、奇偶校验和停止位数等信息。在查询和配置串口的属性时,都要用DCB结构作为缓冲区。

      一般打开串口后,可以调用GetCommState函数来获取串口的初始配置。要修改串口配置,应先修改DCB结构,然后再调用SetCommState函数设置串口。

      DCB结构(Device Control Block)结构定义了串口通信设备的控制设置。

      typedef struct _DCB

      { ......

        DWORD BaudRate;  //波特率

        DWORD fParity;  //指定奇偶校验使能(1为允许奇偶校验)

        BYTE ByteSize;  //通信字节位数,4-8

        BYTE Parity;  //指定1奇偶校验方法

        BYTE StopBits;  //指定停止位的位数

      }DCB;

      GetCommState函数可以获得COM口的设备控制块,从未获得相关参数

      BOOL GetCommState(HANDLE hFile, LPDCB lpDCB);

      BOOL SetCommState(HANDLE hFile, LPDCB lpDCB);

      除了DCB中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。windows用I/O缓冲区来暂存串口输入输出的数据。如果通信的速率较高,则应设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓存区的大小。

      BOOL SetupComm(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue);

      在用ReadFile和WriteFile读写串行口时,需要考虑超时问题。超时的作用是在指定的时间内没有读入或发送指定数量的字符,ReadFile和WriteFile的操作仍然会结束。

      要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用COMMTIMEOUTS结构来设置超时。

      读写串口的超时有两种:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延。总超时是指读写操作总花费的最大时间。写操作只支持总超时,而读操作两种均支持。

      COMMTIMEOUTS结构的定义为:
      typedef struct _COMMTIMEOUTS
      {
          DWORD ReadIntervalTimeout;              //读间隔超时
          DWORD ReadTotalTimeoutMultiplier;     //读时间系数
          DWORD ReadTotalTimeoutConstant;     //读时间常量
          DWORD WriteTotalTimeoutMultiplier;    //写时间系数
          DWORD WriteTotalTimeoutConstant;    //写时间常量
      } COMMTIMEOUTS,*LPCOMMTIMEOUTS;

      读总超时=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant (ms)

      如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时。如果ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且读时间系数和读时间常量都为0,那么在读一次输入缓冲区的内容后读操作就立即返回,而不管是否读入了要求的字符。

      配置串口的示例代码:

      SetupComm(hCom,1024,1024);   //输入输出缓存区的大小都是1024

      COMMTIMEOUTS TimeOuts; 

      TimeOuts.ReadIntervalTimeout = 1000;

      TimeOuts.ReadTotalTimeoutMultiplier = 500;

      TimeOuts.ReadTotalTimeoutConstant = 5000;

      TimeOuts.WriteTotalTimeoutMultiplier = 500;

      TimeOuts.WriteTotalTimeoutConstant = 2000;

      SetCommTimeouts(hCom,&TimeOuts);

      DCB dcb;

      GetCommState(hCom,&dcb);

      dcb.BaudRate = 9600;  dcb.ByteSize = 8;  dcb.Parity = NOPARITY;  dcb.stopBits = TWOSTOPBITS;  

      SetCommState(hCom,&dcb);

      PurgeComm(hCom,PURGE_TXCLEAR|PURGE_PXCLEAR);

      在读写串口之前,还要用PurgeComm()函数清空缓冲区,该函数原型:
      BOOL PurgeComm
      (
          HANDLE hFile,            //串口句柄
          DWORD dwFlags        // 需要完成的操作
      );       
      参数dwFlags指定要完成的操作,可以是下列值的组合:
      PURGE_TXABORT          中断所有写操作并立即返回,即使写操作还没有完成。
      PURGE_RXABORT          中断所有读操作并立即返回,即使读操作还没有完成。
      PURGE_TXCLEAR          清除输出缓冲区
      PURGE_RXCLEAR          清除输入缓冲区

      3.读写串口

      我们使用ReadFile和WriteFile读写串口,下面是两个函数的声明:

      BOOL ReadFile

      (
           HANDLE hFile,                                     //串口的句柄  
           LPVOID lpBuffer,                              //读入的数据存储的地址
           DWORD  nNumberOfBytesToRead,        //要读入的数据的字节数        
           LPDWORD lpNumberOfBytesRead,        //该数值返回读操作实际读入的字节数
           LPOVERLAPPED lpOverlapped       //重叠操作时,该参数指向一个OVERLAPPED结构,同步操作时,该参数为NULL。
      );

      BOOL WriteFile

      ( 

        HANDLE hFile,      
        LPCVOID lpBuffer,
        DWORD nNumberOfBytesToWrite,         //要写入的数据的字节数       
        LPDWORD lpNumberOfBytesWritten,    //该数值返回实际写入的字节数
        LPOVERLAPPED lpOverlapped              //重叠操作时,该参数指向一个OVERLAPPED结构, // 同步操作时,该参数为NULL。
      );  

      ReadFile和WriteFile函数的同步或者异步应该和CreateFile函数相一致。

      ReadFile函数只要在串口输入缓冲区中读入指定数量的字符,就算完成操作。而WriteFile函数不但要把指定数量的字符拷入到输出缓冲区,而且要等这些字符从串行口送出去后才算完成操作。

      需要注意的是,当ReadFile和WriteFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。

      同步方式读写串口比较简单,下面给出以同步方式读写串口的代码:

      //同步读串口

      char str[100];  DWORD dwCount;  BOOL bReadState;

      bReadState = ReadFile(hCom, str, 100, &dwCount, NULL);

      if(!bReadState)

      {

        AfxMessageBox("读取串口失败!");

        return FALSE;

      }

      char lpOutBuffer[10];

      DWORD dwBytesWrite = 100;

      BOOL bWriteState;

      COMSTAT ComStat;  DWORD dwErrorFlags;
      ClearCommError(hCom,&dwErrorFlags,&ComStat);  //此函数清除硬件的通讯错误以及获取通讯设备的当前状态

      bWriteState = WriteFile(hCom, lpOutBuffer, dwBytesWrite, &dwByteWrite, NULL);

      if(!bWriteState)

      AfxMessageBox("写串口失败!");

      PurgeComm(hCom, PURGE_TXABORT|PURGE_WXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);

      

      在重叠时,操作还未完成函数就返回。但重叠I/O也可实现阻塞(如我们设置一定要读取一个数据才能进行下一步操作)。有两种方法可以等待操作完成:一种是使用WaitForSingleObject这样的等待函数来等待OVERLAPPED结构的hEvent成员;另一种方法是调用GetOverlappedResult函数等待。

      在使用ReadFile和WriteFile重叠操作时,线程需要创建OVERLAPPED结构以供这两个函数使用。当串口使用异步通讯时,函数返回时操作可能还没完成,程序可以检查OVERLAPPED结构中的hEvent成员来获取当前的操作状态。当调用ReadFile, WriteFile 函数的时候,该成员会自动被置为无信号状态;当重叠操作完成后,该成员变量会自动被置为有信号状态。

      BOOL GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait)

      BOOL bwait // 该参数用于指定函数是否一直等到重叠操作结束。    // 如果该参数为TRUE,函数直到操作结束才返回。    // 如果该参数为FALSE,函数直接返回,这时如果操作没有完成,    // 通过调用GetLastError()函数会返回ERROR_IO_INCOMPLETE。

      

      异步读串口的示例代码:

      char lpInbuffer[1024];

      DWORD dwBytesRead = 1024;

      OVERLAPPED m_osRead;

      COMSTAT ComStat;  DWORD dwErrorFlags;

      memset(&m_osread, 0, sizeof(OVERLAPPED));

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

      ClearCommError(hCom,&dwErrorFlags,&ComStat);

      dwBytesRead = min(dwBytesRead,(DWORD)ComStat.cbInQue);

      if(!dwBytesRead)

        return FALSE;

      BOOL bReadStatus;

      bReadStatus = ReadFile(hCom, lpInBuffer, dwBytesRead, &dwBytesRead, &m_osRead);

      if(!bReadStatus)

      {

        if(GetLastERROR() == ERROR_IO_PENDING)  //串口正在进行读操作

        {

          WaitForSingleObject(m_osRead.hEvent, 2000);  //等待,直至读操作完成或延时已达到两秒

          PurgeComm(hCom,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);

          return dwBytesRead;  

        }

        return 0;

      }

      PurgeComm(hCom, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEARPURGE_RXCLEAR);

      return dwBytesRead;

      在使用ReadFile函数进行操作前,应先使用ClearCommError函数清除错误。此函数清除硬件的通讯错误以及获取通讯设备的当前状态。

      BOOL ClearCommError

      (

        HANDLE hFile;

        LPDWORD lpErrors;  //指向接受错误码的变量

        LPCOMSTAT lpstat;  //指向通讯状态缓冲区

         );

      其中COMSTAT结构体中的cbINque成员变量代表输入缓冲区的字节数。

      

      下面是使用GetOverLappedResult函数等待的异步读串口示例代码:

      char lpInBuffer[1024];

      DOWRD dwBytesRead = 1024;

      BOOL bReadStatus;

      DWORD dwErrorFlags;  COMSTAT ComStat;

      OVERLAPPED m_osRead;

      ClearCommError(hCom, &dwErrorFlags, &ComStat);

      if(ComStat.cbInQue)

        return 0;

      dwBytesRead = min(dwBytesRead, (DWORD)ComStat.cbInQue);

      bReadStatus = ReadFile(hCom, lpInBuffer, dwBytesRead, &dwBytesRead, &m_osRead);

      if(!bReadStatus)

      {

        if(GetLastError()==ERROR_IO_PENDING)

          {

            GetOverlappedResult(hCom, &m_osRead, &dwBytesRead, TRUE);

            //最后一个参数设为TRUE,函数会一直等待,直到读操作完成或由于错误而返回。

            return dwBytesRead;

          }

        return 0;

      }

      return dwBytesRead;

      异步写串口的示例代码

      char buffer[1024];

      DWORD dwBytesWritten = 1024;

      OVERLAPPED m_osWrite;

      DWORD dwErrorFlags;  COMSTAT ComStat;

      BOOL bWriteStatus;

      bWriteStatus = WriteFile(hCom, buffer, dwBytesWritten, &dwBytesWritten, &m_osWrite);

      if(!bWriteStatus)

      {

        if(GetLastError() == ERROR_IO_PENDING)

        {

          WaitForSingleObject(m_osWrite.hEvent, 1000);

          return dwBytesWritten;

        }

        return 0;

      }

      return dwBytesWritten;

      

      4.关闭串口

      只需使用CreateFile函数返回的句柄作为参数调用CloseHandle即可;

  • 相关阅读:
    cf C. Vasya and Robot
    zoj 3805 Machine
    cf B. Vasya and Public Transport
    cf D. Queue
    cf C. Find Maximum
    cf B. Two Heaps
    cf C. Jeff and Rounding
    cf B. Jeff and Periods
    cf A. Jeff and Digits
    I Think I Need a Houseboat
  • 原文地址:https://www.cnblogs.com/CanWork/p/2579731.html
Copyright © 2011-2022 走看看