Windows下串口编程与单片机串口装备通信(win32-API)

打印 上一主题 下一主题

主题 553|帖子 553|积分 1659

一、前言

串行通信接口,通常简称为“串口”,是一种数据传输方式,其中信息以连续的比特流情势发送,每个比特在差异的时间点被传输。这与并行通信形成对比,在并行通信中,多个比特同时通过多个线路传输。串口通信因其简单的硬件需求和广泛的应用场景而受到青睐,尤其是在远程通信、装备控制、数据采集等领域。


串口通信在现代技术中的应用场景极为广泛,从个人电脑毗连外设(如鼠标、键盘)到工业自动化系统中的传感器网络,从移动装备的数据同步到实验室装备的控制,都能见到其身影。在嵌入式系统开发中,单片机与PC机或其他装备之间的通信经常采用串口,由于其易于实现且成本低廉。
在Windows环境下使用C语言举行串口编程,主要涉及到对Windows API函数的调用。Windows提供了丰富的API用于串口通信,包括CreateFile、SetupComm、PurgeComm、SetCommState、SetCommTimeouts、ReadFile、WriteFile等,这些函数分别用于打开串口、设置串口参数、读写串口数据以及控制串口的输入输出缓冲区等。
下面示例,展示如何使用C语言和Windows API打开指定的串口并举行通信:
  1. #include <windows.h>
  2. #include <stdio.h>
  3. int main() {
  4.     HANDLE hComm;
  5.     DCB dcbSerialParams = {0};
  6.     COMMTIMEOUTS timeouts;
  7.     // 打开串口
  8.     hComm = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  9.     if (hComm == INVALID_HANDLE_VALUE) {
  10.         printf("无法打开串口。\n");
  11.         return -1;
  12.     }
  13.     // 设置串口参数
  14.     dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
  15.     GetCommState(hComm, &dcbSerialParams);
  16.     dcbSerialParams.BaudRate = CBR_9600;       // 设置波特率
  17.     dcbSerialParams.ByteSize = 8;             // 设置字节大小
  18.     dcbSerialParams.StopBits = ONESTOPBIT;    // 设置停止位
  19.     dcbSerialParams.Parity   = NOPARITY;      // 设置校验位
  20.     SetCommState(hComm, &dcbSerialParams);
  21.     // 设置超时时间
  22.     timeouts.ReadIntervalTimeout         = MAXDWORD;
  23.     timeouts.ReadTotalTimeoutMultiplier  = 0;
  24.     timeouts.ReadTotalTimeoutConstant    = 500;
  25.     timeouts.WriteTotalTimeoutMultiplier = 0;
  26.     timeouts.WriteTotalTimeoutConstant   = 500;
  27.     SetCommTimeouts(hComm, &timeouts);
  28.     // 发送数据
  29.     char data[] = "Hello from PC!";
  30.     DWORD dwWritten;
  31.     WriteFile(hComm, data, strlen(data), &dwWritten, NULL);
  32.     // 接收数据
  33.     char buffer[256];
  34.     DWORD dwRead;
  35.     ReadFile(hComm, buffer, sizeof(buffer), &dwRead, NULL);
  36.     buffer[dwRead] = '\0'; // 确保字符串以空字符结尾
  37.     printf("Received: %s\n", buffer);
  38.     // 关闭串口
  39.     CloseHandle(hComm);
  40.     return 0;
  41. }
复制代码
这段代码展示了如何打开一个串口(例如COM3),设置其通信参数,然后向串口发送数据,并从串口接收数据。通过这样的程序计划,可以实现PC机与单片机或其他串口装备之间的双向通信,为数据交换、装备控制等应用提供根本。
串口通信是毗连差异装备之间的一种根本而强大的手段,尤其在嵌入式系统领域。把握Windows环境下的串口编程,对于从事相干领域的开发者来说至关紧张。
二、实操代码

2.1 串口编程的函数详解

