程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 用API函數實現串行通訊

用API函數實現串行通訊

編輯:關於C++

以往的DOS系統是通過DOS中斷和BIOS中斷向用戶提供串行接口的通訊能力。在Windows環境下,C++的開發工具既沒有提供象DOS和BIOS中那樣專門的串行通訊控制方法,也不允許用戶直接控制串口的中斷。

為了保證資源共享,Windows系統完全接管了各種硬件資源,使用中斷來控制端口將破壞系統的多任務性,使系統的穩定性受到影響。但Windows同時也提供了功能強大的API函數使用戶能間接的控制串行通訊。

1、實現串行通訊的相關API函數

API函數不僅提供了打開和讀寫通訊端口的操作方法,還提供了名目繁多的函數以支持對串行通訊的各種操作。常用函數及作用如表5-1所示。

表5-1 常用串行通訊API函數及其作用

函數名 作用

CreateFile 打開串口

GetCommState 檢測串口設置

SetCommState 設置串口

BuilderCommDCB 用字符串中的值來填充設備控制塊

GetCommTimeouts 檢測通信超時設置

SetCommTimeouts 設置通信超時參數

SetCommMask 設定被監控事件

WaitCommEvent 等待被監控事件發生

WaitForMultipleObjects 等待多個被監測對象的結果

WriteFile 發送數據

ReadFile 接收數據

GetOverlappedResult 返回最後重疊(異步)操作結果

PurgeComm 清空串口緩沖區,退出所有相關操作

ClearCommError 更新串口狀態結構體,並清除所有串口硬件錯誤

CloseHandle 關閉串行口

2、打開串口

函數CreateFile原本用於打開文件,但它同樣可用於打開一個通信端口。與系統中其他對象一樣,通信端口也是用句柄來標識的。CreateFile函數返回被操作的通信端口句柄,其調用方法如下:

HANDLE CreateFile (

LPCTSTR lpFileName,  //指向文件名字符串的指針

DWORD dwDesireAccess,   //操作模式

DWORD dwShareMode,    //共享方式

LPSECURITY_ATTRIBUTES lpSecurityAttributes,      

//指向安全屬性的指針

DWORD dwCreationDistribution,//文件建立方式

DWORD dwFlagsAndAttributes //文件屬性

HANDLE hTemplateFile)   //模板文件句柄

lpFileName:指向一個以NULL結束的字符串,該串指定了要創建、打開或截斷的文件、管道、通信源、磁盤設備或控制台的名字。當用CreateFile打開串口時,這個參數可用“COM1”指定串口1,用“COM2”指定串口2,依此類推。

dwDesireAccess: 指定對文件訪問的類型,該參數可以為GENERIC_READ(指定對該文件的讀訪問權)或ENERIC_WRITE(指定該文件的寫訪問權)兩個值之一或同時為為這兩個值。用ENERIC_READ|GENERIC_WRITE則指定可對串口進行讀寫;

dwShareMode:指定此文件可以怎樣被共享。因為串行口不支持任何共享模式,所以dwShareMode必須設為0;

lpSecurityAttributes定義安全屬性,一般不用,可設為NULL。Win 9x下該參數被忽略;

dwCreationDistribution定義文件創建方式, 對串口必須設為OPEN_EXISTING,表示打開已經存在的文件;

dwFlagsAndAttributes為該文件指定定義文件屬性和標志,這個程序中設為FILE_FLAG_OVERLAPPED,表示異步通信方式;

hTemplateFile 指向一個模板文件的句柄,串口無模板可言,設為NULL。在 Windows 9x下該參數必須為NULL。

用異步讀寫方式打開串口1的函數調用如下:

m_hComm = CreateFile(“COM1”,    //打開串口1
            GENERIC_READ | GENERIC_WRITE,//讀寫方式 
            0,       //不能共享
            NULL,     //不用安全結構
            OPEN_EXISTING,   //打開已存在的設備                          
            FILE_FLAG_OVERLAPPED,  //異步方式
            0);       //無模板

串口被成功打開時,返回其句柄,否則返回INVALID_HANDLE_VALUE(0XFFFFFFFF)。

3、串口設置

第一次打開串口時,串口設置為系統默認值,函數GetCommState和SetCommState可用於檢索和設定端口設置的DCB(設備控制塊)結構,該結構中BaudRate、ByteSize、StopBits和Parity字段含有串口波特率、數據位數、停止位和奇偶校驗控制等信息。程序中可先用GetCommState檢索端口的當前設置,修改其中的部分字段後再用SetCommState進行端口設定。這樣可不必構造一個完整的DCB結構。

下面介紹幾個主要的函數和結構體:

(1)GetCommState

BOOL GetCommState( hCommDev, lpdcb);

