最近在学串口通信编程,学习了三天终于靠自己写了一份串口通信编程,在这里记录写下的代码供自己复盘,也欢迎各位朋友交流指正。
思路:
主要利用Windows的CreatFile()函数实现对串口的打开、关闭
ReadFile()读取串口传入的数据(这里可以选择每次读取的字节数,逐字读取比较可靠)
几个函数的具体用法见:createFileA 函数 (fileapi.h) - Win32 apps | Microsoft Learn
注意理解其中参数的用法和作用
用while循环实现报文读入:逐字读入缓冲区,首先判断找到报文头,找到报文头后开始把一条完整的报文读入保存,根据报文尾判断读取到/r/n结束,解析输出。
具体内容见代码和注释:
1.读取、解析ASCII的报文
/*DATE: 2023-10-8
* EDIT BY: 小小污见大大污
* QINGDAO
*/
#include<iostream>
#include<Windows.h>
#include<stdio.h>
#include<vector>
#include<sstream>
using namespace std;
//定义一些全局变量
HANDLE serialCom = 0;
DWORD errorFlag = 0;
char rec_buf[1024] = "";
char temp_buf = '0';
string deCode_buf = "";
DWORD readLen = 0;
int i = 0;
int main(void)
{
serialCom = CreateFile(L"COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);//打开串口
if (serialCom == INVALID_HANDLE_VALUE) //判断端口是否被正常打开
{
errorFlag = ::GetLastError(); //获取错误代码
cout << "打开失败,错误代码" << errorFlag << endl;
}
SetupComm(serialCom, 1024, 1024);//配置缓存区大小
//定义一个DCB的结构体,用于配置串口参数
DCB dcb;
memset(&dcb, 0, sizeof(dcb));//初始化dcb
dcb.DCBlength = sizeof(dcb);
dcb.BaudRate = 19200; //波特率
dcb.ByteSize = 8; //数据位
dcb.Parity = EVENPARITY;//校验方式
dcb.StopBits = ONESTOPBIT;//停止位
dcb.fBinary = TRUE;
dcb.fParity = TRUE;
SetCommState(serialCom, &dcb);//配置串口参数
PurgeComm(serialCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);//清空缓冲区
if (!SetCommState(serialCom, &dcb))//判断是否够配置成功
{
errorFlag = GetLastError();
cout << "配置参数失败," << "错误代码" << errorFlag << endl;
}
bool flag1 = 0;//定义一个bool变量,相当于一个开关
int i = 0;
//读取报文,每次读取一个字节
//若一次读取多个字节,在发送速度过快的情况下可能出现乱码
while(ReadFile(serialCom, &temp_buf, 1, &readLen, NULL))
{
if (temp_buf == '$')//判断报文头是否是$,是--保存并打开开关flag1,开始读取后面的报文,否--进入下一次循环读取下一个字节
{
rec_buf[i] = temp_buf;
i++;
flag1 = 1;
}
else if (flag1 == 1)
{
rec_buf[i] = temp_buf;
i++;
}
if (rec_buf[i - 2] == '\r'&&rec_buf[i-1] == '\n')//读取到报文尾为\r\n则结束,注意这里的i的值
{
deCode_buf = rec_buf;
deCode(deCode_buf);//把读取到的一条完整的报文传入自己写的解析函数
flag1 = 0;//关闭开关 读取下一条报文
i = 0;
char rec_buf[1024] = {};//再次初始化缓冲区 用memset()更好
}
}
system("pause");
return 0;
}
//自己定义的解析报文的函数 根据协议写,这里以gps报文为例
void deCode(string deCode_buf)//解析报文
{
vector<string> arr;//定义一个string类型的容器
int position = 0;
//将报文以逗号分割
//如果传入参数为char类型,也可以其他char下的一些分割函数更方便
while (position != -1)
{
string tmp_s;
position = deCode_buf.find(","); //找到逗号的位置
tmp_s = deCode_buf.substr(0, position); //截取需要的字符串
deCode_buf.erase(0, position + 1); //将已读取的数据删去
arr.push_back(tmp_s); //将字符串压入容器中
}
string datetime = arr[1].substr(0, 2) + ":" + arr[1].substr(2, 2) + ":" + arr[1].substr(4, 2);
cout << "UTC时间:" << datetime << endl;
cout << "经度:" << arr[2] << endl;
cout << "纬度:" << arr[4] << endl;
cout << "卫星数量:" << arr[7] << endl;
cout << "天线高度:" << arr[9] << arr[10] << endl;
cout << "大地椭球面相对海平面的高度:" << arr[11] << arr[12] << endl;
cout << "校验和:" << arr[14] << endl;
}
2.读取、解析十六进制报文,逻辑和上面一样,这里加了一个读取一条报文就发送一次命令的功能,具体实现稍有不同。
#include<iostream>
#include<Windows.h>
#include<stdio.h>
#include<vector>
#include<sstream>
using namespace std;
void deCode(unsigned char deCode_buf[])//解析报文(十六进制报文一般有固定的长度,解析起来简单点)
{
printf("%x", deCode_buf[0]);
printf("%x", deCode_buf[1]);
printf("%x", deCode_buf[2]);
printf("%x", deCode_buf[3]);
printf("%x", deCode_buf[4]);
printf("%x", deCode_buf[5]);
printf("%x", deCode_buf[6]);
printf("%x", deCode_buf[7]);
}
int main()
{
HANDLE serialCom = 0;
DWORD errorFlag = 0;
serialCom = CreateFile(L"COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (serialCom == INVALID_HANDLE_VALUE) //判断端口是否被正常打开
{
errorFlag = ::GetLastError(); //获取错误代码
cout << "打开失败,错误代码" << errorFlag << endl;
}
SetupComm(serialCom, 1024, 1024);//配置缓存区大小
DCB dcb;
memset(&dcb, 0, sizeof(dcb));//初始化dcb
dcb.DCBlength = sizeof(dcb);
dcb.BaudRate = 19200; //波特率
dcb.ByteSize = 8; //数据位数
dcb.Parity = EVENPARITY;
dcb.StopBits = ONESTOPBIT;
dcb.fBinary = TRUE;
dcb.fParity = TRUE;
SetCommState(serialCom, &dcb);
PurgeComm(serialCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
if (!SetCommState(serialCom, &dcb))
{
errorFlag = GetLastError();
cout << "配置参数失败," << "错误代码" << errorFlag << endl;
}
unsigned char rec_buf[1024] = {};
unsigned char temp_buf='0';
DWORD readLen = 0;
DWORD writeLen = 0;
bool flag1 = 0;
bool flag2 = 0;
int i = 0;
DWORD testlen = 0;
char test[] = {0x01,0x04,0x00,0x00,0x00,0x22,0x70,0x13};
WriteFile(serialCom, &test, sizeof(test), &testlen, NULL);
while (ReadFile(serialCom, &temp_buf, 1, &readLen, NULL))
{
if (temp_buf == 0x01)//判断报文头为01,开始读
{
rec_buf[i] = temp_buf;
i++;
flag1 = 1;
}
else if (flag1 == 1)
{
rec_buf[i] = temp_buf;
if (rec_buf[i] == 0xFE&&i==7)//判断报文尾为FE并且是否包含8个数据,结束
{
flag2 = 1;
}
i++;
}
//读取一条报文就发送一条命令
if (flag2==1)
{
/* for (int j = 0; j < 8; j++)
{
printf("%x", rec_buf[j]);
}*/
deCode(rec_buf);
char test1[] = { '$','4','5','6','\r','\n'};
DWORD writeLen1 = 0;
WriteFile(serialCom, &test1, strlen(test1), &writeLen1, NULL);//发送
i = 0;
flag1 = 0;
flag2 = 0;
unsigned char rec_buf[1024] = {};
}
else if (i > 7)
{
i = 0;
flag1 = 0;
flag2 = 0;
unsigned char rec_buf[1024] = {};
}
}
system("pause");
return 0;
}