简单介绍和基本的几个函数解析:
传送门: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;
}