串口通讯-C++

简单介绍和基本的几个函数解析:

传送门:https://www.cnblogs.com/HPAHPA/p/7809445.html

串口通信是异步通信,因此,端口可以在一根线上发送数据的同时在另一根线上接收数据

串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。
(1)波特率:传输速率。如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。
(2)数据位:数据包中发送端想要发送的数据
(3)停止位:用于表示单个包的最后一位,结束标志以及校正时钟同步
(4)奇偶校验:检错方式。一共有四种检错方式:偶、奇、高和低。

串口操作总体流程::打开串口,配置串口,读写串口,关闭串口

打开串口:

(1)打开串口:使用CreateFile函数:
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).lpFileName:要打开或创建的文件名
    2).dwDesiredAccess:访问类型。0为设备查询访问;GENERIC_READ为读访问;GENERIC_WRITE为写访问;
    3).dwShareMode:共享方式。0表示文件不能共享,试图打开文件的操作都会失败;FILE_SHARE_READ表示允许其它读操作;FILE_SHARE_WRITE表示允许写操作;FILE_SHARE_DELETE表示允许删除操作。
    4).lpSecurityAttributes:安全属性。一个指向SECURITY_ATTRIBUTES结构的指针。一般传入NULL
    5).dwCreationDisposition:创建或打开文件时的动作。 OPEN_ALWAYS:打开文件,如果文件不存在则创建;TRUNCATE_EXISTING 打开文件,且将文件清空(故需要GENERIC_WRITE权限),若文件不存在则失败;OPEN_EXISTING打开文件,文件若不存在则会失败;CREATE_ALWAYS创建文件,如果文件已存在则清空;CREATE_NEW创建文件,如文件存在则会失败;
    6).dwFlagsAndAttributes:文件标志属性。FILE_ATTRIBUTE_NORMAL常规属性; FILE_FLAG_OVERLAPPED异步I/O标志,如果不指定此标志则默认为同步IO;FILE_ATTRIBUTE_READONLY文件为只读; FILE_ATTRIBUTE_HIDDEN文件为隐藏。FILE_FLAG_DELETE_ON_CLOSE所有文件句柄关闭后文件被删除
    7). hTemplateFile:一个文件的句柄,且该文件必须是以GENERIC_READ访问方式打开的。如果此参数不是NULL,则会使用hTemplateFile关联的文件的属性和标志来创建文件。如果是打开一个现有文件,则该参数被忽略。
注意点: 使用该函数打开串口时需要注意的是:文件名直接写串口号名,如“COM1”,;共享方式应为0,即串口应为独占方式;打开时的动作应为OPEN_EXISTING,即串口必须存在。

配置串口:

主要包含三部分内容:设置超时和设置缓冲区,设置串口配置信息

获取、设置超时:GetCommTimeouts  SetCommTimeouts  

超时设置的信息体:

typedef struct _COMMTIMEOUTS {
    DWORD ReadIntervalTimeout;          //读两个字符间的事件间隔,若两个字符之间的读操作间隔超过此间隔则立即返回
    DWORD ReadTotalTimeoutMultiplier;   //读操作在读取每个字符时的超时
    DWORD ReadTotalTimeoutConstant;     //读操作的固定超时
    DWORD WriteTotalTimeoutMultiplier;  //写操作在写每个字符时的超时
    DWORD WriteTotalTimeoutConstant;    //写操作的固定超时
    } COMMTIMEOUTS,*LPCOMMTIMEOUTS;

SetupComm()函数用来设置串口的发送/接受缓冲区的大小

GetCommState()和SetCommState()分别用来获得和设置串的配置信息,如波特率、校验方式、数据位个数、停止位个数等。一般也是先调用GetCommState()获得串口配置信息到一个DCB结构中去,再对这个结构自定义后调用SetCommState()进行设置。

读写串口:

主要包括三部分内容:清空缓冲, 清除错误,读写串口数据

清空缓冲:串口第一次使用或者串口长时间没用,再次使用时。读写串口之前,都需要进行清空缓冲
BOOL PurgeComm(HANDLE hFile,  DWORD dwFlags );

第二个参数dwFlags指定串口执行的动作,可以是以下值的组合:
  -PURGE_TXABORT:停止目前所有的传输工作立即返回不管是否完成传输动作。 
  -PURGE_RXABORT:停止目前所有的读取工作立即返回不管是否完成读取动作。 
  -PURGE_TXCLEAR:清除发送缓冲区的所有数据。 
  -PURGE_RXCLEAR:清除接收缓冲区的所有数据。
