首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

VC 6.0 實現串列通訊的三種方法深度解析

     編輯:關於VC++

前言

在實驗室與工業應用領域,串列埠是電腦與外部串列設備進行資料傳輸的常用通道。因其介面簡單、實作容易,應用極為廣泛。依據不同應用場景靈活控制串列埠程式設計,是工控軟體開發的核心技能之一。

專案背景:光學鏡片鍍膜溫度監控系統

在光學鏡片鍍膜製程中,單晶片進行多通道溫度資料採集控制,採集結果以串列方式傳入主機。每 10 秒向主機發送一次採樣資料,主機向單晶片發送控制命令,實現串列資料接收、處理、記錄、顯示與即時繪製曲線。開發環境為 Visual C++ 6.0。

Windows 平台串列通訊架構差異

與傳統 DOS 環境直接控制硬體不同,Windows 不建議應用程式直接操作硬體,而是透過作業系統提供的裝置驅動程式進行資料傳遞。在 Win32 架構下,串列埠作為檔案處理,而非直接操作連接埠。Win32 提供完整的檔案 I/O 函式與通訊函式,透過理解這些函式即可開發符合不同需求的通訊程式。

與通訊設備相關的核心結構有 7 種:COMMCONFIG、COMMPROP、COMMTIMEOUTS、COMSTAT、DCB、MODEMDEVCAPS、MODEMSETTINGS。相關 Windows API 函式共有 26 個,詳細說明請參考 MSDN 文件。以下結合實例,介紹三種實現串列通訊的方法。

方法一:使用 MSComm ActiveX 控制元件

這是最快速上手的方式。首先在對話方塊中建立通訊控制元件,若工具箱缺少該元件,可透過 Project → Add to Project → Components and Controls 插入,再從工具箱拖曳至對話方塊。

使用 ClassWizard 為新建立的通訊控制元件定義成員物件(CMSComm m_Serial),透過該物件即可設定串列埠屬性。MSComm 控制元件共有 27 個屬性,常用者如下:

  • CommPort:設定/回傳通訊埠號,預設 COM1
  • Settings:以字串形式設定/回傳鮑率、奇偶校驗、資料位、停止位
  • PortOpen:設定/回傳通訊埠狀態,可開啟/關閉埠
  • Input:從接收緩衝區回傳並刪除字元
  • Output:向發送緩衝區寫入字串
  • InputLen:設定每次 Input 讀取的字元數,預設 0 表示讀取全部
  • InBufferCount:回傳接收緩衝區已接收字元數,設為 0 可清除緩衝區
  • InputMode:定義 Input 取得資料方式(0:文字;1:二進位)
  • RThreshold/SThreshold:觸發 OnComm 事件前,接收/發送緩衝區可容納的字元數

串列埠初始化範例

BOOL CSampleDlg::PortOpen()
{
    BOOL m_Opened;
    m_Serial.SetCommPort(2);              // 指定串列埠號
    m_Serial.SetSettings("4800,N,8,1");   // 通訊參數設定
    m_Serial.SetInBufferSize(1024);       // 指定接收緩衝區大小
    m_Serial.SetInBufferCount(0);         // 清空接收緩衝區
    m_Serial.SetInputMode(1);             // 設定資料取得方式為二進位
    m_Serial.SetInputLen(0);              // 設定讀取方式為全部
    m_Opened = m_Serial.SetPortOpen(TRUE); // 開啟指定串列埠
    return m_Opened;
}

事件驅動處理通訊互動

開啟串列埠後,需考量通訊時機。接收/發送資料過程中可能需監視並回應事件與錯誤,事件驅動是處理串列埠互動的有效方法。使用 OnComm 事件與 CommEvent 屬性捕捉並檢查通訊事件與錯誤。發生事件或錯誤時觸發 OnComm,CommEvent 值改變,應用程式檢查該值並作出反應。

void CSampleDlg::OnComm()
{
    switch(m_Serial.GetCommEvent())
    {
        case 2:  // 串列埠資料接收事件
            // 處理接收資料
            break;
    }
}

方法二:單執行緒自訂串列通訊類別

控制元件雖簡單易用,但必須置於對話方塊中,在需要於執行緒中實現通訊的場合顯得力有未逢。此時自訂靈活的串列通訊類別可彌補不足。

核心實作步驟

通訊類別 CSimpleComm 需手動加入標頭檔與原始檔,基類為 CObject。

(1)開啟串列埠,取得資源控制代碼

使用 CreateFile 指定串列埠裝置與操作屬性,回傳控制代碼供後續通訊操作使用。關鍵參數設定:

  • 共享模式設為 0(串列埠為不可共享裝置)
  • 建立方式必須為 OPEN_EXISTING
  • dwFlagsAndAttributes 參數:FILE_FLAG_OVERLAPPED 表示非同步通訊模式,支援重疊操作;NULL 表示同步模式(同步模式下應用程式將永久控制程式流程,遇通訊故障導致永久等待,故多採用非同步)

(2)串列埠參數設定

串列埠開啟後屬性為預設值,需根據需求調整。呼叫 GetCommState 讀取目前裝置控制區塊(DCB),修改後透過 SetCommState 寫入。並需設定非同步讀寫的逾時控制,透過 COMMTIMEOUTS 結構設定,呼叫 SetCommTimeouts 寫入。