參數hCommDev標識通信設備,應使用CreateFile返回的句柄。Lpdcb是指向DCB結構的指針,函數調用後當前串口配置信息將被保存在這個結構內。如果函數成功返回值為TRUE;否則返回值為FALSE。SetCommState用法與GetCommState相似,在此不再重復。DCB結構定義如下(只介紹主要的幾項):

    typedef struct _ DCB{
        ……
        DWORD  BardRate;   //波特率的設置
        BYTE   ByteSize;   //數據位的個數
        BYTE   Parity;     //是否有奇偶校驗位
        BYTE   StopBits;    //停止位的個數
        ……
        }DCB;

(2)SetCommTimeouts

BOOL SetCommTimeouts( hCommDev, lpctmo );

Lpctmo指向包含新的超時參數的COMMTIMEOUTS結構。COMMTIMEOUTS結構定義如下:

typedef struct _ COMMTIMEOUTS{

DWORD ReadIntervalTimeout;

DWORD ReadTotalTimeoutMultiplier;

DWORD ReadTotalTimeoutconstant;

DWORD WriteTotalTimeoutMultiplier;

DWORD WriteTotalTimeoutconstant;

}COMMTIMEOUTS, LPCOMMTIMEOUTS;

ReadIntervalTimeout: 以毫秒為單位指定通信線上兩個字符到達之間的最大時間。在ReadFile操作其間,收到第一個字符時開始計算時間。若任意兩個字符到達之間的間隔超過這個最大值,ReadFile操作完成,返回緩沖數據。0值表示不用間隔限時。若該成員為MAXDWORD,且ReadTotalTimeoutconstant和ReadTotalTimeoutMultiplier成員為零,則指出讀操作要立即返回已接收到的字符,即使未收到字符,讀操作也要返回。

ReadTotalTimeoutMultiplier:以毫秒為單位指定一個乘數,該乘數用來計算讀操作的總限時時間。每個讀操作的總限時時間等於讀操作所需的字節數與該值的乘積。

ReadTotalTimeoutConstant:以毫秒為單位指定一個常數,用於計算讀操作的總限時時間。每個操作的總限時時間等於ReadTotalTimeoutMultiplier成員乘以讀操作所需字節數再加上該值的和。ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant成員的值為0表示讀操作不使用限時時間。

WriteTotalTimeoutMultiplier和WriteTotalTimeoutconstant的意義和作用分別與ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant相似,不再重復。

(3)BuilderCommDCB

BOOL BuilderCommDCB(lpszDef,lpdcb)

這個函數按lpszDef字符串所指定的格式來配置串口的DCB。

LpszDef:指向一個以NULL結束的字符串,該字符串指定串口的控制信息。比如,“1200,N,8,1”指定波特率為1200,無奇偶校驗位,有8個數據位和1個停止位。

lpdcb:指向被填充的DCB結構。

(4)SetCommMask

BOOL SetCommMask(hCommDev,fdwEvtMask);

fdwEvtMask指向一個32位的屏蔽碼,如果指定為EV_RXCHAR | EV_CTS,表示程序監控串口的收、發事件。

下面以簡單的例子說明串口設置的步驟:

 m_CommTimeouts.ReadIntervalTimeout = 1000;
     m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000;
     m_CommTimeouts.ReadTotalTimeoutConstant = 1000;
     m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000;
     m_CommTimeouts.WriteTotalTimeoutConstant = 1000;
     if (SetCommTimeouts(m_hComm, &m_CommTimeouts))
     // 串口超時參數設置
        if (SetCommMask(m_hComm, dwCommEvents))
        // 設置串口事件掩碼
            if (GetCommState(m_hComm, &m_dcb))
            // 獲取串口當前狀態
               if (BuildCommDCB(“1200,N,8,1”, &m_dcb))
               // 建立串口設備控制塊
                   if (SetCommState(m_hComm, &m_dcb));
                   // 設置串口參數

……

以上任何一個if語句的判斷條件為假時都將調用GetLastError函數獲取錯誤信息,進行錯誤處理。

4、讀寫串口數據

Win32API函數ReadFile和WriteFile支持對串行口 的讀寫操作。這些函數的行為還受是否使用異步I/O(Overlapped)及通信超時設置的影響。串行口讀寫的同步、異步方式是在打開端口的同時給dwGlagsAndAttributes參數傳入適當的值而設定的。

在同步方式下,調用ReadFile或WriteFile後,當實際讀寫操作完成或發生超時時才返回調用程序。

而異步方式函數在啟動接收或發送過程後立即返回,程序繼續向下執行。程序在調用ReadFile和WriteFile時必須提供一個Overlapped數據結構指針,該結構中包含一個手動的事件同步對象,其後的程序必須借助於該事件同步對象,完成數據的接收和發送過程。

通信端口的超時設置對讀寫的處理方式也會產生影響,如果調用讀寫函數時發生端口超時,則讀寫函數立即返回並返回已傳輸的數據字節數。

下面介紹主要的用於串口讀寫的函數:

