C++中的串口连接
本文介绍了使用 Windows API 打开、读取、写入和管理串行端口连接的基础知识。
本文的主要目的是让您基本了解编程中的串行通信如何工作,并让您朝着正确的方向开始。
本文假定您对 C/C++ 有基本的了解,可以编译和运行程序,并且您的开发环境已设置为使用 Windows API 调用。
C++中的串口连接
C++中读取数据或写入串口有六个步骤:
- 打开串口
- 设置一些基本属性
- 设置超时时间
- 读取或写入数据
- 清理端口
- 一些高级功能
打开串口
第一步是开放港口。 这是最简单的步骤之一,特别是如果您已经了解 Windows 文件 I/O。
首先,您必须确保已在文件中包含所需的头文件,即 windows.h。 然后,使用以下代码:
HANDLE h_Serial;
h_Serial = CreateFile("COM1",GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if(h_Serial==INVALID_HANDLE_VALUE){
if(GetLastError()==ERROR_FILE_NOT_FOUND){
//serial port not found. Handle error here.
}
//any other error. Handle error here.
}
我们在第一行创建了一个HANDLE类型的变量,然后调用函数CreateFile对其进行初始化。 该函数的第一个参数是您需要打开的文件的名称; 在本例中,我们想要打开一个串行端口,这就是为什么我们使用该端口的名称,即 COM1。
第二个参数是指定我们是否需要读取或写入数据。 如果不需要完成任何任务,则可以保留此参数。
接下来的两个参数始终保持为零。 下一个参数是指定是否需要打开现有文件或创建新文件。
在本例中,它是端口,因此我们使用 OPEN_EXISTING。 下一个参数是向 Windows 指定我们不需要任何花哨的东西,但需要定期读取或写入。
最后一个参数也始终保持为零。
设置基本属性
获得端口的句柄后,我们需要设置一些基本属性,如带宽速率、字节大小、停止位等。这是使用 struct DCB 完成的。
DCB dcbSerialParam = {0};
dcbSerial.DCBlength=sizeof(dcbSerialParam);
if (!GetCommState(h_Serial, &dcbSerialParam)) {
//handle error here
}
dcbSerialParam.BaudRate=CBR_19200;
dcbSerialParam.ByteSize=8;
dcbSerialParam.StopBits=ONESTOPBIT;
dcbSerialParam.Parity=NOPARITY;
if(!SetCommState(h_Serial, &dcbSerialParam)){
//handle error here
}
在上面的代码片段中,我们在第一行代码中创建了一个 DCB 对象,并将其初始化为零以清除值。 在下面的行中,我们设置了该结构的长度,这是 Windows 的强制步骤。
之后,我们调用函数 GetCommState 并向其传递两个参数,即我们的端口 HANDLE 和 DCB 对象,以填写当前使用的参数。
一旦我们有了这个,我们必须设置关键参数,即波特率、字节大小、停止位和奇偶校验。
Windows 需要我们使用特殊常量提供 BaudRate。 例如,CBR 19200 代表 19200 波特,CBR 9600 代表 9600 波特,CBR 57600 代表 57600 波特等。
我们可以直接定义 ByteSize,但 StopBits 和 Parity 需要额外的变量。 ONESTOPBIT、ONE5STOPBITS 和 TWOSTOPBITS 是 StopBit 的可能性。
EVENPARITY、NOPARITY 和 ODDPARITY 是最广泛使用的奇偶校验替代方案。 其他的也存在,但不太出名。
有关详细信息,请参阅 MSDN 库项目(搜索 DCB)。
在将 DCB 结构配置为优先级后,我们需要将这些设置应用于串行端口。 SetCommState 函数用于完成此操作。
设置超时
如果没有数据进入串行端口(例如,串行端口设备已关闭或断开连接),则从串行端口读取数据可能会导致应用程序在等待数据出现时停止。 有两种方法可以处理这种情况。
首先,可以在应用程序中使用多线程,一个线程处理串行端口困难,另一个线程处理实际处理。 这可能会变得麻烦和复杂,而且不是必需的。
另一种选择更简单:告诉 Windows 停止等待数据出现! 这是通过以下方式实现的:
COMMTIMEOUTS timeout={0};
timeout.ReadIntervalTimeout=60;
timeout.ReadTotalTimeoutConstant=60;
timeout.ReadTotalTimeoutMultiplier=15;
timeout.WriteTotalTimeoutConstant=60;
timeout.WriteTotalTimeoutMultiplier=8;
if(!SetCommTimeouts(h_Serial, &timeout)){
//handle error here
}
COMMTIMEOUTS 结构相当简单,只有上面列出的字段。 快速回顾一下:
- ReadIntervalTimeout - 指定超时之前接收字符之间必须经过的时间(以毫秒为单位)。
- ReadTotalTimeoutConstant - 提供返回之前等待的时间(以毫秒为单位)。
- ReadTotalTimeoutMultiplier - 指定响应读取操作中请求的每个字节之前等待的时间长度(以毫秒为单位)。
- WriteTotalTimeoutConstant 和 WriteTotalTimeoutMultiplier - 两者都完成与 ReadTotalTimeoutConstant 和 WriteTotalTimeoutMultiplier 相同的任务,但用于写入而不是读取。
将 ReadIntervalTimeout 设置为 MAXDWORD 并将 ReadTotalTimeoutConstant 和 ReadTotalTimeoutMultiplier 设置为 0 会导致任何读取操作立即返回缓冲区中的任何字符(即已经接收到的字符),即使不存在任何字符。
配置 COMMTIMEOUTS 结构后,我们需要使用 SetCommTimeouts 方法将更改应用到串行端口。
读/写数据
您可以在打开串行端口后使用必要的参数和超时开始读取数据。 这些都很容易理解。
考虑从串口读取n个字节的场景。 然后,我们只需按照以下步骤操作:
char sBuff[n + 1] = {0};
DWORD dwRead = 0;
if(!ReadFile(h_Serial, sBuff, n, &dwRead, NULL)){
//handle error here
}
文件(串行端口)的句柄、存储数据的缓冲区、要读取的字节数、对要设置为读取的字节数的整数的引用以及 NULL 都传递给 ReadFile。 ReadFile 操作读取的字节数将存储在 dwRead 中。
写入数据也是同样的过程。 唯一的区别是WriteFile函数可以写入数据。
关闭端口
使用完串口后,合上手柄。 如果不这样做,可能会发生奇怪的事情,包括在您重新启动之前没有其他人能够使用串行端口。
无论如何,这很容易完成,因此请记住以下几点:
CloseHandle(h_Serial);
处理错误 正如您可能已经看到的,我们在每个系统调用后插入了一条注释,说明您应该处理错误。
这始终是一种优秀的编程实践,但对于 I/O 函数尤其重要,因为 I/O 函数更容易失败。 无论如何,以下所有函数在失败时都返回 0,在成功时返回 0 以外的任何值。
要找出问题所在,请使用 GetLastError()
函数,该函数以 DWORD 形式返回错误代码。 您可以使用 FormatMessage 方法将其转换为有意义的字符串:
char lastErr[1020];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
lastErr,
1020,
NULL);
相关文章
C++ 中 DWORD 和 Unsigned Int 的区别
发布时间:2023/08/18 浏览次数:187 分类:C++
-
本文将介绍 DWORD 在 C++ 中的一般用法,它与 unsigned int 有着根本的不同,尽管它们目前具有相同的值。C++ 中 DWORD 和 unsigned int 的区别 根据定义,unsigned int 至少有 16 位长。 unsigned int 通常是特定
修复 C++ 中的分段错误
发布时间:2023/08/18 浏览次数:192 分类:C++
-
本文将讨论 C++ 中的分段错误并提供解决此问题的解决方案。C++ 中的分段错误当您的程序或系统尝试访问超出其范围的内存时,C++ 中就会出现分段错误。 这是导致程序在运行时崩溃的常见情况
在 C++ 中通过掷骰子生成随机值
发布时间:2023/04/09 浏览次数:172 分类:C++
-
本文解释了如何使用时间因子方法和模拟 C++ 中的掷骰子的任意数方法生成随机数。了解它是如何工作的以及它包含哪些缺点。提供了一个 C++ 程序来演示伪数生成器。
在 C++ 中使用模板的链表
发布时间:2023/04/09 浏览次数:162 分类:C++
-
本文解释了使用模板在 C++ 中创建链表所涉及的各个步骤。工作程序演示了一个链表,该链表使用模板来避免在创建新变量时声明数据类型的需要。
在 C++ 中添加定时延迟
发布时间:2023/04/09 浏览次数:235 分类:C++
-
本教程将为你提供有关在 C++ 程序中添加定时延迟的简要指南。这可以使用 C++ 库为我们提供的一些函数以多种方式完成。
在 C++ 中创建查找表
发布时间:2023/04/09 浏览次数:172 分类:C++
-
本文重点介绍如何创建查找表及其在不同场景中的用途。提供了三个代码示例以使理解更容易,并附有代码片段以详细了解代码。
如何在 C++ 中把字符串转换为小写
发布时间:2023/04/09 浏览次数:93 分类:C++
-
介绍了如何将 C++ std::string 转换为小写的方法。当我们在考虑 C++ 中的字符串转换方法时,首先要问自己的是我的输入字符串有什么样的编码