BOOL CSimpleComm::Open()
{
    DCB dcb;
    m_hIDComDev = CreateFile("COM2",
        GENERIC_READ | GENERIC_WRITE,
        0, NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    if (m_hIDComDev == NULL) return FALSE;

    dcb.DCBlength = sizeof(DCB);
    GetCommState(m_hIDComDev, &dcb);
    dcb.BaudRate = CBR_4800;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT;
    // ... 後續設定
}

(3)串列埠讀寫操作

主要運用 ReadFile 與 WriteFile API。非同步模式下,最後一個參數為指向 OVERLAPPED 結構的非空指標。讀寫函式回傳 FALSE 時,呼叫 GetLastError 若回傳 ERROR_IO_PENDING 表示 I/O 操作掛起(轉入背景繼續執行)。此時可用 WaitForSingleObject 等待完成訊號並設定最長等待時間。

BOOL bReadStatus;
bReadStatus = ReadFile(m_hIDComDev, buffer, dwBytesRead, &dwBytesRead, &m_OverlappedRead);
if (!bReadStatus)
{
    if (GetLastError() == ERROR_IO_PENDING)
    {
        WaitForSingleObject(m_OverlappedRead.hEvent, 1000);
        return (int)dwBytesRead;
    }
    return 0;
}
return (int)dwBytesRead;

應用層整合

定義全域變數 m_Serial 作為 CSimpleComm 物件,透過呼叫成員函式實現通訊功能。相比方法一,方法二賦予更大設計彈性:埠的讀寫可選擇簡單的輪詢式,或設定與外設資料發送間隔 TimeCycle 相同的定時器:SetTimer(1, TimeCycle, NULL) 進行定時讀取/發送。

void CSampleView::OnTimer(UINT nIDEvent)
{
    char InputData[30];
    m_Serial.ReadData(InputData, 30);
    // 資料處理
}

事件驅動 I/O 讀寫(嚴格回應時間需求)

Windows 定義 9 種串列通訊事件,常用者:

  • EV_RXCHAR:接收到一個位元組,置入輸入緩衝區
  • EV_TXEMPTY:輸出緩衝區最後一個字元發送完成
  • EV_RXFLAG:接收到事件字元(DCB 中 EvtChar 成員),置入輸入緩衝區

使用 SetCommMask 指定關注事件後,呼叫 WaitCommEvent 等待事件發生。SetCommMask(hComm, 0) 可終止 WaitCommEvent。

方法三:多執行緒串列通訊實現

方法一、二適用於單執行緒通訊。許多工控系統透過擴充串列埠連接多個外設,各外設資料發送頻率不同,要求背景即時無誤捕捉、採集、處理、記錄各埠資料,這就需要在自訂通訊類別中建立埠監視執行緒,於指定事件發生時向相關視窗發送通知訊息。

執行緒基礎概念

  • 使用者介面執行緒 (UI Thread):包含訊息迴路/泵,處理接收訊息
  • 工作者執行緒 (Worker Thread):無訊息迴路,執行背景任務

監視串列埠事件的執行緒屬於工作者執行緒。

多執行緒通訊類別關鍵實作

埠配置、連線部分與單執行緒版本相同。關鍵在於根據實際情況建立多執行緒同步物件:訊號、臨界區、事件等(參考 VC++ 同步類別)。

啟動工作者執行緒

CWinThread* CommThread = AfxBeginThread(
    CommWatchThread,        // 執行緒函式名稱
    (LPVOID)m_pTTYInfo,     // 傳遞參數
    THREAD_PRIORITY_ABOVE_NORMAL,
    0,                      // 最大堆疊大小
    CREATE_SUSPENDED,       // 建立旗標
    NULL                    // 安全性旗標
);

串列埠事件監視執行緒核心邏輯

if (WaitCommEvent(pTTYInfo->idComDev, &dwEvtMask, NULL))
{
    if ((dwEvtMask & pTTYInfo->dwEvtMask) == pTTYInfo->dwEvtMask)
    {
        WaitForSingleObject(pTTYInfo->hPostEvent, 0xFFFFFFFF);
        ResetEvent(pTTYInfo->hPostEvent);  // 設定同步事件物件為非訊號態
        ::PostMessage(CSampleView, ID_COM1_DATA, 0, 0);  // 發送通知訊息
    }
}

視窗端訊息映射與處理

BEGIN_MESSAGE_MAP(CSampleView, CView)
    ON_MESSAGE(ID_COM1_DATA, OnProcessCom1Data)
    ON_MESSAGE(ID_COM2_DATA, OnProcessCom2Data)
    // ...
END_MESSAGE_MAP()

在各成員函式中完成對應串列埠資料的接收處理,但必須確保在下一次監測到資料到來前,能完成所有中間處理工作,否則會造成資料捕捉錯誤。

三種方法比較與選擇建議

三種串列通訊方法比較表

根據不同需求選擇適當方法,可達到事半功倍效果。在溫度監控系統中,筆者採用方法二,在 Windows 98/95 上運行穩定,取得良好效果。

總結建議

方法適用場景優點缺點
MSComm 控制元件簡單對話方塊應用、快速原型開發開發最快、API 封裝完整、視覺化設計需置於對話方塊、難用於執行緒、部署需註冊 OCX
單執行緒自訂類別一般應用、需靈活控制、單一埠通訊完全控制、可移植、無外部依賴需自行處理非同步、錯誤處理較複雜
多執行緒自訂類別多埠並發、即時性要求高、工控系統埠獨立運作、CPU 利用率高、擴充性佳同步機制複雜、除錯困難、開發成本最高
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved