(本文主要记录开发过程以及遇到的困难及解决方式)

预期效果:

使用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了。

暂时先到这。。。

Logo

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

更多推荐