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

news2024/11/26 22:44:37

一、前言

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

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;       // 设置波特率为115200
    dcbSerialParams.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.coloradmin.cn/o/2045115.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

运维的利器–监控–zabbix–第二步:建设–部署zabbix agent5.0–客户端是linux系统–实操记录xy

文章目录 部署zabbix agent5.0–客户端是linux系统第一步&#xff1a;安装agent第二&#x1f645;‍&#xff1a;更改agent配置文件第三&#x1f645;‍&#xff1a;防火墙配置第四&#x1f645;‍&#xff1a;启动agent进程第五&#x1f645;‍&#xff1a;网页端添加主机失败…

互联网大厂薪资分布:90%的人月薪拿到2w-5w!你拖后腿了吗?

互联网大厂的高薪&#xff0c;早已是众人皆知&#xff0c;但究竟高到何种程度&#xff1f;普通人的薪资水平在大厂中处于什么位置&#xff1f;我们是否也拥有跻身高薪行列的可能&#xff1f; 带着这些疑问&#xff0c;我们不妨来深入探究一下互联网大厂的薪资分布情况。 如果…

在阿里云ecs上构建一个WordPress博客网站

1、购买ECS 使用抢占式实例&#xff0c;RDS 使用按量付费 2、在安全组的出入方向添加80端口 3、购买一个公网IP绑定该ecs 4、云数据库rds选择按量付费 5、创建一个名为test_user的普通账号 6、创建数据库 7、设置RDS实例白名单 8、远程登录ecs实例 9、安装apache服务及其扩展包…

【PyCharm激活】2024年最新下载安装激活汉化Pycharm教程!(附激活码)

PyCharm激活码&#xff08;下方领取&#xff09;&#xff1a; 一、下载PyCharm 访问官网&#xff1a; 打开浏览器&#xff0c;访问PyCharm官网。 选择版本&#xff1a; PyCharm提供两个版本&#xff1a;专业版&#xff08;Professional&#xff09;和社区版&#xff08;Commun…

企业级 Zabbix 监控

一、zabbix 监控 1、zabbix 监控架构 zabbix的监控架构在实际监控架构中&#xff0c;zabbix根据网络环境、监控规模等 分了如下两种架构&#xff1a; server-client 、server-proxy-client 1、server-client架构也是zabbix的最简单的架构&#xff0c;监控机和被监控机之间不经…

ElasticSearch文档数据关联关系处理

文章目录 ES如何处理关联关系对象类型案例一 适用场景案例二 不适用场景 嵌套对象nested object父子关联关系嵌套文档 VS 父子关系 ES如何处理关联关系 关系型数据库中的范式化&#xff1a; 减少了数据冗余&#xff0c;节省了磁盘空间减少了不必要的更新操作&#xff0c;因为…

SQL注入(head、报错、盲注)

目录 【学习目标、重难点知识】 【学习目标】 【重难点知识】 1. 报错注入 1.1 那么什么是报错注入呢&#xff1f; 1.2 报错注入原理 extractvalue函数 updatexml函数 1.3 靶场解析 靶场练习 2. HEAD注入 2.1 相关全局变量 2.2 靶场解析 burp暴力破解 靶场练习 3…

关于LLC知识6

总阻抗容抗感抗R 容抗和感抗可以互相抵消&#xff0c;但无论容抗或者感抗都不可以和R相互抵消。 容抗、感抗和R可以这样表示。 这里就出现了一个字母&#xff1a;j&#xff08;沿着坐标系逆时针旋转90&#xff09; 所以&#xff0c;总阻抗-j5Ω 所以&#xff0c;无论怎样&a…

虚拟机哪个软件最好用? 苹果电脑用虚拟机运行Windows程序 Mac电脑怎么玩Windows游戏

虚拟化技术就像是科技界的瑞士军刀&#xff0c;macOS用户就像是在寻找最锋利的那把刀。随着计算机技术的不断发展&#xff0c;虚拟机软件在现代信息技术领域中扮演着越来越重要的角色。虚拟机不仅可以帮助用户在一台物理机器上运行多个操作系统&#xff0c;还能有效隔离不同环境…

Windows Server 2016 Standard 修改远程登录服务 Remote Desktop Service 默认端口号

