windows c/c++串口编程

简介

Windows的C++串口库源码,为UTF-8带BOM格式,建议用该编码格式打开。
本文提供串口同步和异步读写的示例。

代码示例

WzSerialPort.h

#ifndef _WZSERIALPORT_H
#define _WZSERIALPORT_H


class WzSerialPort
{
public:
	WzSerialPort();
	~WzSerialPort();

	// 打开串口,成功返回true,失败返回false
	// portname(串口名): 在Windows下是"COM1""COM2"等,在Linux下是"/dev/ttyS1"等
	// baudrate(波特率): 9600、19200、38400、43000、56000、57600、115200 
	// parity(校验位): 0为无校验,1为奇校验,2为偶校验,3为标记校验(仅适用于windows)
	// databit(数据位): 4-8(windows),5-8(linux),通常为8位
	// stopbit(停止位): 1为1位停止位,2为2位停止位,3为1.5位停止位
	// synchronizeflag(同步、异步,仅适用与windows): 0为异步,1为同步
	bool open(const char* portname, int baudrate, char parity, char databit, char stopbit, char synchronizeflag = 1);

	//关闭串口,参数待定
	void close();

	//发送数据或写数据,成功返回发送数据长度,失败返回0
	int send(const void *buf, int len);

	//接受数据或读数据,成功返回读取实际数据的长度,失败返回0
	int receive(void *buf, int maxlen);

private:
	int pHandle[16];
	char synchronizeflag;
};

#endif

WzSerialPort.cpp

#include "WzSerialPort.h"

#include <stdio.h>
#include <string.h>

#include <WinSock2.h>
#include <windows.h>

WzSerialPort::WzSerialPort()
{

}

WzSerialPort::~WzSerialPort()
{

}

