一、前言
串行通信接口,通常簡稱為“串口”,是一種數(shù)據(jù)傳輸方式,其中信息以連續(xù)的比特流形式發(fā)送,每個比特在不同的時間點被傳輸。這與并行通信形成對比,在并行通信中,多個比特同時通過多個線路傳輸。串口通信因其簡單的硬件需求和廣泛的應用場景而受到青睞,尤其是在遠程通信、設備控制、數(shù)據(jù)采集等領域。
串口通信在現(xiàn)代技術中的應用場景極為廣泛,從個人電腦連接外設(如鼠標、鍵盤)到工業(yè)自動化系統(tǒng)中的傳感器網(wǎng)絡,從移動設備的數(shù)據(jù)同步到實驗室設備的控制,都能見到其身影。在嵌入式系統(tǒng)開發(fā)中,單片機與PC機或其他設備之間的通信經(jīng)常采用串口,因為其易于實現(xiàn)且成本低廉。
在Windows環(huán)境下使用C語言進行串口編程,主要涉及到對Windows API函數(shù)的調(diào)用。Windows提供了豐富的API用于串口通信,包括CreateFile
、SetupComm
、PurgeComm
、SetCommState
、SetCommTimeouts
、ReadFile
、WriteFile
等,這些函數(shù)分別用于打開串口、設置串口參數(shù)、讀寫串口數(shù)據(jù)以及控制串口的輸入輸出緩沖區(qū)等。
下面示例,展示如何使用C語言和Windows API打開指定的串口并進行通信:
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE hComm;
DCB dcbSerialParams = {0};
COMMTIMEOUTS timeouts;
// 打開串口
hComm = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hComm == INVALID_HANDLE_VALUE) {
printf("無法打開串口。n");
return -1;
}
// 設置串口參數(shù)
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
GetCommState(hComm, &dcbSerialParams);
dcbSerialParams.BaudRate = CBR_9600; // 設置波特率
dcbSerialParams.ByteSize = 8; // 設置字節(jié)大小
dcbSerialParams.StopBits = ONESTOPBIT; // 設置停止位
dcbSerialParams.Parity = NOPARITY; // 設置校驗位
SetCommState(hComm, &dcbSerialParams);
// 設置超時時間
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 500;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 500;
SetCommTimeouts(hComm, &timeouts);
// 發(fā)送數(shù)據(jù)
char data[] = "Hello from PC!";
DWORD dwWritten;
WriteFile(hComm, data, strlen(data), &dwWritten, NULL);
// 接收數(shù)據(jù)
char buffer[256];
DWORD dwRead;
ReadFile(hComm, buffer, sizeof(buffer), &dwRead, NULL);
buffer[dwRead] = '?'; // 確保字符串以空字符結尾
printf("Received: %sn", buffer);
// 關閉串口
CloseHandle(hComm);
return 0;
}
這段代碼展示了如何打開一個串口(例如COM3),設置其通信參數(shù),然后向串口發(fā)送數(shù)據(jù),并從串口接收數(shù)據(jù)。通過這樣的程序設計,可以實現(xiàn)PC機與單片機或其他串口設備之間的雙向通信,為數(shù)據(jù)交換、設備控制等應用提供基礎。
串口通信是連接不同設備之間的一種基本而強大的手段,尤其在嵌入式系統(tǒng)領域。掌握Windows環(huán)境下的串口編程,對于從事相關領域的開發(fā)者來說至關重要。
二、實操代碼
2.1 串口編程的函數(shù)詳解
在Windows環(huán)境下進行串口編程時,主要依賴于Windows API中的一系列函數(shù)。這些函數(shù)允許你控制串口的打開、配置、讀寫操作以及錯誤處理。下面是幾個關鍵函數(shù)的詳細說明,包括它們的功能、參數(shù)含義和用法:
1. CreateFile
功能:打開或創(chuàng)建一個指定的設備或文件。
語法:
HANDLE CreateFile(
LPCWSTR lpFileName, // 指定文件名或設備名
DWORD dwDesiredAccess, // 請求的訪問類型
DWORD dwShareMode, // 共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全屬性
DWORD dwCreationDisposition, // 創(chuàng)建或打開的處置
DWORD dwFlagsAndAttributes, // 文件屬性
HANDLE hTemplateFile // 模板文件句柄
);
用法:
- 通常用于打開串口設備,如
CreateFile(TEXT("COM1"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2. CloseHandle
功能:關閉一個已打開的設備或文件句柄。
語法:
BOOL CloseHandle(
HANDLE hObject // 要關閉的句柄
);
用法:
- 在完成串口操作后調(diào)用以釋放資源,如
CloseHandle(hComm);
3. GetCommState
功能:獲取串口當前的通信狀態(tài)。
語法:
BOOL GetCommState(
HANDLE hFile, // 串口句柄
LPDCB lpDCB // 指向DCB結構體的指針
);
用法:
- 用于獲取串口的當前配置,如波特率、數(shù)據(jù)位數(shù)等。
4. SetCommState
功能:設置串口的通信狀態(tài)。
語法:
BOOL SetCommState(
HANDLE hFile, // 串口句柄
LPDCB lpDCB // 指向DCB結構體的指針
);
用法:
- 用于設置串口的配置參數(shù),如波特率、數(shù)據(jù)位、停止位和奇偶校驗。
5. PurgeComm
功能:清除串口的輸入輸出緩沖區(qū)。
語法:
BOOL PurgeComm(
HANDLE hFile, // 串口句柄
DWORD dwMask // 指定要清除的緩沖區(qū)
);
用法:
- 用于清除串口的輸入或輸出緩沖區(qū),避免數(shù)據(jù)殘留。
6. ReadFile
功能:從串口讀取數(shù)據(jù)。
語法:
BOOL ReadFile(
HANDLE hFile, // 串口句柄
LPVOID lpBuffer, // 數(shù)據(jù)緩沖區(qū)
DWORD nNumberOfBytesToRead, // 要讀取的字節(jié)數(shù)
LPDWORD lpNumberOfBytesRead, // 實際讀取的字節(jié)數(shù)
LPOVERLAPPED lpOverlapped // 異步讀取時的重疊結構
);
用法:
- 用于從串口讀取數(shù)據(jù)到緩沖區(qū)中。
7. WriteFile
功能:向串口寫入數(shù)據(jù)。
語法:
BOOL WriteFile(
HANDLE hFile, // 串口句柄
LPCVOID lpBuffer, // 數(shù)據(jù)緩沖區(qū)
DWORD nNumberOfBytesToWrite, // 要寫入的字節(jié)數(shù)
LPDWORD lpNumberOfBytesWritten, // 實際寫入的字節(jié)數(shù)
LPOVERLAPPED lpOverlapped // 異步寫入時的重疊結構
);
用法:
- 用于向串口發(fā)送數(shù)據(jù)。
8. SetCommTimeouts
功能:設置串口的超時值。
語法:
BOOL SetCommTimeouts(
HANDLE hFile, // 串口句柄
LPCOMMTIMEOUTS lpCommTimeouts // 指向COMMTIMEOUTS結構體的指針
);
用法:
- 用于設置讀寫操作的超時時間,防止無限期等待。
9. GetLastError
功能:獲取上一次調(diào)用失敗的錯誤代碼。
語法:
DWORD GetLastError(void);
用法:
- 當API函數(shù)調(diào)用失敗時,可以調(diào)用此函數(shù)獲取具體的錯誤代碼,幫助診斷問題。
以上函數(shù)是進行串口編程時最常用的,它們共同提供了串口設備的完整控制能力。在實際編程中,你需要根據(jù)具體的應用需求選擇合適的函數(shù)組合,以實現(xiàn)串口的高效穩(wěn)定通信。
2.2 掃描當前系統(tǒng)可用串口端口
在Windows環(huán)境下,使用C語言來枚舉所有可用的串口,可以通過調(diào)用Windows API函數(shù)來實現(xiàn)。
以下代碼,會打印出系統(tǒng)上所有可用的串口名稱:
#include <windows.h>
#include <stdio.h>
#include <string.h>
// 定義一個結構體存儲串口信息
typedef struct _SERIAL_INFO {
DWORD dwSize;
HANDLE hFile;
DWORD dwDeviceType;
DWORD dwReserved;
DWORD dwProviderSubType;
DWORD dwServiceCharacteristics;
DWORD dwVendorGuidData;
DWORD dwDriverVersion;
DWORD dwDriverDate;
DWORD dwHardwareIndex;
DWORD dwConfigFlags;
DWORD dwNumParameters;
DWORD dwNumProperties;
} SERIAL_INFO;
// 定義一個結構體存儲串口屬性
typedef struct _SERIAL_PROPERTY_KEY {
DWORD dwPropertyKey;
DWORD dwPropertyType;
DWORD dwReserved;
} SERIAL_PROPERTY_KEY;
int main() {
DWORD dwSize = 0;
DWORD dwRetVal = 0;
HANDLE hComm = NULL;
SERIAL_INFO SerialInfo;
SERIAL_PROPERTY_KEY SerialPropKey;
TCHAR szPortName[MAX_PATH];
DWORD dwBufferSize = 0;
DWORD dwBytesReturned = 0;
DWORD dwError = 0;
// 獲取所需的SERIAL_INFO結構體大小
dwRetVal = QueryDosDevice(NULL, NULL, 0);
if (dwRetVal == 0) {
dwSize = GetLastError();
SerialInfo.dwSize = dwSize;
} else {
printf("QueryDosDevice failed with error: %ldn", GetLastError());
return -1;
}
// 枚舉所有的串口
for (int i = 1; i <= 256; i++) {
wsprintf(szPortName, TEXT("COM%d"), i);
dwRetVal = QueryDosDevice(szPortName, NULL, 0);
if (dwRetVal != 0) {
continue; // 如果返回非零,則跳過,表示端口不存在或不可用
}
dwError = GetLastError();
if (dwError != ERROR_INSUFFICIENT_BUFFER) {
continue; // 如果錯誤不是緩沖區(qū)不足,則跳過
}
// 如果是緩沖區(qū)不足,則獲取正確的緩沖區(qū)大小
dwBufferSize = dwError;
if (dwBufferSize > 0) {
SerialInfo.dwSize = dwBufferSize;
dwRetVal = QueryDosDevice(szPortName, (LPTSTR)&SerialInfo, dwBufferSize);
if (dwRetVal != 0) {
// 成功獲取串口信息,嘗試打開串口
hComm = CreateFile(szPortName,
GENERIC_READ | GENERIC_WRITE,
0, NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hComm != INVALID_HANDLE_VALUE) {
// 打印可用的串口號
wprintf(L"Found COM port: %sn", szPortName);
// 清理資源
CloseHandle(hComm);
}
}
}
}
return 0;
}
這個代碼片段會遍歷從COM1到COM256的所有可能的串口號,嘗試打開每一個串口,如果成功打開,則表明該串口是可用的,并將串口號打印出來。
2.3 創(chuàng)建串口程序與單片機進行數(shù)據(jù)互發(fā)通信
下面是一個使用C語言在Windows環(huán)境下進行串口編程的例子,演示了如何與單片機進行數(shù)據(jù)互發(fā)通信。
創(chuàng)建一個程序,打開串口,設置波特率為115200,然后接收從單片機發(fā)送來的數(shù)據(jù),將其打印出來,并將同樣的數(shù)據(jù)返回給單片機。
#include <windows.h>
#include <stdio.h>
#include <string.h>
int main() {
HANDLE hComm;
DCB dcbSerialParams = {0};
COMMTIMEOUTS timeouts;
// 打開串口
hComm = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hComm == INVALID_HANDLE_VALUE) {
printf("無法打開串口。n");
return -1;
}
// 設置串口參數(shù)
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
if (!GetCommState(hComm, &dcbSerialParams)) {
printf("無法獲取串口狀態(tài)。n");
CloseHandle(hComm);
return -1;
}
dcbSerialParams.BaudRate = CBR_115200; // 設置波特率為115200
dcbSerialParams.ByteSize = 8; // 設置數(shù)據(jù)位為8位
dcbSerialParams.StopBits = ONESTOPBIT; // 設置停止位為1位
dcbSerialParams.Parity = NOPARITY; // 設置無校驗位
if (!SetCommState(hComm, &dcbSerialParams)) {
printf("無法設置串口參數(shù)。n");
CloseHandle(hComm);
return -1;
}
// 設置超時時間
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 500;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 500;
if (!SetCommTimeouts(hComm, &timeouts)) {
printf("無法設置串口超時時間。n");
CloseHandle(hComm);
return -1;
}
// 循環(huán)讀取和回顯數(shù)據(jù)
char buffer[256];
DWORD dwRead, dwWritten;
while (1) {
memset(buffer, 0, sizeof(buffer));
if (!ReadFile(hComm, buffer, sizeof(buffer)-1, &dwRead, NULL)) {
printf("讀取數(shù)據(jù)失敗。n");
break;
}
if (dwRead > 0) {
printf("接收到: %sn", buffer);
if (!WriteFile(hComm, buffer, dwRead, &dwWritten, NULL)) {
printf("寫入數(shù)據(jù)失敗。n");
break;
}
}
}
// 清理資源
CloseHandle(hComm);
return 0;
}
在這個例子中,使用CreateFile
函數(shù)打開串口,然后通過GetCommState
和SetCommState
函數(shù)設置串口的波特率、數(shù)據(jù)位、停止位和校驗位。接著,使用SetCommTimeouts
函數(shù)設置讀寫操作的超時時間,以防在沒有數(shù)據(jù)的情況下無限等待。
接下來,進入一個無限循環(huán),使用ReadFile
函數(shù)從串口讀取數(shù)據(jù)。如果讀取成功,將接收到的數(shù)據(jù)打印出來,并使用WriteFile
函數(shù)將同樣的數(shù)據(jù)返回到串口,實現(xiàn)回顯功能。