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

一、前言

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

image-20240715144416067

image-20240715144518138

串口通信在现代技术中的应用场景极为广泛,从个人电脑连接外设(如鼠标、键盘)到工业自动化系统中的传感器网络,从移动设备的数据同步到实验室设备的控制,都能见到其身影。在嵌入式系统开发中,单片机与PC机或其他设备之间的通信经常采用串口,因为其易于实现且成本低廉。

在Windows环境下使用C语言进行串口编程,主要涉及到对Windows API函数的调用。Windows提供了丰富的API用于串口通信,包括CreateFileSetupCommPurgeCommSetCommStateSetCommTimeoutsReadFileWriteFile等,这些函数分别用于打开串口、设置串口参数、读写串口数据以及控制串口的输入输出缓冲区等。

下面示例,展示如何使用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;}// 设置串口参数dcbSerialParams.DCBlength = sizeof(dcbSerialParams);GetCommState(hComm, &dcbSerialParams);dcbSerialParams.BaudRate = CBR_9600;       // 设置波特率dcbSerialParams.ByteSize = 8;             // 设置字节大小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);// 发送数据char data[] = "Hello from PC!";DWORD dwWritten;WriteFile(hComm, data, strlen(data), &dwWritten, NULL);// 接收数据char buffer[256];DWORD dwRead;ReadFile(hComm, buffer, sizeof(buffer), &dwRead, NULL);buffer[dwRead] = '\0'; // 确保字符串以空字符结尾printf("Received: %s\n", buffer);// 关闭串口CloseHandle(hComm);return 0;
}

这段代码展示了如何打开一个串口(例如COM3),设置其通信参数,然后向串口发送数据,并从串口接收数据。通过这样的程序设计,可以实现PC机与单片机或其他串口设备之间的双向通信,为数据交换、设备控制等应用提供基础。

串口通信是连接不同设备之间的一种基本而强大的手段,尤其在嵌入式系统领域。掌握Windows环境下的串口编程,对于从事相关领域的开发者来说至关重要。

二、实操代码

2.1 串口编程的函数详解

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

1. CreateFile

功能:打开或创建一个指定的设备或文件。

语法

HANDLE CreateFile(LPCWSTR lpFileName,       // 指定文件名或设备名DWORD dwDesiredAccess,    // 请求的访问类型DWORD dwShareMode,        // 共享模式LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全属性DWORD dwCreationDisposition, // 创建或打开的处置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 // 要关闭的句柄
);

用法

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

3. GetCommState

功能:获取串口当前的通信状态。

语法

BOOL GetCommState(HANDLE hFile,     // 串口句柄LPDCB lpDCB       // 指向DCB结构体的指针
);

用法

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

4. SetCommState

功能:设置串口的通信状态。

语法

BOOL SetCommState(HANDLE hFile,     // 串口句柄LPDCB lpDCB       // 指向DCB结构体的指针
);

用法

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

5. PurgeComm

功能:清除串口的输入输出缓冲区。

语法

BOOL PurgeComm(HANDLE hFile,     // 串口句柄DWORD dwMask      // 指定要清除的缓冲区
);

用法

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

6. ReadFile

功能:从串口读取数据。

语法

BOOL ReadFile(HANDLE hFile,         // 串口句柄LPVOID lpBuffer,      // 数据缓冲区DWORD nNumberOfBytesToRead, // 要读取的字节数LPDWORD lpNumberOfBytesRead, // 实际读取的字节数LPOVERLAPPED lpOverlapped    // 异步读取时的重叠结构
);

用法

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

7. WriteFile

功能:向串口写入数据。

语法

BOOL WriteFile(HANDLE hFile,         // 串口句柄LPCVOID lpBuffer,     // 数据缓冲区DWORD nNumberOfBytesToWrite, // 要写入的字节数LPDWORD lpNumberOfBytesWritten, // 实际写入的字节数LPOVERLAPPED lpOverlapped      // 异步写入时的重叠结构
);

用法

  • 用于向串口发送数据。

8. SetCommTimeouts

功能:设置串口的超时值。

语法

BOOL SetCommTimeouts(HANDLE hFile,     // 串口句柄LPCOMMTIMEOUTS lpCommTimeouts // 指向COMMTIMEOUTS结构体的指针
);

用法

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

9. GetLastError

功能:获取上一次调用失败的错误代码。

语法

DWORD GetLastError(void);

用法

  • 当API函数调用失败时,可以调用此函数获取具体的错误代码,帮助诊断问题。

以上函数是进行串口编程时最常用的,它们共同提供了串口设备的完整控制能力。在实际编程中,你需要根据具体的应用需求选择合适的函数组合,以实现串口的高效稳定通信。

2.2 扫描当前系统可用串口端口

在Windows环境下,使用C语言来枚举所有可用的串口,可以通过调用Windows API函数来实现。

以下代码,会打印出系统上所有可用的串口名称:

#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: %ld\n", 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; // 如果错误不是缓冲区不足,则跳过}// 如果是缓冲区不足,则获取正确的缓冲区大小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: %s\n", szPortName);// 清理资源CloseHandle(hComm);}}}}return 0;
}