bool WzSerialPort::open(const char* portname,
	int baudrate,
	char parity,
	char databit,
	char stopbit,
	char synchronizeflag)
{
	this->synchronizeflag = synchronizeflag;
	HANDLE hCom = NULL;
	if (this->synchronizeflag)
	{
		//同步方式
		hCom = CreateFileA(portname, //串口名
			GENERIC_READ | GENERIC_WRITE, //支持读写
			0, //独占方式,串口不支持共享
			NULL,//安全属性指针,默认值为NULL
			OPEN_EXISTING, //打开现有的串口文件
			0, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式
			NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL
	}
	else
	{
		//异步方式
		hCom = CreateFileA(portname, //串口名
			GENERIC_READ | GENERIC_WRITE, //支持读写
			0, //独占方式,串口不支持共享
			NULL,//安全属性指针,默认值为NULL
			OPEN_EXISTING, //打开现有的串口文件
			FILE_FLAG_OVERLAPPED, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式
			NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL
	}

	if (hCom == (HANDLE)-1)
	{
		return false;
	}

	//配置缓冲区大小 
	if (!SetupComm(hCom, 1024, 1024))
	{
		return false;
	}

	// 配置参数 
	DCB p;
	memset(&p, 0, sizeof(p));
	p.DCBlength = sizeof(p);
	p.BaudRate = baudrate; // 波特率
	p.ByteSize = databit; // 数据位

	switch (parity) //校验位
	{
	case 0:
		p.Parity = NOPARITY; //无校验
		break;
	case 1:
		p.Parity = ODDPARITY; //奇校验
		break;
	case 2:
		p.Parity = EVENPARITY; //偶校验
		break;
	case 3:
		p.Parity = MARKPARITY; //标记校验
		break;
	}

	switch (stopbit) //停止位
	{
	case 1:
		p.StopBits = ONESTOPBIT; //1位停止位
		break;
	case 2:
		p.StopBits = TWOSTOPBITS; //2位停止位
		break;
	case 3:
		p.StopBits = ONE5STOPBITS; //1.5位停止位
		break;
	}

	if (!SetCommState(hCom, &p))
	{
		// 设置参数失败
		return false;
	}

	//超时处理,单位:毫秒
	//总超时=时间系数×读或写的字符数+时间常量
	COMMTIMEOUTS TimeOuts;
	TimeOuts.ReadIntervalTimeout = 1000; //读间隔超时
	TimeOuts.ReadTotalTimeoutMultiplier = 500; //读时间系数
	TimeOuts.ReadTotalTimeoutConstant = 5000; //读时间常量
	TimeOuts.WriteTotalTimeoutMultiplier = 500; // 写时间系数
	TimeOuts.WriteTotalTimeoutConstant = 2000; //写时间常量
	SetCommTimeouts(hCom, &TimeOuts);

	PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);//清空串口缓冲区

	memcpy(pHandle, &hCom, sizeof(hCom));// 保存句柄

	return true;
}

void WzSerialPort::close()
{
	HANDLE hCom = *(HANDLE*)pHandle;
	CloseHandle(hCom);
}

int WzSerialPort::send(const void *buf, int len)
{
	HANDLE hCom = *(HANDLE*)pHandle;

	if (this->synchronizeflag)
	{
		// 同步方式
		DWORD dwBytesWrite = len; //成功写入的数据字节数
		BOOL bWriteStat = WriteFile(hCom, //串口句柄
			buf, //数据首地址
			dwBytesWrite, //要发送的数据字节数
			&dwBytesWrite, //DWORD*,用来接收返回成功发送的数据字节数
			NULL); //NULL为同步发送,OVERLAPPED*为异步发送
		if (!bWriteStat)
		{
			return 0;
		}
		return dwBytesWrite;
	}
	else
	{
		//异步方式
		DWORD dwBytesWrite = len; //成功写入的数据字节数
		DWORD dwErrorFlags; //错误标志
		COMSTAT comStat; //通讯状态
		OVERLAPPED m_osWrite; //异步输入输出结构体

		//创建一个用于OVERLAPPED的事件处理,不会真正用到,但系统要求这么做
		memset(&m_osWrite, 0, sizeof(m_osWrite));
		m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, L"WriteEvent");

		ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误,获得设备当前状态
		BOOL bWriteStat = WriteFile(hCom, //串口句柄
			buf, //数据首地址
			dwBytesWrite, //要发送的数据字节数
			&dwBytesWrite, //DWORD*,用来接收返回成功发送的数据字节数
			&m_osWrite); //NULL为同步发送,OVERLAPPED*为异步发送
		if (!bWriteStat)
		{
			if (GetLastError() == ERROR_IO_PENDING) //如果串口正在写入
			{
				WaitForSingleObject(m_osWrite.hEvent, 1000); //等待写入事件1秒钟
			}
			else
			{
				ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误
				CloseHandle(m_osWrite.hEvent); //关闭并释放hEvent内存
				return 0;
			}
		}
		return dwBytesWrite;
	}
}

int WzSerialPort::receive(void *buf, int maxlen)
{
	HANDLE hCom = *(HANDLE*)pHandle;

	if (this->synchronizeflag)
	{
		//同步方式
		DWORD wCount = maxlen; //成功读取的数据字节数
		BOOL bReadStat = ReadFile(hCom, //串口句柄
			buf, //数据首地址
			wCount, //要读取的数据最大字节数
			&wCount, //DWORD*,用来接收返回成功读取的数据字节数
			NULL); //NULL为同步发送,OVERLAPPED*为异步发送
		if (!bReadStat)
		{
			return 0;
		}
		return wCount;
	}
	else
	{
		//异步方式
		DWORD wCount = maxlen; //成功读取的数据字节数
		DWORD dwErrorFlags; //错误标志
		COMSTAT comStat; //通讯状态
		OVERLAPPED m_osRead; //异步输入输出结构体

		//创建一个用于OVERLAPPED的事件处理,不会真正用到,但系统要求这么做
		memset(&m_osRead, 0, sizeof(m_osRead));
		m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, L"ReadEvent");

		ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误,获得设备当前状态
		if (!comStat.cbInQue)return 0; //如果输入缓冲区字节数为0,则返回false

		BOOL bReadStat = ReadFile(hCom, //串口句柄
			buf, //数据首地址
			wCount, //要读取的数据最大字节数
			&wCount, //DWORD*,用来接收返回成功读取的数据字节数
			&m_osRead); //NULL为同步发送,OVERLAPPED*为异步发送
		if (!bReadStat)
		{
			if (GetLastError() == ERROR_IO_PENDING) //如果串口正在读取中
			{
				//GetOverlappedResult函数的最后一个参数设为TRUE
				//函数会一直等待,直到读操作完成或由于错误而返回
				GetOverlappedResult(hCom, &m_osRead, &wCount, TRUE);
			}
			else
			{
				ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误
				CloseHandle(m_osRead.hEvent); //关闭并释放hEvent的内存
				return 0;
			}
		}
		return wCount;
	}
}

