【QT+PCL】串口读取单线激光雷达数据并在QVtkWidget组件中实时刷新(一)
(本文主要记录开发过程以及遇到的困难及解决方式)
(本文主要记录开发过程以及遇到的困难及解决方式)
预期效果:
使用qt串口连接雷达,提取,解析雷达数据,并在QVtkWidget中显示雷达探测的廓视线并实时刷新点云图,效果如图:
开发工具:
开发环境:qt+pcl+vtk;
雷达型号及串口协议:M10单线 TOF 近距离机械式激光雷达
开发过程:
(一)串口连接
Qt中有专门的串口连接模块可以直接进行使用;
首先,需要在qt的工程文件(.pro)当中添加以下这行代码:
QT += serialport
其次,再添加使用串口所需要的头文件:
#include <QSerialPortInfo> //获取串口信息
#include <QSerialPort>
以上是使用串口的准备工作,下面可以实现用串口连接雷达了,首先在头文件中声明一个串口类对象,根据连接设备的串口协议来进行串口初始化:
QSerialPort *m_port;//雷达串口(头文件中声明)
void serialInit();
void MainWindow::serialInit()
{
m_port = new QSerialPort(this);//雷达串口
m_port->open(QIODevice::ReadWrite);//打开串口
m_port->setBaudRate(512000);//设置波特率
m_port->setDataBits(QSerialPort::Data8);//设置数据位数
m_port->setParity(QSerialPort::NoParity);//设置奇偶校验
m_port->setStopBits(QSerialPort::OneStop);//设置停止位
m_port->setFlowControl(QSerialPort::NoFlowControl);//设置流控制
m_port->close();//关闭串口
}
串口初始化工作完成,接下来就可以进行提取,解析雷达数据了。
(二)雷达串口数据提取与解析
第一步先要打开雷达串口并且进行连接,得到雷达每次读取所返回的数据。要得到实时数据,需要进行信号与槽的连接,要的效果是串口读取到数据立即调用处理数据的函数;读取雷达串口数据如下:
//读取串口数据
QByteArray MainWindow::ReadData()
{
m_port->setPortName("COM5");//打开雷达串口
m_port->open(QIODevice::ReadOnly);//只读模式打开
QByteArray a1 = m_port->readAll().toHex();//创建临时变量接收雷达返回的数据
/*QByteArray变量接收到的数据是单字符,但是雷达串口协议当中是将两个字符代表一个数据量,所以
在这里需要处理一下字符串,粘连问题。例如帧头是5a a5 ,不做处理的话接收到的数据为5 a a 5,虽然说到后面也能进行解析,但无疑要麻烦很多;*/
QByteArray a2 = QByteArray::fromHex(QString(a1).toLocal8Bit());//处理字符粘连
return a2;
}
接下来是信号与槽连接了,需要在头文件中声明信号,但是只需要声明不需要实现:
QByteArray data;//存储串口数据
signals:
void readyRead();//信号
private slots:
QByteArray ReadData();//读取串口数据函数
void sloUpdataPCL(); //刷新点云图函数,后面会用到
在构造函数中进行连接,我在这里使用了lambda表达式:
ReadData();
connect(m_port,&QSerialPort::readyRead,[=](){
data.append(ReadData()); //追加的方式读取数据
if(data.size()>4000)
{
dataEmit(data);
}
});
在连接前,我还在这调用了一次ReadData()函数,因为你必须先要读取数据,后面才会触发readyRead()这个信号,才能够实时刷新数据。到这里,data中已经提取到了雷达返回的数据。
输出结果如下,成功读取到雷达串口数据:
接下来是进行雷达数据的解析,将发送的串口数据根据串口协议,解析成坐标中点,并且进行显示。(这一步耗费了我大把的时间,前方高能)
首先根据串口协议以及开发所需,在头文件中设计并定义相应的变量:
bool l_flag = 0; //读取pcd文件的标志位
bool flag = 0; //一圈的数据标志位
unsigned char Buffer[3840] = {0}; //160(一帧的数据量)*24
const unsigned char DATA_NUM = 24; //串口协议中360度包含24组完整的数据
float Buffer_ang[1680] = {0}; //每个点在坐标系中对应的角度
float Buffer_dis[1680] = {0}; //每个点的长度
变量定义好之后,就是数据解析的函数了:
void dataProcess(QByteArray &data)
{
QByteArray d1 = data.left(4000);
unsigned char* buff = (unsigned char*) d1.data();
static unsigned char group = 0, nums = 0, s = 0; //组号,计数器(0-159),s:帧头状态
static int index = 0;//收到的数据需要查找到帧头才进行下一步的处理
while(flag == 0)
{
if((buff[index] == 0xa5)&&(s == 0))
{
s = 1;
Buffer[nums + group * 160] = buff[index++];
nums++;
}
else if((buff[index] == 0x5a)&&(s == 1))
{
s = 2;
Buffer[group * 160 + nums] = buff[index++];
nums++;
}
else if ((s == 2) && (nums < 160))
{
Buffer[group * 160 + nums] = buff[index++];
nums++;
if (group != DATA_NUM - 1) //不是最后一组
{
if (nums == 159)
{
Buffer[group * 160 + nums] = buff[index++];//保留Buffer数据完整性,可不加这条语句;
nums = 0;
s = 0;
group++;
}
}
if (group == DATA_NUM - 1) //最后一组
{
if (nums == 159)
{
Buffer[group * 160 + nums] = buff[index];//保留Buffer数据完整性,可不加这条语句;
nums = 0;
s = 0;
group = 0;
flag = 1;
data.remove(0,3840+index);
}
}
}
else
{
index++;
s=0;
}
}
if(flag == 1)
{
index = 0;
group = 0;
nums = 0;
s = 0;
dataGet();
}
}
主要的思路是这样的,data取得串口数据之后(追加的方式读取),当它的长度大于一周数据的量之后(3840),截取这段数据进行解析,处理完之后将一圈的标志位设为1,便得到了一圈的数据,处理完之后再将标志位设为0,便能够实现对数据的实时解析了;
得到数据之后就能将它转换成坐标系下的点了:
void dataGet()
{
pcl::PointCloud<pcl::PointXYZ> cloud;
cloud.width=1680;
cloud.height=1;
cloud.is_dense=false;
cloud.points.resize(cloud.width*cloud.height);
int i = 0, j = 0;
double Ang_pre = 0; //每组的初始角度
double Ang_add = 0.21428571; // 15度均分70个点
for (i = 0; i < DATA_NUM; i++) //每帧循环24组,每组15°,共360°
{
Ang_pre = ((float)Buffer[i * 160 + 4] * 256 + (float)Buffer[i * 160 + 5]) / 100; //该组角度,buf[4]高8位,buf[5]低八位
for( j = 0; j < 70; j++)
{
if((Buffer[i * 160 + 8 + 2 * j] == 0xff) && (Buffer[i * 160 + 9 + 2 * j] == 0xff) )
{
Buffer[i * 160 + 8 + 2 * j] = 0;
Buffer[i * 160 + 9 + 2 * j] = 0;
}
}
for(j = 0; j < 70; j++)
{
Buffer_ang[i * 70 + j] = Ang_pre + Ang_add * j;//angel
if(Buffer_ang[i * 70 + j] > 360)
{
Buffer_ang[i * 70 + j] = Buffer_ang[i* 70 + j] - 360;
}
if(Buffer[i * 160 + 8 + 2 * j] >= 128)
{
Buffer[i * 160 + 8 + 2 * j] = Buffer[i * 160 + 8 + 2 * j] - 128; //高字节最高位是一的话减去一
}
Buffer_dis[i * 70 + j] = ((float)Buffer[i * 160 + 8 + 2 * j] * 256 + (float)Buffer[i * 160 + 9 + 2 * j])/1000;//距离单位:m
cloud.points[i * 70 + j].x=Buffer_dis[i * 70 + j] * cos(Buffer_ang[i * 70 + j] * 0.0174533);
cloud.points[i * 70 + j].y=Buffer_dis[i * 70 + j] * sin(Buffer_ang[i * 70 + j] * 0.0174533);
cloud.points[i * 70 + j].z=0;
}
}
bool binary_mode = false; // binary 或 ascii 模式, 默认是ascii.
if(-1 == pcl::io::savePCDFile("learn_1.pcd", cloud, binary_mode)){
std::cout<<"save pcd file failed."<<std::endl;
}else{
l_flag = true;//读取到文件的标志位
}
flag = 0;
}
到这里,已将雷达串口数据提取并且解析成坐标系下的点,采用pcl::pointXYZ点云类型保存在learn_1.pcd文件中,接下来需要做的就是将这些显示在QVTKWidget组件中。下面的难点在我看来就只有QT+PCL+VTK环境的搭建了,搭建好之后剩下的就是小case了。
暂时先到这。。。

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)