陈思思:在VB中如何用API函数控制计算机的COM端口??

来源:百度文库 编辑:九乡新闻网 时间:2024/04/28 02:16:47
Win32串口编程
金贝贝

一、基本知识  

     Win32下串口通信与16位串口通信有很大的区别。在Win32下,可以使用两种编程方式实现串口通信,其一是调用的Windows的API函数,其二是使用ActiveX控件。使用API   调用,可以清楚地掌握串口通信的机制,熟悉各种配置和自由灵活采用不同的流控进行串口通信。下面介绍串口操作的基本知识。  

  打开串口:使用CreateFile()函数,可以打开串口。有两种方法可以打开串口,一种是同步方式(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)结构定义了串口通信设备的控制设置。许多重要设置都是在DCB结构中设置的,有三种方式可以初始化DCB。  

  (1)通过GetCommState()函数得DCB的初始值,其使用方式为:  

DCB   dcb   =   {0};
if   (!GetCommState(hComm,   &dcb))
//   Error   getting   current   DCB   settings
else
//   DCB   is   ready   for   use.

  (2)用BuildCommDCB()函数初始化DCB结构,该函数填充   DCB的波特率、奇偶校验类型、数据位、停止位。对于流控成员函数设置了缺省值。其用法是:  

DCB   dcb;
FillMemory(&dcb,   sizeof(dcb),   0);
dcb.DCBlength   =   sizeof(dcb);
if   (!BuildCommDCB(“9600,n,8,1 ",   &dcb))   {  
//   Couldn 't   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(hComm,   &dcb))   //   get   current   DCB
//   Error   in   GetCommState
return   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的结构的各成员的含义,可以参看MSDN帮助。  

     2.流控设置  

  硬件流控:串口通信中的硬件流控有两种,DTE/DSR方式和RTS/CTS方式,这与DCB结构的初始化有关系,DCB结构中的OutxCtsFlow、   fOutxDsrFlow、fDsrSensitivity、fRtsControl、fDtrControl几个成员的初始值很关键,不同的值代表不同流控,也可以自己设置流控,但建议采用标准流行的流控方式。采用硬件流控时,DTE、DSR、RTS、CTS的逻辑位直接影响到数据的读写及收发数据的缓冲区控制。  

     软件流控:串口通信中采用特殊字符XON和XOFF作为控制串口数据的收发。与此相关的DCB成员是:fOut、fInX、XoffChar、XonChar、   XoffLim和XonLim。具体含义参见MSDN帮助。  

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

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

    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)   {
    //   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.
    else
    fWaitingOnRead   =   TRUE;
    }
    else   {  
    //   read   completed   immediately
    HandleASuccessfulRead(lpBuf,   dwRead);
    }
    }

     如果读操作被挂起,可以调用WaitForSingleObject()函数或WaitForMuntilpleObjects()函数等待读操作完成或者超时发生,再调用   GetOverlappedResult()得到想要的信息。  

     写操作:与读操作相似,故不详述,调用的API函数是:   WriteFile函数。  

     串口状态:  

  (1)通信事件:用SetCommMask()函数设置想要得到的通信事件的掩码,再调用WaitCommEvent()函数检测通信事件的发生。可设置的通信事件标志(即SetCommMask()函数所设置的掩码)可以有EV_BREAK、EV_CTS、EV_DSR、   EV_ERR、EV_RING、EV_RLSD、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY。  

     注意:1对于EV_RING标志的设置,WIN95是不会返回EV_RING事件的,因为WIN95不检测该事件。2设置EV_RXCHAR,可以检测到字符到达,但是在绑定此事件和ReadFile()函数一起读取串口接收数据时,可能会出现错误,造成少读字节数,具体原因查看MSDN帮助。可以采用循环读的办法,另外一个比较好的解决办法是调用ClearCommError()函数,确定在一次读操作中在缓冲区中等待被读的字节数。  

  (2)错误处理和通信状态:在串口通信中,可能会产生很多的错误,使用ClearCommError()函数可以检测错误并且清除错误条件。  

     (3)Modem状态:用SetcommMask()可以包含很多事件标志,但是这些事件标志只指示在串口线路上的电压变化情况。而调用   GetCommModemStatus()函数可以获得线路上真正的电压状态。  

     扩展函数:如果应用程序想用自己的流控,可以使用   EscapeCommFunction()函数设置DTR和RTS线路的电平。  

     通信超时:在通信中,超时是个很重要的考虑因素,因为如果在数据接收过程中由于某种原因突然中断或停止,如果不采取超时控制机制,将会使得I/O线程被挂起或无限阻塞。串口通信中的超时设置分为两步,首先设置   COMMTIMEOUTS结构的五个变量,然后调用SetcommTimeouts()设置超时值。对于使用异步方式读写的操作,如果操作挂起后,异步成功完成了读写,WaitForSingleObject()或   WaitForMultipleObjects()函数将返回WAIT_OBJECT_0,GetOverlappedResult()返回TRUE。其实还可以用GetCommTimeouts()得到系统初始值。  

     关闭串口:程序结束或需要释放串口资源时,应该正确关闭串口,关闭串口比较简单,使用API调用CloseHandle()关闭串口的句柄就可以了。  

  调用方法为:CloseHandle(hComm);  

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

二、实现
  1.程序设计思路  
     对于不同的应用程序,虽然界面不同,但是如果采用串口与主机之间的通信,对串口的处理方式大致相似,无非就是通过串口收发数据,对于通过串口接收到的数据,交给上层软件处理显示,对于上层要发给串口的数据,进行转发。但在实际编程中,由于采用的通信方式和流控不同,串口设置也不同,这就涉及到   DCB的初始化问题和读写串口等细节问题。串口通信应用程序设计的总体思路(即操作过程)是:首先,确定要打开的串口名、波特率、奇偶校验方式、数据位、停止位,传递给CreateFile()函数打开特定串口;其次,为了保护系统对串口的初始设置,调用   GetCommTimeouts()得到串口的原始超时设置;然后,初始化DCB对象,调用SetCommState()   设置DCB,调用SetCommTimeouts()设置串口超时控制;再次,调用SetupComm()设置串口接收发送数据的缓冲区大小,串口的设置就基本完成,之后就可以启动读写线程了。  

  一般来说,串口的读写由串口读写线程完成,这样可以避免读写阻塞时主程序死锁。对于全双工的串口读写,应该分别开启读线程和写线程;对于半双工和单工的,建议只需开启一个线程即可。在线程中,按照预定好的通信握手方式,正确检测串口状态,读取发送串口数据。  

  2.实现细节  

  在半双工的情况下,首先完成必要的串口配置,成功打开串口、DCB设置、超时设置;然后开启线程,如:   CwinThread   hSerialThread   =   (CWinThread*)   AfxBeginThread(SerialOperation,hWnd,THREAD_PRIORITY_NORMAL);   其中开启之线程为SerialOperation,优先级为普通。  

     全双工情况下的串口编程,与单工差不多,区别仅仅在于启动双线程,分别为读线程和写线程,读线程根据不同的事件或消息,通过不断查询串口所收到的有效数据,完成读操作;写线程通过接收主线程的发送数据事件和要发送的数据,向串口发送。