在實驗室與工業應用領域,串列埠是電腦與外部串列設備進行資料傳輸的常用通道。因其介面簡單、實作容易,應用極為廣泛。依據不同應用場景靈活控制串列埠程式設計,是工控軟體開發的核心技能之一。
在光學鏡片鍍膜製程中,單晶片進行多通道溫度資料採集控制,採集結果以串列方式傳入主機。每 10 秒向主機發送一次採樣資料,主機向單晶片發送控制命令,實現串列資料接收、處理、記錄、顯示與即時繪製曲線。開發環境為 Visual C++ 6.0。
與傳統 DOS 環境直接控制硬體不同,Windows 不建議應用程式直接操作硬體,而是透過作業系統提供的裝置驅動程式進行資料傳遞。在 Win32 架構下,串列埠作為檔案處理,而非直接操作連接埠。Win32 提供完整的檔案 I/O 函式與通訊函式,透過理解這些函式即可開發符合不同需求的通訊程式。
與通訊設備相關的核心結構有 7 種:COMMCONFIG、COMMPROP、COMMTIMEOUTS、COMSTAT、DCB、MODEMDEVCAPS、MODEMSETTINGS。相關 Windows API 函式共有 26 個,詳細說明請參考 MSDN 文件。以下結合實例,介紹三種實現串列通訊的方法。
這是最快速上手的方式。首先在對話方塊中建立通訊控制元件,若工具箱缺少該元件,可透過 Project → Add to Project → Components and Controls 插入,再從工具箱拖曳至對話方塊。
使用 ClassWizard 為新建立的通訊控制元件定義成員物件(CMSComm m_Serial),透過該物件即可設定串列埠屬性。MSComm 控制元件共有 27 個屬性,常用者如下:
CommPort:設定/回傳通訊埠號,預設 COM1Settings:以字串形式設定/回傳鮑率、奇偶校驗、資料位、停止位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。
使用 CreateFile 指定串列埠裝置與操作屬性,回傳控制代碼供後續通訊操作使用。關鍵參數設定:
OPEN_EXISTINGdwFlagsAndAttributes 參數:FILE_FLAG_OVERLAPPED 表示非同步通訊模式,支援重疊操作;NULL 表示同步模式(同步模式下應用程式將永久控制程式流程,遇通訊故障導致永久等待,故多採用非同步)串列埠開啟後屬性為預設值,需根據需求調整。呼叫 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;
// ... 後續設定
}
主要運用 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);
// 資料處理
}
Windows 定義 9 種串列通訊事件,常用者:
EV_RXCHAR:接收到一個位元組,置入輸入緩衝區EV_TXEMPTY:輸出緩衝區最後一個字元發送完成EV_RXFLAG:接收到事件字元(DCB 中 EvtChar 成員),置入輸入緩衝區使用 SetCommMask 指定關注事件後,呼叫 WaitCommEvent 等待事件發生。SetCommMask(hComm, 0) 可終止 WaitCommEvent。
方法一、二適用於單執行緒通訊。許多工控系統透過擴充串列埠連接多個外設,各外設資料發送頻率不同,要求背景即時無誤捕捉、採集、處理、記錄各埠資料,這就需要在自訂通訊類別中建立埠監視執行緒,於指定事件發生時向相關視窗發送通知訊息。
監視串列埠事件的執行緒屬於工作者執行緒。
埠配置、連線部分與單執行緒版本相同。關鍵在於根據實際情況建立多執行緒同步物件:訊號、臨界區、事件等(參考 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 利用率高、擴充性佳 | 同步機制複雜、除錯困難、開發成本最高 |