main.cpp

#include <iostream>
#include "SerialPort/WzSerialPort.h"

using namespace std;

void sendDemo()
{
	WzSerialPort w;
	if (w.open("COM1", 9600, 0, 8, 1))
	{
		for (int i = 0; i < 10; i++)
		{
			w.send("helloworld", 10);
		}
		cout << "send demo finished...";
	}
	else
	{
		cout << "open serial port failed...";
	}
}

void receiveDemo()
{
	WzSerialPort w;
	if (w.open("COM1", 9600, 0, 8, 1))
	{
		char buf[1024];
		int cnt;
		while (true)
		{
			memset(buf, 0, 1024);
			cnt = w.receive(buf, 1024);
			buf[cnt] = '\0';
			std::cout << buf << std::flush;
		}
	}
}

int main(int argumentCount, const char* argumentValues[])
{
	// 假设COM1已经和另外一个串口连接好了

	// 发送 demo
	//sendDemo();

	// 接收 demo
	receiveDemo();

	getchar();
	return 0;
}

发送结果如下

在这里插入图片描述

接收结果如下

在这里插入图片描述

Windows上常见问题

  • 在Windows上,要打开COM10以后的串口(包括COM10),串口名称不再是“COM10”,而是“\.\COM10”,因此在open函数中,如果是COM10及以后的串口,串口名(假设当前要打开COM10)应该写“\\.\COM10”,其中“\\.\”为“\.\”的转义。
  • 在visual studio的项目中,编译报错,错误指向WzSerialPort.cpp的157行、211行的问题:

157| m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE,
L"WriteEvent");

211| m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE,
L"ReadEvent");

导致这个编译报错的其实是VS的字符集设置所导致的。①将字符集改为“使用Unicode字符集”即可,②或者将 L"WriteEvent" 和 L"ReadEvent" 前面的 L 删除掉也可以。 总而言之吧,这是微软的要求,字符集设置为 “使用Unicode字符集” 时需要加在字符串前加 L 先将 char* 转为 wchar_t* 再转为 LPCWSTR,字符集设置为 “使用多字节字符集” 时,则无需将 char* 转为 wchar_t* ,可直接转为LPCWSTR,因此无需加 L

附录–使用windows线程API接收串口数据

DWORD WINAPI ThreadRead(LPVOID lpParameter)
{
	WzSerialPort w;
	if (w.open("COM1", 9600, 0, 8, 1))
	{
		char buf[1024];
		int cnt;
		while (true)
		{
			memset(buf, 0, 1024);
			cnt = w.receive(buf, 1024);
			buf[cnt] = '\0';
			std::cout << buf << std::flush;
		}
	}
}

int main(int argumentCount, const char* argumentValues[])
{
	// 假设COM1已经和另外一个串口连接好了

	// 发送 demo
	//sendDemo();

	// 接收 demo
	//receiveDemo();

	// 接收 demo2 ,线程接收
	HANDLE HRead;
	HRead = CreateThread(NULL, 0, ThreadRead, NULL, 0, NULL);
	while (1) { ; }
	CloseHandle(HRead);


	getchar();
	return 0;
}