如清除串口的所有操作和缓冲:PurgeComm(hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);

清除错误:ClearCommError

读写串口:WriteFile()向串口中写数据,ReadFile()从串口读数据

关闭串口:

BOOL WINAPI CloseHandle(HANDLE hObject);

代码:

用法

1.m_SerialPort.open(comInfo);

2.BYTE bufSend[] = { 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x8C, 0x3A };
   m_SerialPort.write(bufSend, 8);

3.if (m_SerialPort.readAll(buf, len) < 0||len==0)
        {
            continue;
        }
        if (init)
        {
            memcpy(m_revBuf + m_nBufLen, buf, len);
            m_nBufLen += len;
        }
        else
        {
            memcpy(m_revBuf, buf+8, len-8);
            m_nBufLen += (len-8);
            init=true;
        }

//MySerialPort.h
#pragma once
#include "windows.h"
/*
* 内  容:自用串口类
* 功  能:EXPORT
*/
#include <tchar.h>
#define LOGE printf
#define LOGW printf
#define LOGD printf
//串口停止位
enum STOPBITS {
	COM_ONESTOPBITS = 0,
	COM_ONEHALFBITS,
	COM_TWOSTOPBITS,
};
//校验方式
enum PARITY {
	COM_NOPARITY=0,
	COM_ODDPARITY,
	COM_EVENPARITY,
	COM_MARKPARITY,
	COM_SPACEPARITY,
};
//流控制
enum FLOWCONTROL {
	COM_DTR_DSR=0,
	COM_RTS_CTS,
	COM_XON_XOFF,
	COM_NO_HANDSHAKE,
};
// 串口相关信息
#define COM_NAME_LEN	8
#define COM_FLAG_LEN	64
typedef struct  BY_COM_INFO {
	TCHAR portName[COM_NAME_LEN];		// = COM6
	int baudRate;	// = 9600 波特率	other 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
	int dataBits;		// = 8 数据位 other 7, 8
										//
	int stopBits;		// = 1, 停止位 0=1, 1=1.5, 2=2
	int parity;			// = n, 校验模式 0 =不校验,1=Odd, 2=Even, 3= Mark,4 = 空格
	int flowControl;	// = 3, 串口流控制模式 0= DTR/DSR, 1=RTS/CTS,2 = Xon/Xoff, 3= None
										
	int	readTimeout;	//读超时				// = 3000
	int writeTimeout;	//写超时				// = 3000
}ByCominfo;
//
typedef struct DATA_INFO {
	unsigned int lenOfData;//实际长度
	unsigned int  lenOfMem; //最大内存
	unsigned char data[1];
}DataInfo;
/*
@ 我的串口类
*/
class MySerialPort {
private:
	ByCominfo m_config;
	bool connected;
	HANDLE m_handle;
	BOOL processData(unsigned char *data, unsigned int dataLen, unsigned char *subData, unsigned int subDataLen);
public:
	MySerialPort();
	~MySerialPort();
	bool isConnected() { return connected; };

	int open(ByCominfo byCfg);//连接 打开串口

	int read(unsigned char *receive_buf, unsigned int recv_buf_len,
		unsigned char *stopReadFlag, int stopReadFlagLen);

	int readAll(unsigned char *buf, int &len);//有多少数据读多少数据

	int comRead(unsigned char *receive_buf, unsigned int recv_buf_len,int needReadLen);

	int write(unsigned char *data_buf, unsigned int data_len);//写数据

	void close();
};