为什么要改默认端口号&#xff1f; 主要是为了安全&#xff0c;如果该服务持续被攻击&#xff0c;会触发系统的安全机制&#xff0c;锁定账号密码&#xff0c;导致无法远程登录服务器。那可就麻烦大了。。。 1 打开注册表 在CMD命令行窗口输入 “regedit” 2 在注册表中&…

CAD二次开发IFoxCAD框架系列(16)- IFoxCad的架构介绍

主要是提供一个最小化的内核&#xff0c;即 DBTrans、SymbolTable、ResultData、SelectFilter 等基础类&#xff0c;其他的功能都通过扩展方法的方式来实现。 DBTrans类 事务管理器是cad .net二次开发非常重要的一部分&#xff0c;只要涉及到读写cad数据的地方几乎都在事务里…

掌握Selenium爬虫的日志管理:调整–log-level选项的用法

介绍 在使用Selenium进行Web数据采集时&#xff0c;日志管理是一个至关重要的部分。日志不仅帮助开发者监控爬虫的运行状态&#xff0c;还能在出现问题时提供有价值的调试信息。Selenium提供了多种日志级别选项&#xff0c;通过调整–log-level参数&#xff0c;开发者可以控制日…

架起高效工作与持续学习之间的桥梁

程序员如何平衡日常编码工作与提升式学习&#xff1f; 在快速迭代的编程世界中&#xff0c;程序员们不仅需要高效完成日常编码任务&#xff0c;还需不断学习新技术、深化专业知识&#xff0c;以应对日益复杂的项目挑战。然而&#xff0c;如何在繁忙琐碎的编码工作与个人成长之…

Facebook的区块链技术:提升数据安全与隐私保护

去中心化的优势 随着数字化时代的快速发展&#xff0c;数据安全和隐私保护已成为全球范围内备受关注的话题。Facebook作为全球最大的社交平台之一&#xff0c;正在积极探索如何通过区块链技术来提升数据的安全性和用户的隐私保护。区块链技术以其去中心化、不可篡改和透明的特…

网络硬盘录像机NVR解決方案:海思3520D模组与全面的NVR方案支持

随着视频监控技术的不断发展&#xff0c;网络硬盘录像机&#xff08;NVR&#xff09;已经成为现代安防系统中不可或缺的一部分。NVR作为视频监控系统的核心设备&#xff0c;不仅负责视频的实时录制和存储&#xff0c;还承担着视频回放、告警触发、远程监控等重要功能。 我们基…

C语言中的⽂件操作

1. 为什么使⽤⽂件&#xff1f; 如果没有⽂件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运⾏程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进⾏持久化…

科望医药两年亏损超16亿:大幅收缩成本,核心产品试验数据欠佳

《港湾商业观察》黄懿 6月27日&#xff0c;科望医药集团&#xff08;下称“科望医药”&#xff09;向港交所提交上市申请&#xff0c;中信证券为其独家保荐人。 科望医药是一家处于临床阶段的生物医药公司&#xff0c;利用新一代疗法创新肿瘤治疗。 该公司大有来头&#xff…

【VRPCB】Python+Gurobi求解运输问题建模实践三

采用PythonGurobi求解带有集群回程需求的VRPCB问题 目录 1. 模型1.1 VRPB问题介绍1.2 数学模型1.2.1 模型参数1.2.2 数学模型1.2.3 模型分解 2. 数据结构3. Gurobi源码4. 求解结果参考 1. 模型 1.1 VRPB问题介绍 带有回程需求的VRP问题&#xff08;VRP with Backhauls,VRPB)最…

PTrade常见问题系列23—量化是否支持读写文件?如何实现?

jupyterhub进程异常退出&#xff1f; 1、检查/var/log/jupyterhub.log日志&#xff0c;发现在进程503之前存在QA_DATA的请求URL&#xff0c;该问题是通过终端内帮助文档页面的常见问题说明链接跳转时&#xff0c;存在小概率导致hub进程503的问题&#xff1b; 2、已提交需求202…

高质量翻译对增强游戏对用户情感影响的影响

游戏中的事件往往是游戏中最难忘、最激动人心的时刻。这些事件——无论是戏剧性的情节转折、激烈的战斗&#xff0c;还是发自内心的角色互动——都是为了唤起玩家强烈的情感&#xff0c;让他们深深地投入到游戏中。然而&#xff0c;如果这些事件不能有效地传达给不同语言和文化…