•  作者:admin
  •  积分:2705
  •  等级:专家教授
  •  2013/05/08 10:22:44
  •  
  • 楼主(阅读:5957/回复:0)API串口通信

    API串口通信
    串行端口是系统资源的一部分,其本质是作为CPU和串行设备间的编码转换器。当数据从 CPU经过串行端口发送出去时,字节数据转换为串行的位(Bit); 接收数据时,串行的位被转换为字节数据。应用程序要使用串口进行通信,必须在使用之前向操作系统提出资源申请要求(即打开串口),通信完成后再释放资源(即关闭串口)。

    串行通信一般可以分为同步和异步两种操作方式。所谓同步方式是指在串口的接收缓冲区中读取规定数目的数据,直到规定数目的数据全部被读出或设定的超时时间已到才返回。如果规定的待读取数据量大且设定的超时时间也较长,而接收缓冲区较小,则可能引起线程阻塞。而异步方式是利用Windows的多线程结构,让串口的读写操作在后台进行,而应用程序的其他部分在前台执行。
    如果按驱动方式分,串口通信也可分为查询和事件驱动两种操作类型。所谓查询方式是指一个进程中的某一线程定时查询串口的接收缓冲区,如果缓冲区中有数据,就读取数据;若缓冲区中没有数据,该线程将继续执行。查询方式会占用大量的CPU时间,它实际上是同步方式的一种派生。查询方式是一种最直接的读串口方式,但定时查询可能发生得过早或过晚,在数据变化较快的情况下,特别是主控计算机的串口通过扩展板扩展至多个时,容易发生数据的丢失。虽然指定时间隔越小,数据的实时性越高,但系统的资源也被占去越多。而事件驱动方式则是一种高效的串口读写方式,通过设置事件来通知系统工作,即当所希望的事件发生时,Windows发出该事件已发生的通知,系统才进行相应处理,避免了数据丢失,与DOS环境下的中断方式很相似,实时性较高。Windows中提供文件读写的异步方式,主要是针对文件I/O相对较慢的特点而进行的改进,它利用了Windows的多线程结构。虽然在Windows中没有实现任何对文件I/O的异步操作,但它却能对串口进行异步操作,因此可以提高系统的整体性能。
    通过Visual C++的标准通信函数_inp和_outp可直接通过串口输入和输出数据。一般来说,在Visual C++中开发串口通信程序主要有调用API函数和使用ActiveX控件技术两种方式。基本步骤为:打开串口设备,设置串口通信属性,进行串口读写操作,关闭串口。下面将较为详细地讨论在VC中实现串口通信的上述两种方法。

    使用Win32的API

    API是附带在Windows内部的一个极其重要的组成部分。Windows的32位API主要是一系列复杂的函数和消息集合,可以看做是Windows系统为其下运行的各种开发系统提供的开放式通用功能增强接口。Windows环境下对串行端口进行操作,是把它作为文件来处理的,其中涉及到大量API函数,操作起来比较复杂,可以概括为以下的几个操作步骤:
    1. 打开串行通信设备。在VC中使用CreateFile函数打开串口,CreateFile将返回串口的句柄。该句柄将被用于后续的通信操作,并贯穿整个通信过程。当采用异步方式时,CreateFile函数的参数fdwAttrsAndFlags必须设为FILE_FLAG_ OVERLAPPED,如:
    m_hComFile =CreateFile(“COM1”,
    //HANDLE m_hComFile,全局变量
    GENERIC_READ | GENERIC_WRITE,
    // 允许读写操作
    0, // 此项必须为0
    NULL, // 安全设置
    OPEN_EXISTING, //设置打开方式
    FILE_FLAG_OVERLAPPED,
    //使用异步通信标志
    NULL );
    2. 指定并初始化读写缓冲区。程序通过调用SetupComm函数来指定读写缓冲区的大小,并执行重新分配内部输入和输出缓冲的任务,用PurgeComm函数对输入和输出缓冲进行初始化,如:
    SetCommMask(m_hComFile, EV_RXCHAR | EV_TXEMPTY ); //设置事件驱动的类型
    SetupComm(m_hComFile, 1024,1024) ;
    //设置输入、输出缓冲区的大小
    PurgeComm(m_hComFile,
    PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR );
    //清空输入、输出缓冲区
    3.设置串口属性,配置DCB结构。当用CreateFile函数完成串口打开操作时,默认继承设备控制块(DCB结构)设置。通过调用GetCommState函数读取当前串口设备控制块DCB设置,修改后通过SetCommState函数将其写入。也可以使用GetCommProperties获取COMMPROP结构,其中记载了系统支持的各项设置,包括当前所使用的串行设备、数据传输波特率、输入输出缓冲区大小等。例如:
    DCB dcb ;
    //定义设备控制块结构
    GetCommState(m_hComFile, &dcb ) ;
    //读取串口原来的参数设置
    dcb.BaudRate =9600;
    dcb.ByteSize =8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT ;
    dcb.fBinary = TRUE ;
    dcb.fParity = FALSE;
    SetCommState(m_hComFile, &dcb ) ;
    //串口参数配置
    4. 设置超时值。串口打开后,I/O操作的超时值采用默认值。超时值的设置与结构COMMTIMEOUTS及函数GetCommTimeouts和SetCommTimeouts有关。用GetCommTimeouts函数可以获得当前I/O操作的超时值配置,而调用SetCommTimeouts函数可以修改此配置,如:
    COMMTIMEOUTS timeouts ;
    //定义超时结构,并填写该结构
    timeouts.ReadIntervalTimeout = 500;
    timeouts.ReadTotalTimeoutMultiplier = 1;
    timeouts.ReadTotalTimeoutConstant = 1000;
    timeouts.WriteTotalTimeoutMultiplier = 1;
    timeouts.WriteTotalTimeoutConstant = 1000;  SetCommTimeouts(m_hComFile,&timeouts );
    //设置读写操作所允许的超时
    其中,区间超时(ReadIntervalTimeout)指的是在读取两个字符之间的时间间隔,它仅对从端口中读取数据有效;总超时指的是当读或写特定的字节数需要的总时间超过某一阈值时,超时触发。超时的计算公式如下:
    ReadTotalTimeout= (ReadTotalTimeoutMultiplier * bytes_to_read)+ ReadToTaltimeoutConstant
    WriteTotalTimeout = (WriteTotalTimeoutMuliplier * bytes_to_write) + WritetoTotalTimeoutConstant
    5. 进行串行数据通信。调用函数ReadFile和WriteFile读写串口。若采用异步通信方式,两函数中最后一个参数为指向OVERLAPPED结构的非空指针,在读写函数返回值为FALSE的情况下,调用GetLastError函数,返回值为ERROR_IO_PENDING,表明I/O操作悬挂,即操作转入后台继续执行。此时,可以用WaitForSingleObject函数来等待结束信号并设置最长等待时间。下面的例子中,在主线程中发送命令,用一个辅助线程来监视串口,有数据到达时依靠事件驱动读入数据并向主线程报告。
    下面的代码实现在主线程中准备并发送数据:
    BOOL  fWriteStat ;
    char sndBuffer[count];
    ...... // sndBuffer[]中存放待发送的数据
    OVERLAPPED overwrite;
    //设置用于异步操作的OVERLAPPED结构
    overwrite. hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    fWriteStat = WriteFile(m_hComFile, sndBuffer, dwBytesToWrite, &dwBytesWritten, &overwrite); //写数据
    if (!fWriteStat){
       if (GetLastError() == ERROR_IO_PENDING) {……}
    } 
    创建辅助线程:
    hReadThread=CreateThread( (LPSECURITY_ATTRIBUTES) NULL,
    //安全属性
    0, //初始化线程栈的大小,缺省为与主线程大小相同
    (LPTHREAD_START_ROUTINE) CommReadProc, //线程函数
    GetSafeHwnd(), //此处传入主框架的句柄
    0, (LPDWORD)lpThreadID );
    在辅助线程中监视串口并接收数据:
    UINT CommReadProc(HWND hSendWnd){
      DWORD dwEvtMask=0 ;
      SetCommMask(m_hComFile, EV_RXCHAR|EV_TXEMPTY );
    //设置串口事件驱动
      WaitCommEvent(m_hComFile, &dwEvtMask, os ); //等待串口事件
      if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR){ //缓冲区中有数据到达  
    DWORD dwLength = ComStat.cbInQue ;
    //输入缓冲区数据长度
    COMSTAT ComStat ;
    ClearCommError(m_hComFile, &dwErrorFlags, &ComStat ) ;
    OVERLAPPED overread;
    overread. hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    if (dwLength > 0) {
    BOOL fReadStat = ReadFile(m_hComFile, lpBuffer,dwLength, &dwBytesRead,&overread);
    //读数据
    if (!fReadStat){
       if (GetLastError() == ERROR_IO_PENDING){
         ……}
    }            
    ::PostMessage((HWND)hSendWnd,
    WM_NOTIFYPROCESS,0,0);
    //通知主线程,串口收到数据
    }
    6. 关闭串行端口。调用函数CloseHandle即可。
    总体说来,调用API 函数实现串行通信,程序更为复杂,但应用更加灵活。在API串口通信中可以将串口的属性设置和操作封装成一个专用的串口类,同时结合Windows非阻塞通信、多线程、动态链接库等手段,编写出高质量的通信程序,特别是在CPU处理任务比较繁重、与外围设备中有大量的通信数据时,更具实际意义。

    欢迎使用串口论坛
    波仕与您畅游RS232/RS485串口的世界


    目前不允许游客回复,请 登录 注册 发表言论。