这个代码片段会遍历从COM1到COM256的所有可能的串口号,尝试打开每一个串口,如果成功打开,则表明该串口是可用的,并将串口号打印出来。

2.3 创建串口程序与单片机进行数据互发通信

下面是一个使用C语言在Windows环境下进行串口编程的例子,演示了如何与单片机进行数据互发通信。

创建一个程序,打开串口,设置波特率为115200,然后接收从单片机发送来的数据,将其打印出来,并将同样的数据返回给单片机。

#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;}// 设置串口参数dcbSerialParams.DCBlength = sizeof(dcbSerialParams);if (!GetCommState(hComm, &dcbSerialParams)) {printf("无法获取串口状态。\n");CloseHandle(hComm);return -1;}dcbSerialParams.BaudRate = CBR_115200;       // 设置波特率为115200dcbSerialParams.ByteSize = 8;               // 设置数据位为8位dcbSerialParams.StopBits = ONESTOPBIT;      // 设置停止位为1位dcbSerialParams.Parity = NOPARITY;          // 设置无校验位if (!SetCommState(hComm, &dcbSerialParams)) {printf("无法设置串口参数。\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;}// 循环读取和回显数据char buffer[256];DWORD dwRead, dwWritten;while (1) {memset(buffer, 0, sizeof(buffer));if (!ReadFile(hComm, buffer, sizeof(buffer)-1, &dwRead, NULL)) {printf("读取数据失败。\n");break;}if (dwRead > 0) {printf("接收到: %s\n", buffer);if (!WriteFile(hComm, buffer, dwRead, &dwWritten, NULL)) {printf("写入数据失败。\n");break;}}}// 清理资源CloseHandle(hComm);return 0;
}

在这个例子中,使用CreateFile函数打开串口,然后通过GetCommStateSetCommState函数设置串口的波特率、数据位、停止位和校验位。接着,使用SetCommTimeouts函数设置读写操作的超时时间,以防在没有数据的情况下无限等待。

接下来,进入一个无限循环,使用ReadFile函数从串口读取数据。如果读取成功,将接收到的数据打印出来,并使用WriteFile函数将同样的数据返回到串口,实现回显功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/404398.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记 韩顺平 零基础30天学会Java(2024.8.15)

P512 ArrayList底层源码2 P513 Vector注意事项 最近有点懈怠&#xff0c;要去新的环境上学了&#xff0c;有点焦虑&#xff0c;调整状态ing&#xff0c;准备开始研一&#xff0c;希望能继续本科的荣耀&#xff01;&#xff01; PS&#xff1a;本科应该算是荣耀的吧哈哈哈哈哈

html 首行缩进2字符

1. html 首行缩进2字符 1.1. 场景 在Html开发中让一段文字&#xff08;富文本等&#xff09;首行缩进两个文字&#xff0c;可能在前面加上8个“ ”&#xff0c;因为过去对CSS不熟悉&#xff0c;这种方法实现虽然比较直接&#xff0c;但是文字多的时候会有很多“ ”充斥在代码中…

深度学习设计模式之外观模式

文章目录 前言一、介绍二、特点三、详细分析1.核心组成2.代码示例3.优缺点优点缺点 4.使用场景 总结 前言 外观模式是结构型设计模式&#xff0c;定义一个高层接口&#xff0c;用来访问子系统中的众多接口&#xff0c;使系统更加容易使用。 一、介绍 外观设计模式&#xff08…

机器学习第十二章-计算学习理论

目录 12.1基础知识 12.2 PAC学习 12.3有限假设空间 12.3.1可分情形 12.3.2不可分情形 12.4VC维 12.5 Rademacher复杂度 12.1基础知识 计算学习理论研究的是关于通过"计算"来进行"学习"的理论&#xff0c;即关于机器学习的理论基础&#xff0c;其目的…

encoding with ‘idna‘ codec failed (UnicodeError: label empty or too long)

今天在使用Flask连接mysql的时候&#xff0c;遇到了一个报错&#xff1a;encoding with ‘idna’ codec failed (UnicodeError: label empty or too long) 网上查了一下说是字符集的问题&#xff0c;然后尝试修改了一下字符集&#xff0c;结果还是不行。 最后去翻阅SQLAlchemy…

汇昌联信科技做拼多多电商有哪些策略?

在当今竞争激烈的电商平台上&#xff0c;汇昌联信科技以其独到的策略成功立足拼多多。他们不仅凭借对市场的深刻理解&#xff0c;还通过一系列创新举措&#xff0c;实现了品牌的快速成长和市场份额的不断扩大。接下来&#xff0c;我们将深入探讨汇昌联信科技在拼多多平台上所采…

Python爬虫案例一:获取古诗文并按用户输入的作者名进行数据保存

前言&#xff1a; 1、什么是爬虫&#xff1f;也称为网页蜘蛛&#xff08;Web Spider&#xff09;&#xff0c;通俗来说&#xff0c;解放人的双手, 去互联网获取数据, 以数据库, txt, excel, csv, pdf, 压缩文件, image, video, music保存数据。本质: 模拟浏览器, 向服务器发送…

