Windows使用串口API函数串口编程
Windows基于串口API函数的串口程序开发介绍欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导
Windows使用串口API函数串口编程
前言
本文介绍了Windows上用串口api进行软件开发的步骤,其大致分为4步:(1)打开串口 (2)配置串口(3)串口通讯(4)关闭串口。接下来,会用C++代码示例来详细介绍具体的开发流程。
1. 打开串口
调用CreateFile()函数来打开串口,函数执行成功返回串口句柄,出错返回INVALID_HANDLE_VALUE。
HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
1.1 参数详解
lpFileName表示要打开的串口逻辑名,如“COM1”;注意当串口号大于9时,字符串前需加上"\\.\"的前缀;
dwDesiredAccess指定类型的访问对象。如果为 GENERIC_READ 表示允许对设备进行读访问;如果为 GENERIC_WRITE 表示允许对设备进行写访问(可组合使用);如果为零,表示只允许获取与一个设备有关的信息 ;
dwShareMode打开串口时,该参数只能为0,表示独占方式;
lpSecurityAttributes设置为NULL即可;
dwCreationDisposition打开串口时必须设置为OPEN_EXISTING;
dwFlagsAndAttributes对于串口而言,唯一有意义的设置时FILE_FLAG_OVERLAPPED,创建时指定该设置,串口可执行异步操作;否则只能执行同步操作;
hTemplateFile串口操作时必须设置为NULL.
1.2 代码示例
1.2.1 获取串口号
若使用的是沁恒微的CH34X USB转串口芯片产品,可通过调用CH341PT.DLL库中的接口,识别出为其分配的串口和监测串口设备的插拔行为。
下面给出一段调用示例以供参考:
// 方法1 : 通过串口句柄来识别串口, 使用的库函数CH341PtHandleIsCH341()
...
for (j=1;j<21;j++)
{
// fullportname为完整的串口名字符串,格式为"\\\\.\\COMxx"
porthandle=CreateFile((CHAR *)fullportname, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL );
if ( porthandle != INVALID_HANDLE_VALUE )
{
if(CH341PtHandleIsCH341(porthandle))
// ... 识别到位CH34X串口
CloseHandle(porthandle);
}
}
...
// 方法2 : 通过串口名称来识别串口, 使用的库函数CH341PtNameIsCH341()
...
for (j=0;j<20;j++)
{
// fullportnamebuf数组中元素为完整的串口名字符串,从COM1开始枚举
if(CH341PtNameIsCH341(fullportnamebuf[j]))
{
// ... 识别到位CH34X串口
}
}
...
1.2.2 打开串口(同步通信)
HANDLE m_hCom;
m_hCom = CreateFile("COM1", //串口名,COM10及以上的串口名格式应为:"\\\\.\\COM10"
GENERIC_READ|GENERIC_WRITE, //允许读或写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
NULL, //同步方式
NULL );
1.2.3 打开串口(异步通信)
HANDLE m_hCom;
m_hCom = CreateFile("\\\\.\\COM11", //串口名,COM10及以上的串口名格式应为:"\\\\.\\COM10"
GENERIC_READ|GENERIC_WRITE, //允许读或写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
FILE_FLAG_OVERLAPPED, //异步方式
NULL );
2. 关闭串口
调用CloseHandle()函数来关闭串口,函数参数为串口句柄。
BOOL WINAPI CloseHandle(HANDLE hObject);
3. 配置串口
串口成功打开后,就可以开始对串口属性进行配置。
3.1 配置输入输出缓冲区
我们可以根据需求用SetupComm()来配置输入输出缓冲区:
BOOL WINAPI SetupComm(
__in HANDLE hFile, //串口句柄
__in DWORD dwInQueue, //输入缓冲区大小
__in DWORD dwOutQueue //输出缓冲区大小
);
其中缓冲区大小以字节为单位,也可以不调用该函数,Windows系统也会分配默认的发送和接收缓冲区
3.2 配置超时时间
超时有两种,一个叫间隔超时,另一个叫总超时。
读端口有间隔超时和总超时设置,写端口只有总超时设置。
要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。
COMMTIMEOUTS结构介绍:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; /* Maximum time between read chars. */
DWORD ReadTotalTimeoutMultiplier; /* Multiplier of characters. */
DWORD ReadTotalTimeoutConstant; /* Constant in milliseconds. */
DWORD WriteTotalTimeoutMultiplier; /* Multiplier of characters. */
DWORD WriteTotalTimeoutConstant; /* Constant in milliseconds. */
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
ReadIntervalTimeout为读操作时两个字符间的间隔超时;
ReadTotalTimeoutMultiplier为读操作在读取每个字符时的超时;
ReadTotalTimeoutConstant为读操作的固定超时;
WriteTotalTimeoutMultiplier为写操作在写每个字符时的超时;
WriteTotalTimeoutConstant为写操作的固定超时;
读操作的间隔超时 = ReadIntervalTimeout
读操作的总超时 = ReadTotalTimeoutConstant + ReadTotalTimeoutMultiplier*要读的字符数
写操作的总超时 = WriteTotalTimeoutConstant + WriteTotalTimeoutMultiplier*要写的字符数
需要注意的是,对于异步读写时,SetCommTimeouts()仍然是起作用的,在这种情况下,超时规定的是I/O操作的完成时间,而不是ReadFile和WriteFile的返回时间。
3.3 串口的属性配置
在配置串口波特率,数据位,奇偶校验,停止位等属性时,首先调用GetCommState获取一个DCB结构:
DCB dcb;
GetCommState(m_hCom, &dcb); // m_hCom是串口句柄
然后对dcb结构体中的成员进行赋值:
dcb.BaudRate = 115200; // 波特率
dcb.ByteSize = 8; // 8位数据位
dcb.StopBits = ONESTOPBIT; // 1位停止位
// | ONESTOPBIT | 1位停止位 |
// | ONE5STOPBITS | 1.5位停止位 |
// | TWOSTOPBITS | 2位停止位 |
dcb.Parity = NOPARITY; // 无校验
// | EVENPARITY | 偶校验 |
// | MARKPARITY | 标号校验 |
// | NOPARITY | 无校验 |
// | ODDPARITY | 奇校验 |
// | SPACEPARITY | 空格校验 |
其中:
BaudRate用于指定串口设备通信的数据传输速率。
ByteSize指定端口当前使用的数据位数。
Parity用于指定端口当前使用的奇偶校验方式。
StopBits用于指定串口当前使用的停止位数。
如想查看完整DCB结构说明,请点击https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb
4. 同步方式读写串口
4.1 函数介绍
在串口打开并配置好之后,可以开始读写串口,读写之前,需要清空串口缓冲区和清除错误,代码参考如下:
PurgeComm(m_hCom, PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_RXABORT); // 清空串口缓冲区,终止发送接收异步操作
DWORD dwError;
if (!ClearCommError(g_hCom, &dwError, NULL))
{
...
}
完成后,可以调用ReadFile和WriteFile来对串口进行读写操作,API原型如下:
BOOL ReadFile(
HANDLE hFile, // 串口句柄
LPVOID lpBuffer, // 读缓冲区
DWORD nNumberOfBytesToRead, // 要求读入的字节数
LPDWORD lpNumberOfBytesRead, // 实际读入的字节数
LPOVERLAPPED lpOverlapped // 重叠结构
);
BOOL WriteFile(
HANDLE hFile, //文件句柄
LPCVOID lpBuffer, //数据缓存区指针
DWORD nNumberOfBytesToWrite, //你要写的字节数
LPDWORD lpNumberOfBytesWritten, //用于保存实际写入字节数的存储区域的指针
LPOVERLAPPED lpOverlapped //OVERLAPPED结构体指针
) ;
4.2 代码示例
下面给出一个简单的同步通信的控制台程序示例:
// ComDemo.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <windows.h>
#include <iostream>
// 全局变量
HANDLE h_Com; // 串口句柄
// 打开串口
void ConnectCom(LPCTSTR ComName)
{
COMMTIMEOUTS TimeOuts; //串口设置超时结构体
DCB dcb;
//打开一个串口设备
h_Com = CreateFile(ComName, GENERIC_READ | GENERIC_WRITE, 0, NULL,OPEN_EXISTING, NULL, NULL);
if (h_Com == INVALID_HANDLE_VALUE)
{
std::cout << "串口无法打开" << std::endl;
return;
}
SetupComm(h_Com, 4096, 4096); //设置输入输出缓冲
// 超时设置
TimeOuts.ReadIntervalTimeout = 100;
TimeOuts.ReadTotalTimeoutMultiplier = 500;
TimeOuts.ReadTotalTimeoutConstant = 0;
TimeOuts.WriteTotalTimeoutMultiplier = 0;
TimeOuts.WriteTotalTimeoutConstant = 0;
SetCommTimeouts(h_Com, &TimeOuts);
// 设置串口属性
GetCommState(h_Com, &dcb); //串口属性配置
dcb.BaudRate = 115200;
dcb.ByteSize = 8;
dcb.StopBits = ONESTOPBIT;
dcb.Parity = NOPARITY;
if (!SetCommState(h_Com, &dcb))
{
CloseHandle(h_Com);
std::cout << "串口配置失败" << std::endl;
return ;
}
PurgeComm(h_Com, PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_RXABORT);//清空串口缓冲区
}
int main()
{
ConnectCom(L"COM7"); // 打开指定串口并进行配置
// 发送
DWORD dwError;
DWORD dwSend = 0;
const char* pSendBuf = "test send";
if (ClearCommError(h_Com, &dwError, NULL))
{
PurgeComm(h_Com, PURGE_TXABORT | PURGE_TXCLEAR);
}
if (!WriteFile(h_Com, pSendBuf, strlen(pSendBuf), &dwSend,NULL))
{
std::cout << "发送失败" << std::endl;
}
std::cout << "已发送: ";
std::cout << dwSend << "个字节" << std::endl;
// 数据接收
DWORD dwWantRead = 100;
DWORD dwRead = 0;
char* pReadBuf = new char[100];
memset(pReadBuf, 0, 100);
if (ClearCommError(h_Com, &dwError, NULL))
{
PurgeComm(h_Com, PURGE_RXABORT | PURGE_RXCLEAR);
}
if (!ReadFile(h_Com, pReadBuf, dwWantRead, &dwRead, NULL))
{
std::cout << "读取失败" << std::endl;
}
std::cout << "读取的内容: ";
std::cout << pReadBuf << std::endl;
delete[] pReadBuf;
}
5. 异步读写数据
异步(重叠)I/O操作指的是应用程序可以在后台读或是写数据,需要使用OVERLAPPED结构。
接下来,会重点介绍异步操作的具体步骤。
5.1 异步操作介绍
1.首先要使用OVERLAPPED结构,CreateFile()函数的dwFlagsAndAttributes参数必须设为FILE_FLAG_OVERLAPPED,调用读写串口时,也必须在参数中指定OVERLAPPED结构。
2.OVERLAPPED结构类型说明如下:
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
} DUMMYSTRUCTNAME;
PVOID Pointer;
} DUMMYUNIONNAME;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
其中,hEvent表示I/O操作完成后触发的事件(信号),其余完整参数说明请参考:https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-overlapped
3.设置了异步I/O操作后,I/O操作和函数返回有以下两种情况:
① 函数返回时I/O操作已完成
② 函数返回时I/O操作还未完成:此时一方面,函数返回值为FALSE,并且GetLastError函数返回ERROR_IO_PENDING;另一方面,系统把OVERLAPPED中的信号事件设为无信号状态。当I/O操作完成时,系统要把它设为信号状态。
4.异步I/O操作可以由GetOverLappedResult()函数来获取结果,也可以使用等待函数来查询事件当前状态或等待Windows状态信号。
关于GetOverLappedResult()详细说明,请参看以下链接:https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getoverlappedresult
5.2 代码示例
下面给出一个简单的异步通讯的控制台程序作为参考:
// ComDemo.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <windows.h>
#include <iostream>
// 全局变量
HANDLE h_Com; // 串口句柄
// 打开串口
void ConnectCom(LPCTSTR ComName)
{
COMMTIMEOUTS TimeOuts; //串口设置超时结构体
DCB dcb;
//打开一个串口设备
h_Com = CreateFile(ComName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); // 重叠方式
if (h_Com == INVALID_HANDLE_VALUE)
{
std::cout << "串口无法打开" << std::endl;
return;
}
SetupComm(h_Com, 4096, 4096); // 设置输入输出缓冲
// 超时设置
TimeOuts.ReadIntervalTimeout = 100;
TimeOuts.ReadTotalTimeoutMultiplier = 500;
TimeOuts.ReadTotalTimeoutConstant = 0;
TimeOuts.WriteTotalTimeoutMultiplier = 0;
TimeOuts.WriteTotalTimeoutConstant = 0;
SetCommTimeouts(h_Com, &TimeOuts);
// 设置串口属性
GetCommState(h_Com, &dcb); //串口属性配置
dcb.BaudRate = 115200;
dcb.ByteSize = 8;
dcb.StopBits = ONESTOPBIT;
dcb.Parity = NOPARITY;
if (!SetCommState(h_Com, &dcb))
{
CloseHandle(h_Com);
std::cout << "串口配置失败" << std::endl;
return ;
}
PurgeComm(h_Com, PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_RXABORT);//清空串口缓冲区
}
int main()
{
ConnectCom(L"COM7"); // 打开指定串口并进行配置
// 建立一个重叠结构
OVERLAPPED wrOverlapped;
ZeroMemory(&wrOverlapped, sizeof(wrOverlapped));
if (wrOverlapped.hEvent != NULL)
{
ResetEvent(wrOverlapped.hEvent);
wrOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
}
// 发送
DWORD dwError;
DWORD dwSend = 0;
const char* pSendBuf = "test send";
if (ClearCommError(h_Com, &dwError, NULL))
{
PurgeComm(h_Com, PURGE_TXABORT | PURGE_TXCLEAR);
}
if (!WriteFile(h_Com, pSendBuf, strlen(pSendBuf), &dwSend, &wrOverlapped))
{
if (GetLastError() == ERROR_IO_PENDING)
{
while (!GetOverlappedResult(h_Com, &wrOverlapped, &dwSend, FALSE))
{
if (GetLastError() == ERROR_IO_INCOMPLETE)
{
continue;
}
else
{
std::cout << "发送失败" << std::endl;
ClearCommError(h_Com, &dwError, NULL);
break;
}
}
}
}
std::cout << "已发送: ";
std::cout << dwSend << "个字节" << std::endl;
// 数据接收
DWORD dwWantRead = 100;
DWORD dwRead = 0;
char* pReadBuf = new char[100];
memset(pReadBuf, 0, 100);
if (ClearCommError(h_Com, &dwError, NULL))
{
PurgeComm(h_Com, PURGE_RXABORT | PURGE_RXCLEAR);
}
if (!ReadFile(h_Com, pReadBuf, dwWantRead, &dwRead, &wrOverlapped))
{
if (dwError = GetLastError() == ERROR_IO_PENDING)
{
while (!GetOverlappedResult(h_Com, &wrOverlapped, &dwRead, FALSE))
{
if (GetLastError() == ERROR_IO_INCOMPLETE)
{
continue;
}
else
{
std::cout << "接收失败" << std::endl;
ClearCommError(h_Com, &dwError, NULL);
return 0;
}
}
}
}
std::cout << "读取的内容: ";
std::cout << pReadBuf << std::endl;
delete[] pReadBuf;
}
6. 通信事件
Windows进程中监视发生在通信资源中的一组事件,这样应用程序可以不检查端口状态就可以知道某些条件何时发生。
Windows通信事件列表如下:
值 | 描述 |
---|---|
EV_BREAK | 检测到输入的终止 |
EV_CTS | CTS(清除发送)信号改变状态 |
EV_DSR | DSR(数据设置就绪)信号改变状态 |
EV_ERR | 发生了线路状态错误,线路状态错误时CE_FRAME(帧错误),CE_OVERRUN(接收缓冲区超限)和CE_RXPARITY(奇偶校验错误) |
EV_RING | 检测到振铃 |
EV_RLSD | RLSD(接收线路信号检测)信号改变状态 |
EV_RXCHAR | 接收到一个字符,并放入输入缓冲区 |
EV_RXFLAG | 接收到事件字符(DCB结构的EvtChar成员),并放入输入缓冲区 |
EV_TXEMPTY | 输出缓冲区中最后一个字符发送出去 |
1. 操作通信事件
应用程序可以通过SetCommMask()函数建立事件掩模来监视指定通信资源上的事件。SetCommMask()函数的声明如下:
BOOL SetCommMask(
HANDLE hFile,
DWORD dwEvtMask
);
其中 dwEvtMask标识被监视的通信事件,其值可以是上表中的任意通信事件组合。
如果想获取特定通信资源的当前事件掩模,可以使用GetCommMask()函数。其函数声明如下:
BOOL GetCommMask(
HANDLE hFile,
LPDWORD lpEvtMask
);
2. 监视通信事件
在用SetCommMask()指定了有用的事件后,应用程序就调用WaitCommEvent()函数来等待其中一个事件发生。其函数声明如下:
BOOL WaitCommEvent(
HANDLE hFile,
LPDWORD lpEvtMask,
LPOVERLAPPED lpOverlapped
);
其参数的详细说明,请参见:https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitcommevent
当lpOverlapped参数指向了一个OVERLAPPED结构,并且打开hfile参数标识的通信设备时指定了FILE_FLAG_OVERLAPPED标志,则WaitCommEvent()函数以异步操作实现。
这种情况下,OVERLAPPED结构体必须含有一个人工复位事件的句柄,当异步操作不能立即实现时,WaitCommEvent()返回false,且GetLastError()函数返回ERROR_IO_PENDING。此时,在该函数返回前,系统将OVERLAPPED结构中的hEvent参数设置为无信号状态;当指定事件发生后,再置为有信号状态。
操作通信事件的具体代码见下章节–API串口通讯示例。
7. API串口通讯代码示例
接下来我们提供了一个完整的API串口通讯的控制台程序示例。
https://download.csdn.net/download/WCH_TechGroup/12228991
也可在github上下载我们的示例程序。
https://github.com/WCHSoftGroup/SerialPortDemo-Win
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)