• 方案介紹
  • 附件下載
  • 相關(guān)推薦
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

Windows下串口編程與單片機(jī)串口設(shè)備通信(win32-API)

07/02 09:00
191
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

更多詳細(xì)資料請(qǐng)聯(lián)系.docx

共1個(gè)文件

一、前言

串行通信接口,通常簡(jiǎn)稱為“串口”,是一種數(shù)據(jù)傳輸方式,其中信息以連續(xù)的比特流形式發(fā)送,每個(gè)比特在不同的時(shí)間點(diǎn)被傳輸。這與并行通信形成對(duì)比,在并行通信中,多個(gè)比特同時(shí)通過多個(gè)線路傳輸。串口通信因其簡(jiǎn)單的硬件需求和廣泛的應(yīng)用場(chǎng)景而受到青睞,尤其是在遠(yuǎn)程通信、設(shè)備控制、數(shù)據(jù)采集等領(lǐng)域。

image-20240715144416067

image-20240715144518138

串口通信在現(xiàn)代技術(shù)中的應(yīng)用場(chǎng)景極為廣泛,從個(gè)人電腦連接外設(shè)(如鼠標(biāo)、鍵盤)到工業(yè)自動(dòng)化系統(tǒng)中的傳感器網(wǎng)絡(luò),從移動(dòng)設(shè)備的數(shù)據(jù)同步到實(shí)驗(yàn)室設(shè)備的控制,都能見到其身影。在嵌入式系統(tǒng)開發(fā)中,單片機(jī)與PC機(jī)或其他設(shè)備之間的通信經(jīng)常采用串口,因?yàn)槠湟子趯?shí)現(xiàn)且成本低廉。

在Windows環(huán)境下使用C語(yǔ)言進(jìn)行串口編程,主要涉及到對(duì)Windows API函數(shù)的調(diào)用。Windows提供了豐富的API用于串口通信,包括CreateFileSetupComm、PurgeComm、SetCommState、SetCommTimeouts、ReadFileWriteFile等,這些函數(shù)分別用于打開串口、設(shè)置串口參數(shù)、讀寫串口數(shù)據(jù)以及控制串口的輸入輸出緩沖區(qū)等。

下面示例,展示如何使用C語(yǔ)言和Windows API打開指定的串口并進(jìn)行通信:

#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è)置串口參數(shù)
    dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
    GetCommState(hComm, &dcbSerialParams);
    dcbSerialParams.BaudRate = CBR_9600;       // 設(shè)置波特率
    dcbSerialParams.ByteSize = 8;             // 設(shè)置字節(jié)大小
    dcbSerialParams.StopBits = ONESTOPBIT;    // 設(shè)置停止位
    dcbSerialParams.Parity   = NOPARITY;      // 設(shè)置校驗(yàn)位
    SetCommState(hComm, &dcbSerialParams);

    // 設(shè)置超時(shí)時(shí)間
    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] = '?'; // 確保字符串以空字符結(jié)尾
    printf("Received: %sn", buffer);

    // 關(guān)閉串口
    CloseHandle(hComm);
    return 0;
}

這段代碼展示了如何打開一個(gè)串口(例如COM3),設(shè)置其通信參數(shù),然后向串口發(fā)送數(shù)據(jù),并從串口接收數(shù)據(jù)。通過這樣的程序設(shè)計(jì),可以實(shí)現(xiàn)PC機(jī)與單片機(jī)或其他串口設(shè)備之間的雙向通信,為數(shù)據(jù)交換、設(shè)備控制等應(yīng)用提供基礎(chǔ)。

串口通信是連接不同設(shè)備之間的一種基本而強(qiáng)大的手段,尤其在嵌入式系統(tǒng)領(lǐng)域。掌握Windows環(huán)境下的串口編程,對(duì)于從事相關(guān)領(lǐng)域的開發(fā)者來說至關(guān)重要。

二、實(shí)操代碼

2.1 串口編程的函數(shù)詳解

在Windows環(huán)境下進(jìn)行串口編程時(shí),主要依賴于Windows API中的一系列函數(shù)。這些函數(shù)允許你控制串口的打開、配置、讀寫操作以及錯(cuò)誤處理。下面是幾個(gè)關(guān)鍵函數(shù)的詳細(xì)說明,包括它們的功能、參數(shù)含義和用法:

1. CreateFile

功能:打開或創(chuàng)建一個(gè)指定的設(shè)備或文件。

語(yǔ)法

HANDLE CreateFile(
  LPCWSTR lpFileName,       // 指定文件名或設(shè)備名
  DWORD dwDesiredAccess,    // 請(qǐng)求的訪問類型
  DWORD dwShareMode,        // 共享模式
  LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全屬性
  DWORD dwCreationDisposition, // 創(chuàng)建或打開的處置
  DWORD dwFlagsAndAttributes, // 文件屬性
  HANDLE hTemplateFile      // 模板文件句柄
);

用法

  • 通常用于打開串口設(shè)備,如CreateFile(TEXT("COM1"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

2. CloseHandle

功能:關(guān)閉一個(gè)已打開的設(shè)備或文件句柄。

語(yǔ)法

BOOL CloseHandle(
  HANDLE hObject // 要關(guān)閉的句柄
);

用法

  • 在完成串口操作后調(diào)用以釋放資源,如CloseHandle(hComm);

3. GetCommState

功能:獲取串口當(dāng)前的通信狀態(tài)。

語(yǔ)法

BOOL GetCommState(
  HANDLE hFile,     // 串口句柄
  LPDCB lpDCB       // 指向DCB結(jié)構(gòu)體的指針
);

用法

  • 用于獲取串口的當(dāng)前配置,如波特率、數(shù)據(jù)位數(shù)等。

4. SetCommState

功能:設(shè)置串口的通信狀態(tài)。

語(yǔ)法

BOOL SetCommState(
  HANDLE hFile,     // 串口句柄
  LPDCB lpDCB       // 指向DCB結(jié)構(gòu)體的指針
);

用法

  • 用于設(shè)置串口的配置參數(shù),如波特率、數(shù)據(jù)位、停止位和奇偶校驗(yàn)。

5. PurgeComm

功能:清除串口的輸入輸出緩沖區(qū)。

語(yǔ)法

BOOL PurgeComm(
  HANDLE hFile,     // 串口句柄
  DWORD dwMask      // 指定要清除的緩沖區(qū)
);

用法

  • 用于清除串口的輸入或輸出緩沖區(qū),避免數(shù)據(jù)殘留。

6. ReadFile

功能:從串口讀取數(shù)據(jù)。

語(yǔ)法

BOOL ReadFile(
  HANDLE hFile,         // 串口句柄
  LPVOID lpBuffer,      // 數(shù)據(jù)緩沖區(qū)
  DWORD nNumberOfBytesToRead, // 要讀取的字節(jié)數(shù)
  LPDWORD lpNumberOfBytesRead, // 實(shí)際讀取的字節(jié)數(shù)
  LPOVERLAPPED lpOverlapped    // 異步讀取時(shí)的重疊結(jié)構(gòu)
);

用法

  • 用于從串口讀取數(shù)據(jù)到緩沖區(qū)中。

7. WriteFile

功能:向串口寫入數(shù)據(jù)。

語(yǔ)法

BOOL WriteFile(
  HANDLE hFile,         // 串口句柄
  LPCVOID lpBuffer,     // 數(shù)據(jù)緩沖區(qū)
  DWORD nNumberOfBytesToWrite, // 要寫入的字節(jié)數(shù)
  LPDWORD lpNumberOfBytesWritten, // 實(shí)際寫入的字節(jié)數(shù)
  LPOVERLAPPED lpOverlapped      // 異步寫入時(shí)的重疊結(jié)構(gòu)
);

用法

  • 用于向串口發(fā)送數(shù)據(jù)。

8. SetCommTimeouts

功能:設(shè)置串口的超時(shí)值。

語(yǔ)法

BOOL SetCommTimeouts(
  HANDLE hFile,     // 串口句柄
  LPCOMMTIMEOUTS lpCommTimeouts // 指向COMMTIMEOUTS結(jié)構(gòu)體的指針
);

用法

  • 用于設(shè)置讀寫操作的超時(shí)時(shí)間,防止無限期等待。

9. GetLastError

功能:獲取上一次調(diào)用失敗的錯(cuò)誤代碼。

語(yǔ)法

DWORD GetLastError(void);

用法

  • 當(dāng)API函數(shù)調(diào)用失敗時(shí),可以調(diào)用此函數(shù)獲取具體的錯(cuò)誤代碼,幫助診斷問題。

以上函數(shù)是進(jìn)行串口編程時(shí)最常用的,它們共同提供了串口設(shè)備的完整控制能力。在實(shí)際編程中,你需要根據(jù)具體的應(yīng)用需求選擇合適的函數(shù)組合,以實(shí)現(xiàn)串口的高效穩(wěn)定通信。

2.2 掃描當(dāng)前系統(tǒng)可用串口端口

在Windows環(huán)境下,使用C語(yǔ)言來枚舉所有可用的串口,可以通過調(diào)用Windows API函數(shù)來實(shí)現(xiàn)。

以下代碼,會(huì)打印出系統(tǒng)上所有可用的串口名稱:

#include <windows.h>
#include <stdio.h>
#include <string.h>

// 定義一個(gè)結(jié)構(gòu)體存儲(chǔ)串口信息
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;

// 定義一個(gè)結(jié)構(gòu)體存儲(chǔ)串口屬性
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結(jié)構(gòu)體大小
    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; // 如果錯(cuò)誤不是緩沖區(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) {
                    // 打印可用的串口號(hào)
                    wprintf(L"Found COM port: %sn", szPortName);
                    // 清理資源
                    CloseHandle(hComm);
                }
            }
        }
    }

    return 0;
}