效果如下
在这里插入图片描述

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: C/C++控制台串口通讯编程是一种通过使用C/C++语言来实现与串口设备进行通信的编程技术。 在控制台串口通讯编程中,我们可以使用C/C++语言提供的相关库函数来访问和操作串口设备。其中,包括了打开串口、设置串口属性、读写串口数据等功能。 首先,我们需要使用C/C++的库函数来打开串口设备。通过指定串口的名称和访问权限,我们可以从操作系统中获得对串口设备的访问句柄。然后,我们可以使用该句柄来设置串口的属性,如波特率、数据位数、停止位等。设置好属性后,我们就可以通过该串口进行数据的收发。 在串口通讯中,数据的读写是通过调用读写函数来实现的。通常,我们可以使用读函数来从串口中读取数据,并将其存储到缓冲区中。而写函数则用于将数据从缓冲区中写入到串口中。通过读写函数的调用,我们可以实现与外部设备之间的数据交互。 此外,对于串口通讯编程,还需要注意处理错误和异常情况。在进行串口操作时,可能会出现一些错误,如无法打开串口、读写超时等。为了确保程序的可靠性,我们需要在代码中添加适当的错误处理机制,以便及时发现和处理异常情况。 总而言之,C/C++控制台串口通讯编程是一种实现与串口设备进行通信的编程技术。通过使用相关的库函数,我们可以打开串口、设置属性、读写数据,并处理可能出现的错误情况,以实现与外部设备的数据交互。 ### 回答2: C/C++ 控制台串口通讯编程是一种通过串口与外部设备进行数据通讯的编程技术。在控制台应用程序中,通过使用C/C++ 编程语言来实现与串口通讯的功能。 首先,需要了解操作系统提供的相关串口通讯 API 接口。在Windows系统中,可以使用Windows API函数来访问串口,如CreateFile()、ReadFile() 和 WriteFile() 等函数。在Linux系统中,可以通过打开文件描述符的方式来访问串口设备文件,并使用read() 和 write() 函数进行数据的读写。 其次,需要设置串口的参数,包括波特率、数据位、停止位、校验位等。这些参数需要根据外部设备的通讯规范进行设置,以确保正确的数据传输。 然后,可以通过编写串口数据发送和接收的函数来实现数据的收发。发送数据时,可以使用WriteFile() 或 write() 函数将数据写入串口缓冲区,并等待数据的发送完成。接收数据时,可以使用ReadFile() 或 read() 函数从串口缓冲区中读取数据。 此外,需要注意在编程过程中处理异常情况。例如,当串口无法打开、写入数据超时或读取数据错误时,应设置相应的错误处理机制,例如打印错误信息或重新尝试等。 最后,可以在控制台应用程序中实现用户交互界面,通过命令行参数或菜单选项来控制串口通讯的功能,例如设置参数、发送数据、接收数据等。 总之,C/C++ 控制台串口通讯编程需要理解串口通讯的原理和外部设备的通讯规范,熟悉操作系统提供的串口访问函数,并编写相应的发送和接收函数来实现数据的传输。 ### 回答3: C/C++控制台串口通讯编程是指使用C/C++编程语言,在控制台环境中通过串口与外部设备进行通讯的编程过程。 串口通讯是一种常见的硬件通讯接口,用于计算机与外围设备的数据传输。C/C++是一种常见的程序设计语言,提供了丰富的库函数和语法特性,可以方便地进行串口通讯编程。 在进行C/C++控制台串口通讯编程时,首先需要引入相关的库文件,如Windows.h或者Linux的unistd.h header文件,这些文件包含了一些API函数,用于读取和写入串口数据。然后,通过打开相应的串口端口,设置通讯参数(如波特率、数据位、停止位等),可以使用相关API函数进行数据的发送和接收。 例如,通过使用C/C++的读取或写入文件的API函数来读取或写入串口数据,可以实现串口的数据发送和接收操作。可以通过`CreateFile()`函数来打开串口设备,通过`ReadFile()`和`WriteFile()`函数来读取和写入数据。 在进行C/C++串口通讯编程时,需要注意一些细节,比如在读取数据时需要保证数据的完整性,可以使用缓冲区来存储接收到的数据。另外,还需考虑相关的错误处理和异常情况,以确保程序的可靠性。 总之,C/C++控制台串口通讯编程是一种利用C/C++编程语言,在控制台环境下通过串口与外部设备进行数据传输的编程过程。通过合理使用相关的API函数和语言特性,可以实现串口通讯的功能,满足不同场景对数据传输的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路过的小熊~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值