前言:最近在做上位机和下位机的串口通讯,上位机是用C++代码写的串口通讯,所以写了该文章。
串口通讯
- 串口通讯
- 代码
- 串口通讯类
- PurgeComm清除缓冲区函数
- 读取不定长数据
- 串口测试
串口通讯
串口:作为 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);//发送数据
};
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];sprintf_s(szPort, "COM%d", Port);// 打开指定串口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_115200dcbSerialParams.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;
}
PurgeComm清除缓冲区函数
PurgeComm清除缓冲区函数:
- PURGE_TXABORT:终止所有正在进行的字符输出操作
- PURGE_RXABORT:终止所有正在进行的字符输入操作
- PURGE_TXCLEAR:清除输出缓冲区,经常与PURGE_TXABORT 命令标志一起使用
- PURGE_RXCLEAR:清除输入缓冲区,经常与PURGE_RXABORT 命令标志一起使用
读取不定长数据
不定长数据没有固定长度,但是通常会规定好数据边界标志。
不定长数据不知道读取的数据是什么时候结束的,所以只要缓冲区有数据就读取。对所有读取到的数据进行数据边界的识别,从而来划分出不同的不定长数据。
读取串口缓冲区中所有数据的函数:
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;// 返回读取得到数据长度
}
串口测试
可以使用串口通讯助手或者下位机进行测试。