工業控制中,工控機(Windows 平台)常透過 RS-485 總線與智能儀表進行半雙工通訊。主節點(PC)輪詢子節點(儀表),發送命令並接收應答。Win32 下有兩種實作方式:ActiveX 元件(簡單但欠靈活)與 Windows API(掌握機制、自由靈活)。本文專注 API 實作。
| 模式 | 特性 | 適用場景 |
|---|---|---|
| 同步操作 | API 阻塞直到完成;多執行緒下阻塞監聽執行緒 | 簡單應用、低頻通訊 |
| 重疊/非同步操作 | API 立即返回,I/O 在背景進行;避免執行緒阻塞 | 高效能、即時系統、多埠並發 |
CreateFileDCB、SetupComm、SetCommTimeoutsReadFile/WriteFileCloseHandleHANDLE CreateFile(
LPCTSTR lpFileName, // "COM1"
DWORD dwDesiredAccess, // GENERIC_READ | GENERIC_WRITE
DWORD dwShareMode, // 0 (獨佔)
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition, // OPEN_EXISTING
DWORD dwFlagsAndAttributes, // 0=同步, FILE_FLAG_OVERLAPPED=非同步
HANDLE hTemplateFile // NULL
);
DCB 關鍵參數:
BaudRate:波特率(CBR_9600 等常數)ByteSize:資料位(4-8)Parity:奇偶校驗(NOPARITY、ODDPARITY、EVENPARITY)StopBits:停止位(ONESTOPBIT、TWOSTOPBITS、ONE5STOPBITS)緩衝區與超時:
SetupComm(hCom, 1024, 1024); // 輸入/輸出緩衝區 1024 bytes
COMMTIMEOUTS TimeOuts = {0};
TimeOuts.ReadIntervalTimeout = 1000; // 讀間隔超時
TimeOuts.ReadTotalTimeoutMultiplier = 500; // 讀時間係數
TimeOuts.ReadTotalTimeoutConstant = 5000; // 讀時間常數
TimeOuts.WriteTotalTimeoutMultiplier = 500; // 寫時間係數
TimeOuts.WriteTotalTimeoutConstant = 2000; // 寫時間常數
SetCommTimeouts(hCom, &TimeOuts);
// 總超時 = 時間係數 × 字元數 + 時間常數
// 同步讀取
char str[100];
DWORD wCount;
BOOL bReadStat = ReadFile(hCom, str, 100, &wCount, NULL);
// 同步寫入
char lpOutBuffer[100];
DWORD dwBytesWrite = 100;
ClearCommError(hCom, &dwErrorFlags, &ComStat);
BOOL bWriteStat = WriteFile(hCom, lpOutBuffer, dwBytesWrite, &dwBytesWrite, NULL);
OVERLAPPED m_osRead = {0};
m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
ClearCommError(hCom, &dwErrorFlags, &ComStat);
dwBytesRead = min(dwBytesRead, (DWORD)ComStat.cbInQue);
BOOL bReadStatus = ReadFile(hCom, lpInBuffer, dwBytesRead, &dwBytesRead, &m_osRead);
if (!bReadStatus && GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(m_osRead.hEvent, 2000); // 等待 2 秒
// 操作完成,m_osRead.hEvent 變為有號狀態
}
if (!bReadStatus && GetLastError() == ERROR_IO_PENDING) {
GetOverlappedResult(hCom, &m_osRead, &dwBytesRead, TRUE); // TRUE=一直等待
}
OVERLAPPED m_osWrite = {0};
m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
BOOL bWriteStat = WriteFile(hCom, buffer, dwBytesWritten, &dwBytesWritten, &m_osWrite);
if (!bWriteStat && GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(m_osWrite.hEvent, 1000);
}
文章提供兩個完整的對話框專案範例:
兩者皆實現與百特顯示儀表(XMA5000)透過 RS-485 通訊,協議為:幀 11 位元、1 起始位、8 資料位、2 停止位、半雙工。主機發送 DC1 AAA BB ETX 讀取瞬時值,儀表回應對應資料。
| 項目 | 同步版 | 非同步版 |
|---|---|---|
| CreateFile 旗標 | 0 | FILE_FLAG_OVERLAPPED |
| ReadFile/WriteFile | 最後參數 NULL | 最後參數 OVERLAPPED* |
| 等待完成方式 | 函數阻塞自動等待 | WaitForSingleObject / GetOverlappedResult |
| UI 回應性 | 可能凍結 | 不阻塞主執行緒 |
建議:簡單應用用同步,複雜/高效能需求用非同步。理解兩者差異,才能根據專案需求做最佳選擇。