//MySerialPort.cpp
#include "MySerialPort.h"
#include "stdio.h"
/*
@ constructor
*/
MySerialPort::MySerialPort() 
{
	this->m_config = {0};
	this->connected =false;
	this->m_handle = INVALID_HANDLE_VALUE;
}
/*
@ destructor
*/
MySerialPort::~MySerialPort() 
{
}
/*
@ 打开串口
*/
int MySerialPort::open(ByCominfo comCfg)
{
	//
	if (connected)
	{
		close();
	}
	//
	if (_tcsclen(comCfg.portName) == 0)
	{
		LOGE("comCfg.PortName is null pointer");
		return -1;
	}
	//复制入参,方便重连
	m_config = comCfg;
	//拼接获取连接名
	const int COM_MAX_SIZE = 4096;
	TCHAR comName[COM_MAX_SIZE];
	_tcscpy_s(comName, COM_MAX_SIZE, _T("\\\\.\\"));
	_tcscat_s(comName, COM_MAX_SIZE - 4, comCfg.portName);
	//串口操作,以串口连接名创建文件,对文件读写实现交互
	m_handle = CreateFile(comName,
		GENERIC_READ | GENERIC_WRITE, //访问类型 0为设备查询访问;GENERIC_READ为读访问;GENERIC_WRITE为写访问;
		0,                            //共享方式 0表示文件不能共享,试图打开文件的操作都会失败;FILE_SHARE_READ表示允许其它读操作;FILE_SHARE_WRITE表示允许写操作;FILE_SHARE_DELETE表示允许删除操作。
		NULL,                         //安全属性 一般传入NULL
		OPEN_EXISTING,                //指定要打开的文件已存在或不存在的动作  OPEN_ALWAYS:打开文件,如果文件不存在则创建;TRUNCATE_EXISTING 打开文件,且将文件清空(故需要GENERIC_WRITE权限),若文件不存在则失败;OPEN_EXISTING打开文件,文件若不存在则会失败;CREATE_ALWAYS创建文件,如果文件已存在则清空;CREATE_NEW创建文件,如文件存在则会失败;
		0,                            //文件属性和标志 FILE_ATTRIBUTE_NORMAL常规属性; FILE_FLAG_OVERLAPPED异步I/O标志,如果不指定此标志则默认为同步IO;FILE_ATTRIBUTE_READONLY文件为只读; FILE_ATTRIBUTE_HIDDEN文件为隐藏。FILE_FLAG_DELETE_ON_CLOSE所有文件句柄关闭后文件被删除
		NULL                          //一个指向模板文件的句柄
		);
//注意点: 使用该函数打开串口时需要注意的是:文件名直接写串口号名,如“COM1”,;共享方式应为0,即串口应为独占方式;打开时的动作应为OPEN_EXISTING,即串口必须存在。
	if (INVALID_HANDLE_VALUE == m_handle) {
		LOGE("INVALID_HANDLE_VALUE == m_handle");
		return -2;
	}
	//
	// 配置串口  
	SetupComm(m_handle, COM_MAX_SIZE, COM_MAX_SIZE);//设置缓存 
	// 超时设置(first)
	COMMTIMEOUTS timeouts;
	if (!GetCommTimeouts(m_handle, &timeouts)) {
		LOGE("GetCommTimeouts失败,GetLastError = %d", GetLastError());
		return -5;
	}
	timeouts.ReadIntervalTimeout = 20;		// 设置读间隔超时为最大值
	timeouts.ReadTotalTimeoutMultiplier = 0;		// 读超时时间系数
	timeouts.ReadTotalTimeoutConstant = comCfg.readTimeout;		// 读超时时间常量
	timeouts.WriteTotalTimeoutMultiplier = 0;		// 写超时时间系数
	timeouts.WriteTotalTimeoutConstant = comCfg.writeTimeout;		// 写超时时间常量
	if (!SetCommTimeouts(m_handle, &timeouts)) {
		LOGE("SetCommTimeouts失败,GetLastError = %d", GetLastError());
		return -6;
	}
	//DCB结构
	DCB dcb;
	// 设置端口的参数
	if (!GetCommState(m_handle, &dcb)) {//读取串口设置
		LOGE("GetCommState失败");
		return -3;
	}
	//
	dcb.DCBlength = sizeof(DCB);
	//
	dcb.BaudRate = comCfg.baudRate;	//波特率
	dcb.ByteSize = comCfg.dataBits;	//数据位
	//
	dcb.StopBits = comCfg.stopBits;	//停止位
	dcb.Parity = comCfg.parity;		//
	if (comCfg.parity != NOPARITY) {

		dcb.fParity = TRUE;
	}
	//
	if (comCfg.flowControl == COM_DTR_DSR) {
		//DTR/DSR
		dcb.fDtrControl = DTR_CONTROL_ENABLE;
		dcb.fRtsControl = RTS_CONTROL_DISABLE;
		dcb.fOutxCtsFlow = FALSE;
		dcb.fOutxDsrFlow = TRUE;
		dcb.fDsrSensitivity = FALSE;
	}
	else if (comCfg.flowControl == COM_RTS_CTS) {
		//RTS/CTS
		dcb.fDtrControl = DTR_CONTROL_DISABLE;
		dcb.fRtsControl = RTS_CONTROL_ENABLE;
		dcb.fOutxCtsFlow = TRUE;
		dcb.fOutxDsrFlow = FALSE;
		dcb.fDsrSensitivity = FALSE;
	}
	else if (comCfg.flowControl == COM_XON_XOFF) {
		// XON/XOFF
		dcb.fDtrControl = DTR_CONTROL_DISABLE;
		dcb.fRtsControl = RTS_CONTROL_DISABLE;
		dcb.fOutxCtsFlow = FALSE;
		dcb.fOutxDsrFlow = FALSE;
		dcb.fOutX = TRUE;
		dcb.fInX = TRUE;
		dcb.XonChar = 0x11;
		dcb.XoffChar = 0x13;
		dcb.fDsrSensitivity = FALSE;
		dcb.fTXContinueOnXoff = FALSE;
	}
	else if (comCfg.flowControl == COM_NO_HANDSHAKE) {
		//NO
		dcb.fDtrControl = DTR_CONTROL_DISABLE;
		dcb.fRtsControl = RTS_CONTROL_DISABLE;
		dcb.fOutxCtsFlow = FALSE;
		dcb.fOutxDsrFlow = FALSE;
		dcb.fOutX = FALSE;
		dcb.fInX = FALSE;
	}
	//
	if (!SetCommState(m_handle, &dcb)) {	//设置COM口设备控制块
		LOGE("SetCommState失败,GetLastError = %d", GetLastError());
		return -4;
	}
	//清空缓存 
	PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
	//
	connected = true;
	return 0;
}
/*
@ 读串口
@ 返回正确接收的数据长度,如果<=0 都是错误!
*/
int MySerialPort::read(unsigned char *receive_buf, unsigned int recv_buf_len,unsigned char *stopReadFlag, int stopReadFlagLen)
{
	//
	if (m_handle == INVALID_HANDLE_VALUE) {
		LOGE("handle_port is NULL.");
		return -1;
	}
	//
	if ((NULL == receive_buf) || (recv_buf_len <= 0) || (NULL == stopReadFlag) || (stopReadFlagLen <= 0)) {
		LOGE("Input parameter is invalid.");
		return -2;
	}
	//
	DWORD	dwErrorMask = 0;
	COMSTAT cs;
	//查询超时时间
	DWORD	dwBegin = GetTickCount();
	DWORD	dwEnd = dwBegin + m_config.readTimeout;
	DWORD	hasReadLength = 0, curNeedReadLength, curRealReadLength;
	int tt = GetTickCount();
	int read_success_flag =FALSE;
	//
	while (dwBegin <= dwEnd) {
		if (FALSE == ClearCommError(m_handle, &dwErrorMask, &cs)) {
			LOGE("ClearCommError failed");
			return -3;
		}
		//
		if (cs.cbInQue > 0) {
			if (hasReadLength + cs.cbInQue > recv_buf_len) {
				curNeedReadLength = recv_buf_len - hasReadLength;
			}
			else {
				curNeedReadLength = cs.cbInQue;
			}
			//
			if (!ReadFile(m_handle, receive_buf + hasReadLength, curNeedReadLength, &curRealReadLength, NULL)) {
				LOGE("ReadFile failed");
				return -3;
			}
			//
			hasReadLength += curRealReadLength;
			//
			if (processData(receive_buf, hasReadLength, stopReadFlag, stopReadFlagLen)) {
				read_success_flag = TRUE;
				break;
			}
			//
			if (hasReadLength >= recv_buf_len) {
				break;
			}
			//
			dwEnd = GetTickCount() + m_config.readTimeout;
			continue;
		}
		//
		Sleep(10);
		dwBegin = GetTickCount();
	}
	//
	LOGD("Read cost time:%d.", GetTickCount() - tt);
	//
	//PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
	if (read_success_flag == FALSE) {
		LOGW("未读取到结束标识符.");
		return -4;
	}
	//
	return hasReadLength;
}
/*
@ 有多少读多少
@ 返回值 0 为正常,<0 为异常
*/
int MySerialPort::readAll(unsigned char *buf,int &len)
{
	DWORD ReadSize = 0;
	BOOL rtn = FALSE;

	//设置读取1个字节数据,当缓存中有数据到达时则会立即返回,否则直到超时
	rtn = ReadFile(m_handle, buf, 1000, &ReadSize, NULL);

	//如果是超时rtn=true但是ReadSize=0,如果有数据到达,会读取一个字节ReadSize=1
	if ((rtn == TRUE) && (ReadSize>0))
	{
		len = ReadSize;
	}
	//
	//PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
	//
	return 0;
}
/*
@
*/
int MySerialPort::comRead(unsigned char *receive_buf, unsigned int recv_buf_len, int needReadLen)
{
	//
	int hasReadLength = 0;
	int read_success_flag = FALSE;
	//
	if (m_handle == INVALID_HANDLE_VALUE) {
		LOGE("handle_port is NULL.");
		return -1;
	}
	//
	DWORD ReadSize = 0;
	BOOL rtn = FALSE;

	//设置读取1个字节数据,当缓存中有数据到达时则会立即返回,否则直到超时
	rtn = ReadFile(m_handle, receive_buf, 1, &ReadSize, NULL);
	++hasReadLength;
	//如果是超时rtn=true但是ReadSize=0,如果有数据到达,会读取一个字节ReadSize=1
	if (rtn == TRUE && 1 == ReadSize)
	{
		if (receive_buf[0] != 0x1b)
		{
			LOGE("receive_buf[0] !=0x1b");
			PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
			return -1;
		}
		DWORD Error;
		COMSTAT cs = { 0 };
		int ReadLen = 0;
		while (hasReadLength < needReadLen)
		{
			//查询剩余多少字节未读取,存储于cs.cbInQue中
			ClearCommError(m_handle, &Error, &cs);
			ReadLen = (cs.cbInQue > (needReadLen-hasReadLength)) ? (needReadLen - hasReadLength) : cs.cbInQue;
			if (ReadLen > 0)
			{
				//由于之前等待时以读取一个字节,所欲buf+1
				rtn = ReadFile(m_handle, receive_buf + hasReadLength, ReadLen, &ReadSize, NULL);
				if (rtn)
				{
					hasReadLength = ReadLen + 1;
				}
			}
		}	
	}
	//
	if (hasReadLength !=3)
	{
		LOGW("未读取到结束标识符.");
		return -4;
	}
	//
	PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
	return hasReadLength;
}
/*
@ 写串口
@ 返回正确发出的数据长度,如果<=0 都是错误!
*/
int MySerialPort::write(unsigned char *data_buf, unsigned int data_len) {
	//
	if (m_handle == INVALID_HANDLE_VALUE) {
		LOGE("handle_port is NULL.");
		return -1;
	}
	//
	if ((data_len <=0)  || (NULL == data_buf) ) {
		LOGE("Input parameter is invalid.");
		return -2;
	}
	//清空缓存
	//PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
	//
	const int BytesOnce = 1024;
	DWORD dwBytesWritten = 0;
	int nCount = data_len / BytesOnce;
	int remain = data_len % BytesOnce;
	int real_write_len = 0;
	//
	for (int i = 0; i < nCount; i++) {
		WriteFile(m_handle, data_buf + i * BytesOnce, BytesOnce, (LPDWORD)&real_write_len, 0);
		dwBytesWritten += real_write_len;
		if (real_write_len != BytesOnce) {
			real_write_len = dwBytesWritten;
			LOGE("write failed.real_write_len(%d) != BytesOnce(%d).", real_write_len, BytesOnce);
			return -3;
			break;
		}
	}
	//
	if (remain != 0) {
		WriteFile(m_handle, data_buf + nCount * BytesOnce, remain, (LPDWORD)&real_write_len, 0);

		dwBytesWritten += real_write_len;

		if (real_write_len != remain) {
			real_write_len = dwBytesWritten;
			LOGE("write failed.real_write_len(%d) != remain(%d).", real_write_len, remain);
			return -3;
		}
	}
	//
	return dwBytesWritten;
}
/*
@ 关闭窗口
*/
void MySerialPort::close() {
	if (INVALID_HANDLE_VALUE != m_handle) {
		CloseHandle(m_handle);
		m_handle = INVALID_HANDLE_VALUE;
	}
	connected = false;
}
//
BOOL MySerialPort::processData(unsigned char *data, unsigned int dataLen, unsigned char *subData, unsigned int subDataLen) {
	unsigned char *tmpData = data, *tmpSubData = subData;
	//
	if (data == NULL || subData== NULL || subDataLen<= 0 || dataLen <= 0|| dataLen < subDataLen) {
		return FALSE;
	}
	//
	for (tmpData = data; tmpData < data + dataLen; tmpData++) {
		unsigned char * pSrc = tmpData;
		for (tmpSubData = subData; tmpSubData < subData + subDataLen; tmpSubData++, pSrc++) {
			if (*pSrc != *tmpSubData) {
				break;
			}
		}
		//
		if (tmpSubData == subData + subDataLen) {
			return TRUE;
		}
	}
	//
	return FALSE;
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值