這個(gè)代碼片段會(huì)遍歷從COM1到COM256的所有可能的串口號(hào),嘗試打開每一個(gè)串口,如果成功打開,則表明該串口是可用的,并將串口號(hào)打印出來。

2.3 創(chuàng)建串口程序與單片機(jī)進(jìn)行數(shù)據(jù)互發(fā)通信

下面是一個(gè)使用C語(yǔ)言在Windows環(huán)境下進(jìn)行串口編程的例子,演示了如何與單片機(jī)進(jìn)行數(shù)據(jù)互發(fā)通信。

創(chuàng)建一個(gè)程序,打開串口,設(shè)置波特率為115200,然后接收從單片機(jī)發(fā)送來的數(shù)據(jù),將其打印出來,并將同樣的數(shù)據(jù)返回給單片機(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è)置串口參數(shù)
    dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
    if (!GetCommState(hComm, &dcbSerialParams)) {
        printf("無法獲取串口狀態(tài)。n");
        CloseHandle(hComm);
        return -1;
    }

    dcbSerialParams.BaudRate = CBR_115200;       // 設(shè)置波特率為115200
    dcbSerialParams.ByteSize = 8;               // 設(shè)置數(shù)據(jù)位為8位
    dcbSerialParams.StopBits = ONESTOPBIT;      // 設(shè)置停止位為1位
    dcbSerialParams.Parity = NOPARITY;          // 設(shè)置無校驗(yàn)位

    if (!SetCommState(hComm, &dcbSerialParams)) {
        printf("無法設(shè)置串口參數(shù)。n");
        CloseHandle(hComm);
        return -1;
    }

    // 設(shè)置超時(shí)時(shí)間
    timeouts.ReadIntervalTimeout = MAXDWORD;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.ReadTotalTimeoutConstant = 500;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 500;
    if (!SetCommTimeouts(hComm, &timeouts)) {
        printf("無法設(shè)置串口超時(shí)時(shí)間。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;
}

在這個(gè)例子中,使用CreateFile函數(shù)打開串口,然后通過GetCommStateSetCommState函數(shù)設(shè)置串口的波特率、數(shù)據(jù)位、停止位和校驗(yàn)位。接著,使用SetCommTimeouts函數(shù)設(shè)置讀寫操作的超時(shí)時(shí)間,以防在沒有數(shù)據(jù)的情況下無限等待。

接下來,進(jìn)入一個(gè)無限循環(huán),使用ReadFile函數(shù)從串口讀取數(shù)據(jù)。如果讀取成功,將接收到的數(shù)據(jù)打印出來,并使用WriteFile函數(shù)將同樣的數(shù)據(jù)返回到串口,實(shí)現(xiàn)回顯功能。

  • 更多詳細(xì)資料請(qǐng)聯(lián)系.docx
    下載

相關(guān)推薦