当前位置:   article > 正文

C++| 串口通讯_c++串口通信

c++串口通信

前言:最近在做上位机和下位机的串口通讯,上位机是用C++代码写的串口通讯,所以写了该文章。

串口通讯

串口:作为 CPU 和串行设备间的编码转换器。

串口通讯基本原理:

  • 异步通信,发送方和接收方之间没有统一的时钟信号。
  • 串行通讯,每次同时只能传输1个二进制位。
  • 数据转换,发送数据时候,字节数据经过串口,会转换为串行的位(二进制流);接受数据的时候,串行的位(二进制流)被转换为字节数据。
  • 通信传输线,串口通常会有三根线来完成通讯,分别是地线、发送线、接收线。
  • 通信参数一致,收发双方事先规定好通信参数,例如波特率、数据位、奇偶校验位、停止位等。
  • 数据帧,起始位(数据帧开始的信号)、数据位(实际发送的数据)、校验位(数据检错的方式)和停止位(数据帧结束的信号)。

串口通讯代码的特点:

  • 串口资源,应用程序使用串口进行通信之前,需要先向操作系统提出资源申请要求(打开串口),通信完成后必须释放资源(关闭串口)。
  • 通信参数,主要是串口通讯双方串口和波特率要一致,其余的还有数据位、奇偶校验位和停止位等。
  • 信息编码和解码方法,通信双方要约定好信息编码和解码方法,来确保信息的有效传递。

代码

串口通讯类

Serial.h

class CSerial
{
private:
	HANDLE hSerial;//串口Com的类
	bool openFlag;//串口是否打开
	DWORD errors;
public:
	CSerial();

	~CSerial();