高性能 Web 服务器:让网页瞬间绽放的魔法引擎(上)

目录 一.Apache介绍 1.Apache prefork 模型 2.Apache worker 模型 3.Apache event模型 二.Nginx介绍 1.什么是Nginx 2.Nginx 功能介绍 3.Nginx基础特性 4.Nginx 进程结构&#xff1a;web请求处理机制 5.主进程(master process)的功能&#xff1a; 6.工作进程&#x…

k8s基础概念以及部署

kubernetes基础概念 来历 kubernetes以谷歌borg为前身&#xff0c;基于谷歌15年生产环境经验开源的一个项目。k8s是一个开源&#xff0c;的分布式的容器编排技术。 k8s的优势 对比对象 裸容器 例如docker&#xff0c;直接将容器部署在宿主机的方式被称为裸容器。 缺点 纯粹的裸…

使用docker-compose运行kafka及验证(无需zookpeer)

前言&#xff1a;要求安装docker-compose kafka镜像版本&#xff1a;apache/kafka:3.8.0 可能存在镜像拉不下来的情况&#xff1a; 1、vim /etc/docker/daemon.json {"data-root":"/data/docker","registry-mirrors": ["https://docker.m…

【C++二分查找】875. 爱吃香蕉的珂珂

本文涉及的基础知识点 C二分查找 LeetCode875. 爱吃香蕉的珂珂 珂珂喜欢吃香蕉。这里有 n 堆香蕉&#xff0c;第 i 堆中有 piles[i] 根香蕉。警卫已经离开了&#xff0c;将在 h 小时后回来。 珂珂可以决定她吃香蕉的速度 k &#xff08;单位&#xff1a;根/小时&#xff09;…

Golang基于DTM的分布式事务SAGA实战

SAGA介绍 SAGA是“长时间事务”运作效率的方法&#xff0c;大致思路是把一个大事务分解为可以交错运行的一系列子事务的集合。原本提出 SAGA 的目的&#xff0c;是为了避免大事务长时间锁定数据库的资源&#xff0c;后来才逐渐发展成将一个分布式环境中的大事务&#xff0c;分…

The Sandbox 新提案: 2024 年亚洲和拉丁美洲区块链活动预算

理事会建议&#xff1a; 积极 &#x1f642; 内容 此提案请求为2024年第四季度&#xff0c;The Sandbox 在东南亚和拉丁美洲的主要区块链活动中的激活分配 94,500 美元的 SAND 倡议预算。&#xff08;具体活动列表见下方活动描述&#xff09; 原因 区域团队希望在这些现场活…

一文学会本地部署可视化应用JSONCrack并配置公网地址实现远程协作

文章目录 前言1. Docker安装JSONCrack2. 安装Cpolar内网穿透工具3. 配置JSON Crack界面公网地址4. 远程访问 JSONCrack 界面5. 固定 JSONCrack公网地址 前言 本文主要介绍如何在Linux环境使用Docker安装数据可视化工具JSONCrack&#xff0c;并结合cpolar内网穿透工具实现团队在…

【二分查找】--- 进阶题目赏析

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey 本篇博客我们继续来了解一些有关二分查找算法的进阶题目。 &#x1f3e0; 寻找峰值 &#x1f4cc; 题目内容 162. 寻找峰值 - 力扣&#…

【python爬虫】邮政包裹物流查询2瑞数6加密

大家好呀&#xff0c;我是你们的好兄弟【星云牛马】&#xff0c;今天给大家带来的是瑞数6的补环境的总结&#xff0c;补环境肯定是需要一些基础补环境知识的&#xff0c;所以建议没有基础的小伙伴可以加入学习群进行学习&#xff0c;有基础的伙伴加入交流起来。 QQ群&#xff…

用C#写一个随机音乐播放器

form1中namespce里的代码如下 public partial class Form1 : Form {public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){string folder textBox1.Text;string folderPath folder; // 指定音频文件所在的文件夹路径OpenRandomFi…

thinkphp5漏洞分析之文件包含

目录 一、环境 二、开始研究 三、漏洞分析 四、漏洞修复 五、攻击总结 一、环境 thinkphp官网下载 创建 application/index/view/index/index.html 文件&#xff0c;内容随意&#xff08;没有这个模板文件的话&#xff0c;在渲染时程序会报错&#xff09; 二、开始研究 创…

后端开发刷题 | 链表内指定区间反转【链表篇】

描述 将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转&#xff0c;要求时间复杂度 O(n)O(n)&#xff0c;空间复杂度 O(1)O(1)。 例如&#xff1a; 给出的链表为 1→2→3→4→5→NULL1→2→3→4→5→NULL, m2,n4 返回 1→4→3→2→5→NULL 数据范围&#xff1a; 链表…

java使用itext 直接生成pdf

itext 使用 需求背景itext 的使用依赖简单示例基础设置&#xff08;页面大小、边距、字体等&#xff09;段落内部&#xff0c;特殊设置关键字 字体或颜色生成动态表格页脚展示页数其他设置密码添加水印&#xff08;背景图&#xff09;目录Header, Footer分割 PDF合并 PDF 需求背…