在Windows环境下举行串口编程时,主要依赖于Windows API中的一系列函数。这些函数允许你控制串口的打开、配置、读写操作以及错误处理惩罚。下面是几个关键函数的具体说明,包括它们的功能、参数含义和用法:
1. CreateFile

功能:打开或创建一个指定的装备或文件。
语法
  1. HANDLE CreateFile(
  2.   LPCWSTR lpFileName,       // 指定文件名或设备名
  3.   DWORD dwDesiredAccess,    // 请求的访问类型
  4.   DWORD dwShareMode,        // 共享模式
  5.   LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全属性
  6.   DWORD dwCreationDisposition, // 创建或打开的处置
  7.   DWORD dwFlagsAndAttributes, // 文件属性
  8.   HANDLE hTemplateFile      // 模板文件句柄
  9. );
复制代码
用法


  • 通常用于打开串口装备,如CreateFile(TEXT("COM1"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2. CloseHandle

功能:关闭一个已打开的装备或文件句柄。
语法
  1. BOOL CloseHandle(
  2.   HANDLE hObject // 要关闭的句柄
  3. );
复制代码
用法


  • 在完成串口操作后调用以释放资源,如CloseHandle(hComm);
3. GetCommState

功能:获取串口当前的通信状态。
语法
  1. BOOL GetCommState(
  2.   HANDLE hFile,     // 串口句柄
  3.   LPDCB lpDCB       // 指向DCB结构体的指针
  4. );
复制代码
用法


  • 用于获取串口的当前配置,如波特率、数据位数等。
4. SetCommState

功能:设置串口的通信状态。
语法
  1. BOOL SetCommState(
  2.   HANDLE hFile,     // 串口句柄
  3.   LPDCB lpDCB       // 指向DCB结构体的指针
  4. );
复制代码
用法


  • 用于设置串口的配置参数,如波特率、数据位、制止位和奇偶校验。
5. PurgeComm

功能:清除串口的输入输出缓冲区。
语法
  1. BOOL PurgeComm(
  2.   HANDLE hFile,     // 串口句柄
  3.   DWORD dwMask      // 指定要清除的缓冲区
  4. );
复制代码
用法


  • 用于清除串口的输入或输出缓冲区,避免数据残留。
6. ReadFile

功能:从串口读取数据。
语法
  1. BOOL ReadFile(
  2.   HANDLE hFile,         // 串口句柄
  3.   LPVOID lpBuffer,      // 数据缓冲区
  4.   DWORD nNumberOfBytesToRead, // 要读取的字节数
  5.   LPDWORD lpNumberOfBytesRead, // 实际读取的字节数
  6.   LPOVERLAPPED lpOverlapped    // 异步读取时的重叠结构
  7. );
复制代码
用法


  • 用于从串口读取数据到缓冲区中。
7. WriteFile

功能:向串口写入数据。
语法
  1. BOOL WriteFile(
  2.   HANDLE hFile,         // 串口句柄
  3.   LPCVOID lpBuffer,     // 数据缓冲区
  4.   DWORD nNumberOfBytesToWrite, // 要写入的字节数
  5.   LPDWORD lpNumberOfBytesWritten, // 实际写入的字节数
  6.   LPOVERLAPPED lpOverlapped      // 异步写入时的重叠结构
  7. );
复制代码
用法


  • 用于向串口发送数据。
8. SetCommTimeouts

功能:设置串口的超时值。
语法
  1. BOOL SetCommTimeouts(
  2.   HANDLE hFile,     // 串口句柄
  3.   LPCOMMTIMEOUTS lpCommTimeouts // 指向COMMTIMEOUTS结构体的指针
  4. );
复制代码
用法


  • 用于设置读写操作的超时时间,防止无限期等待。
9. GetLastError

功能:获取上一次调用失败的错误代码。
语法
  1. DWORD GetLastError(void);
复制代码
用法


  • 当API函数调用失败时,可以调用此函数获取具体的错误代码,帮助诊断问题。
以上函数是举行串口编程时最常用的,它们共同提供了串口装备的完整控制本领。在实际编程中,你需要根据具体的应用需求选择符合的函数组合,以实现串口的高效稳固通信。
2.2 扫描当前系统可用串口端口

在Windows环境下,使用C语言来罗列所有可用的串口,可以通过调用Windows API函数来实现。
以下代码,会打印出系统上所有可用的串口名称:
  1. #include <windows.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. // 定义一个结构体存储串口信息
  5. typedef struct _SERIAL_INFO {
  6.     DWORD dwSize;
  7.     HANDLE hFile;
  8.     DWORD dwDeviceType;
  9.     DWORD dwReserved;
  10.     DWORD dwProviderSubType;
  11.     DWORD dwServiceCharacteristics;
  12.     DWORD dwVendorGuidData;
  13.     DWORD dwDriverVersion;
  14.     DWORD dwDriverDate;
  15.     DWORD dwHardwareIndex;
  16.     DWORD dwConfigFlags;
  17.     DWORD dwNumParameters;
  18.     DWORD dwNumProperties;
  19. } SERIAL_INFO;
  20. // 定义一个结构体存储串口属性
  21. typedef struct _SERIAL_PROPERTY_KEY {
  22.     DWORD dwPropertyKey;
  23.     DWORD dwPropertyType;
  24.     DWORD dwReserved;
  25. } SERIAL_PROPERTY_KEY;
  26. int main() {
  27.     DWORD dwSize = 0;
  28.     DWORD dwRetVal = 0;
  29.     HANDLE hComm = NULL;
  30.     SERIAL_INFO SerialInfo;
  31.     SERIAL_PROPERTY_KEY SerialPropKey;
  32.     TCHAR szPortName[MAX_PATH];
  33.     DWORD dwBufferSize = 0;
  34.     DWORD dwBytesReturned = 0;
  35.     DWORD dwError = 0;
  36.     // 获取所需的SERIAL_INFO结构体大小
  37.     dwRetVal = QueryDosDevice(NULL, NULL, 0);
  38.     if (dwRetVal == 0) {
  39.         dwSize = GetLastError();
  40.         SerialInfo.dwSize = dwSize;
  41.     } else {
  42.         printf("QueryDosDevice failed with error: %ld\n", GetLastError());
  43.         return -1;
  44.     }
  45.     // 枚举所有的串口
  46.     for (int i = 1; i <= 256; i++) {
  47.         wsprintf(szPortName, TEXT("COM%d"), i);
  48.         dwRetVal = QueryDosDevice(szPortName, NULL, 0);
  49.         if (dwRetVal != 0) {
  50.             continue; // 如果返回非零,则跳过,表示端口不存在或不可用
  51.         }
  52.         dwError = GetLastError();
  53.         if (dwError != ERROR_INSUFFICIENT_BUFFER) {
  54.             continue; // 如果错误不是缓冲区不足,则跳过
  55.         }
  56.         // 如果是缓冲区不足,则获取正确的缓冲区大小
  57.         dwBufferSize = dwError;
  58.         if (dwBufferSize > 0) {
  59.             SerialInfo.dwSize = dwBufferSize;
  60.             dwRetVal = QueryDosDevice(szPortName, (LPTSTR)&SerialInfo, dwBufferSize);
  61.             if (dwRetVal != 0) {
  62.                 // 成功获取串口信息,尝试打开串口
  63.                 hComm = CreateFile(szPortName,
  64.                                    GENERIC_READ | GENERIC_WRITE,
  65.                                    0, NULL,
  66.                                    OPEN_EXISTING,
  67.                                    FILE_ATTRIBUTE_NORMAL,
  68.                                    NULL);
  69.                 if (hComm != INVALID_HANDLE_VALUE) {
  70.                     // 打印可用的串口号
  71.                     wprintf(L"Found COM port: %s\n", szPortName);
  72.                     // 清理资源
  73.                     CloseHandle(hComm);
  74.                 }
  75.             }
  76.         }
  77.     }
  78.     return 0;
  79. }
复制代码
这个代码片段会遍历从COM1到COM256的所有大概的串口号,尝试打开每一个串口,如果乐成打开,则表明该串口是可用的,并将串口号打印出来。
2.3 创建串口程序与单片机举行数据互发通信

下面是一个使用C语言在Windows环境下举行串口编程的例子,演示了如何与单片机举行数据互发通信。
创建一个程序,打开串口,设置波特率为115200,然后接收从单片机发送来的数据,将其打印出来,并将同样的数据返回给单片机。
  1. #include <windows.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. int main() {
  5.     HANDLE hComm;
  6.     DCB dcbSerialParams = {0};
  7.     COMMTIMEOUTS timeouts;
  8.     // 打开串口
  9.     hComm = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
  10.     if (hComm == INVALID_HANDLE_VALUE) {
  11.         printf("无法打开串口。\n");
  12.         return -1;
  13.     }
  14.     // 设置串口参数
  15.     dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
  16.     if (!GetCommState(hComm, &dcbSerialParams)) {
  17.         printf("无法获取串口状态。\n");
  18.         CloseHandle(hComm);
  19.         return -1;
  20.     }
  21.     dcbSerialParams.BaudRate = CBR_115200;       // 设置波特率为115200
  22.     dcbSerialParams.ByteSize = 8;               // 设置数据位为8位
  23.     dcbSerialParams.StopBits = ONESTOPBIT;      // 设置停止位为1位
  24.     dcbSerialParams.Parity = NOPARITY;          // 设置无校验位
  25.     if (!SetCommState(hComm, &dcbSerialParams)) {
  26.         printf("无法设置串口参数。\n");
  27.         CloseHandle(hComm);
  28.         return -1;
  29.     }
  30.     // 设置超时时间
  31.     timeouts.ReadIntervalTimeout = MAXDWORD;
  32.     timeouts.ReadTotalTimeoutMultiplier = 0;
  33.     timeouts.ReadTotalTimeoutConstant = 500;
  34.     timeouts.WriteTotalTimeoutMultiplier = 0;
  35.     timeouts.WriteTotalTimeoutConstant = 500;
  36.     if (!SetCommTimeouts(hComm, &timeouts)) {
  37.         printf("无法设置串口超时时间。\n");
  38.         CloseHandle(hComm);
  39.         return -1;
  40.     }
  41.     // 循环读取和回显数据
  42.     char buffer[256];
  43.     DWORD dwRead, dwWritten;
  44.     while (1) {
  45.         memset(buffer, 0, sizeof(buffer));
  46.         if (!ReadFile(hComm, buffer, sizeof(buffer)-1, &dwRead, NULL)) {
  47.             printf("读取数据失败。\n");
  48.             break;
  49.         }
  50.         if (dwRead > 0) {
  51.             printf("接收到: %s\n", buffer);
  52.             if (!WriteFile(hComm, buffer, dwRead, &dwWritten, NULL)) {
  53.                 printf("写入数据失败。\n");
  54.                 break;
  55.             }
  56.         }
  57.     }
  58.     // 清理资源
  59.     CloseHandle(hComm);
  60.     return 0;
  61. }
复制代码
在这个例子中,使用CreateFile函数打开串口,然后通过GetCommState和SetCommState函数设置串口的波特率、数据位、制止位和校验位。接着,使用SetCommTimeouts函数设置读写操作的超时时间,以防在没有数据的环境下无限等待。
接下来,进入一个无限循环,使用ReadFile函数从串口读取数据。如果读取乐成,将接收到的数据打印出来,并使用WriteFile函数将同样的数据返回到串口,实现回显功能。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用户云卷云舒

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表