	BOOL Open(int Port, int nBaud);// 设置和打开串口
	BOOL Close(void);// 关闭串口
	bool ReadData(char* buffer, unsigned int limit);//接收数据
	bool SendData(char* buffer, unsigned int length);//发送数据
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Serial.cpp

CSerial::CSerial() {
	openFlag = false;
	hSerial = NULL;
}


CSerial::~CSerial() {
	Close();
}

BOOL CSerial::Open(int Port, int nBaud) {
	// Port端口号,nBaud波特率
	if (openFlag) return true;

	// Port端口号
	char szPort[15];
	if(Port >9)
		wsprintf(szPort, "\\\\.\\COM%d", nPort);// 出现与 “LPCWSTR“ 类型的形参不兼容
	else 
		wsprintf(szPort, "COM%d", nPort);

	// 打开指定串口
	hSerial = CreateFileA(szPort,     /* 设备名,COM1,COM2等 */
		GENERIC_READ | GENERIC_WRITE, /* 访问模式,可同时读写 */
		0,                            /* 共享模式,0表示不共享 */
		NULL,                         /* 安全性设置,一般使用NULL */
		OPEN_EXISTING,                /* 该参数表示设备必须存在,否则创建失败 */
		FILE_ATTRIBUTE_NORMAL,		  /* 明确设置没有属性 */
		NULL);
	if (hSerial == NULL) return false;

	// 设置串口的超时时间,均设为0,表示不使用超时限制
	COMMTIMEOUTS  CommTimeouts;
	CommTimeouts.ReadIntervalTimeout = 0;
	CommTimeouts.ReadTotalTimeoutMultiplier = 0;
	CommTimeouts.ReadTotalTimeoutConstant = 0;
	CommTimeouts.WriteTotalTimeoutMultiplier = 0;
	CommTimeouts.WriteTotalTimeoutConstant = 0;
	SetCommTimeouts(hSerial, &CommTimeouts);

	// 串口初始化参数
	DCB dcbSerialParams = { 0 };
	dcbSerialParams.BaudRate = nBaud;// Ardiuno CBR_115200
	dcbSerialParams.ByteSize = 8;
	dcbSerialParams.StopBits = ONESTOPBIT;
	dcbSerialParams.Parity = NOPARITY;
	if (!SetCommState(hSerial, &dcbSerialParams))
	{
		errors = GetLastError();
		printf("ALERT: Could not set Serial Port parameters");
		CloseHandle(hSerial);
		return false;
	}

	openFlag = true;
	return openFlag;
}

BOOL CSerial::Close() {
	if (!openFlag || hSerial == NULL)return true;

	CloseHandle(hSerial);
	openFlag = false;
	hSerial = NULL;

	return true;
}

bool CSerial::ReadData(char* buffer, unsigned int limit) {
	if (!openFlag || hSerial == NULL) return false;

	// 从缓冲区读取limit大小的数据
	DWORD BytesRead = (DWORD)limit;
	if (!ReadFile(hSerial, buffer, BytesRead, &BytesRead, NULL)) {
		errors = GetLastError();
		PurgeComm(hSerial, PURGE_RXCLEAR | PURGE_RXABORT);// 清空串口缓冲区
		return false;
	}

	return (BytesRead == (DWORD)limit);
}

bool CSerial::SendData(char* buffer, unsigned int length) {
	if (!openFlag || hSerial == NULL) return false;

	// 向缓冲区写入指定大小length的数据 
	DWORD bytesSend;
	if (!WriteFile(hSerial, (void*)buffer, length, &bytesSend, NULL)) {
		errors = GetLastError();
		PurgeComm(hSerial, PURGE_TXCLEAR | PURGE_TXABORT);// 清空串口缓冲区
		return false;
	}
	
	return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

PurgeComm清除缓冲区函数

PurgeComm清除缓冲区函数:

  • PURGE_TXABORT:终止所有正在进行的字符输出操作
  • PURGE_RXABORT:终止所有正在进行的字符输入操作
  • PURGE_TXCLEAR:清除输出缓冲区,经常与PURGE_TXABORT 命令标志一起使用
  • PURGE_RXCLEAR:清除输入缓冲区,经常与PURGE_RXABORT 命令标志一起使用

端口号超过10

Window编写的时候,微软规定如果要访问这样COM号超过9的设备,应该在其前面添加“\\\\.\\”。

不能"COM10"
而是改成"\\\\.\\COM10"
  • 1
  • 2

其原因在于,微软预定义的标准设备只有“COM1”-“COM9”,对于COM10及以上的串口,只视之为一般意义上的文件,而非串行设备。

尤其是USB做串口通讯的时候,还有大量串行设备的时候,COM口序列号很容易超过10。

读取不定长数据

不定长数据没有固定长度,但是通常会规定好数据边界标志。

不定长数据不知道读取的数据是什么时候结束的,所以只要缓冲区有数据就读取。对所有读取到的数据进行数据边界的识别,从而来划分出不同的不定长数据。

读取串口缓冲区中所有数据的函数:

unsigned int CSerial::ReadAll(char* buffer) {
	// 读取后的数据存在buffer这里
	if (!open || hSerial == NULL) return false;

	DWORD dwErrorFlags; //错误标志
	COMSTAT comStat; //通讯状态
	OVERLAPPED m_osRead; //异步输入输出结构体

	memset(&m_osRead, 0, sizeof(m_osRead));
	m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, L"ReadEvent");

	ClearCommError(hSerial, &dwErrorFlags, &comStat); //清除通讯错误,获得设备当前状态

	DWORD BytesRead = (DWORD)comStat.cbInQue;// 获取缓冲区所有数据的长度
	//if (limit < (int)BytesRead)
		//BytesRead = (DWORD)limit;

	if (!ReadFile(hSerial, buffer, BytesRead, &BytesRead, NULL)) {
		if (GetLastError() == ERROR_IO_PENDING) {
			//如果串口正在读取中
			//GetOverlappedResult函数的最后一个参数设为TRUE
			//函数会一直等待,直到读操作完成或由于错误而返回
			GetOverlappedResult(hSerial, &m_osRead, &BytesRead, TRUE);
		}
		else {
			ClearCommError(hSerial, &dwErrorFlags, &comStat); //清除通讯错误
			CloseHandle(m_osRead.hEvent); //关闭并释放hEvent的内存
			return -1;
		}
	}

	return BytesRead;// 返回读取得到数据长度
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

串口测试

一般做串口都是为了连接下位机,但是很多时候下位机看不到效果的时候,很难知道通讯出了什么问题。可以在直接用下位机测试之前先用串口调试助手测试一遍。

串口调试助手可以直接下载一个,我是从Microsoft Apps中下载一个。
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/837005
推荐阅读
相关标签
  

闽ICP备14008679号