(1)ReadFile 函數的調用方法如下:

 BOOL ReadFile (
            HANDLE hFile,     //用CreateFile 獲得的文件句柄
            LPVOID lpBuffer,   //輸入緩沖區首址
            DWORD nNumberOfBytesToRead,//設定讀入字節數
            LPDWORD lpNumberOfByteRead, //實際讀入字節數
            LPOVERLAPPED lpOverlapped  //重疊操作方式數據結構地址
            );

(2)WriteFile函數的調用方法如下:

BOOL WriteFile(
       HANDLE hFile,       //用CreateFile 獲得的文件句柄
       LPCVOID lpBuffer,  //輸出緩沖區首址
       DWORD nNumberOfBytesToWrite,  //要求輸出的字節數
       LPDWORD lpNumberOfBytesWritten,//實際輸出字節數
       LPOVERLAPPED lpOverlapped):    //重疊操作方式數據結構地址

(3)GetOverlappedResult函數調用方法如下:

BOOL GetOverlappedResult(
       HANDLE hFile, //用CreateFile 獲得的文件句柄
       LPOVERLAPPED lpOverlapped,  
                      //指向一個在啟動重疊操作時指定的
                      OVERLAPPED結構(即讀寫函數中指定的OverLapped結構)
       LPDWORD lpNumberOfBytesTransferred,//實際傳輸的字節數
            BOOL bWait,   //是否等待懸掛的重疊操作完成,若為
                          //TRUE,則此函數直到操作完成後才返回。
       );

OVERLAPPED結構定義如下:

 typedef struct _OVERLAPPED {
       DWORD Internal;
       DWORD InternalHigh;
       DWORD Offset;
       DWORD OffsetHigh;
       HANDLE hEvent;
        } OVERLAPPED;

如果采用異步方式,則在調用ReadFile或WriteFile函數時必需指定一個Overlapped結構,調用後程序可繼續執行其它操作,在合適的地方再調用函數GetOverlappedResult判斷異步重疊操作是否完成(判斷OVERLAPPED結構中的hEvent是否被置位)。

(4)WaitCommEvent函數的調用法如下:

BOOL WaitCommEvent(
       HANDLE hCommDev,       //串口句柄
       LPDWORD lpfdwEvtMask,    //見SetCommEvent函數說明
       LPOVERLAPPED lpo,  //重疊操作方式數據結構地址
       );

當由SetCommMask函數所指定的事件產生時這個函數將返回TRUE。

下面是一個串口讀寫的流程圖。

串口操作流程圖

5、關閉串口

如果不再使用某一端口,須將該端口關閉,以便其他程序可以使用該端口。如果不顯式關閉某端口,當程序退出時打開的端口也將被自動關閉。但為了安全起見,最好是顯式的關閉它。關閉串口的語句為CloseHandle(hCommDev);其中hCommDev為用CreateFile建立的串口句柄。

6、一個完整的例子

這個串口控制類是我做的一個軟件中使用的,基本用WINAPI實現,在Win95/Win98/Win2000下運行良好 ,可不做修改的用在其它BCB程序中 。讀數據采用事件驅動方式,用一個線程處理相應事件,收到的字符用消息發往父窗口;由於我編的軟件中寫串口的數據不多,所以采用直接寫的方式,而沒用線程。

串口控制類 COM.h COM.cpp

串口控制類中使用的讀串口線程 COMThread.h COMThread.cpp

打包下載

這個類的使用方法如下:

1、建立串口類的實例,如:

SerialPort=new TSerialPort();  //實例化一個串口控制類

//初始化串口,Baud:9600,DataBits:8,StopBits:1,無校驗位

SerialPort->InitPort(this,g_iSerialPort,9600);

SerialPort->StartMonitoring();  //創建串口監控線程,開始監控

2、接收串口數據

(1)映射串口消息,在創建以上實例的窗口類的聲明中加入:

void __fastcall OnComRx(TMessage &Message);  
   //指定消息WM_COMM_RXCHAR(由串口監控線程發出的消息,表示接收到一個字符)的處理函數為OnComRx
    BEGIN_MESSAGE_MAP
      MESSAGE_HANDLER(WM_COMM_RXCHAR, TMessage, OnComRx)
    END_MESSAGE_MAP(TForm)

(2)實現函數OnComRx(),如:

//---------------------------------------------------------------------------
//串口消息(接收到一個字符)處理函數
//消息的參數WParam就是接收到的字符
void __fastcall TDlgProgram::OnComRx(TMessage &Message)
{
    unsigned char ucRxData=(unsigned char)Message.WParam;   //串口監控線程收到的字符
    ………………
}
//---------------------------------------------------------------------------
  

3、向串口發送數據

調用函數bool WriteToPort(unsigned char ucTxChar)可向串口寫一個字符。

如SerialPort->WriteToPort(0x55);

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved