缘由

几年前做的一个串口激光雷达上位机,搞到一个扫地机器人上用的单线激光雷达,并且卖家有部分协议(强度部分协议没有,理论上可以显示彩色点云),自己做个上位机玩玩,准备装到小车玩SLAM。

QT安装

网上教程很多,安装时一般选择如下组件即可:

MinGW 5.3.0 32 bit 编译器模块。MinGW 是 Minimalist GNU for Windows 的缩写,MinGW 是 Windows 平台上使用的 GNU 工具集导入库的集合。
Qt Charts是二维图表模块,用于绘制柱状图、饼图、曲线图等常用二维图表。
Qt Data Visualization 是三维数据图表模块,用于数据的三维显示,如散点的三维空间分布、三维曲面等。
Qt Purchasing、Qt WebEngine、Qt Network Auth(TP)等其他模块,括号里的 TP 表示技术预览。

Qt Creator 4.3.1 是用于 Qt 程序开发的 IDE。
MinGW 5.3.0 是 MinGW 编译工具链。

各个模块实现

主窗口

程序部分地方有写舵机,因为之前还测试过串口舵机,测试随便写的程序,这里不体现舵机

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "console.h"
#include "settingsdialog.h"
#include "pointForm.h"

#include <QMessageBox>
#include <QLabel>
#include <QtSerialPort/QSerialPort>
#include<iostream>
using namespace std;


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    //setupUi(this)是由.ui文件生成的类的构造函数,
    //这个函数的作用是对界面进行初始化,它按照我们在Qt设计器里设计的样子把窗体画出来,
    //把我们在Qt设计器里面定义的信号和槽建立起来。
    //也可以说,setupUi是我们画界面和写程序之间的桥梁。
    ui->setupUi(this);
    //控制台
    console = new Console;
    console->setEnabled(false);
    //将控制台设置为中心窗口部件
    setCentralWidget(console);
    //舵机
    servo=new MSmartServo();
    lidar=new lidar_Driver();
    point=new pointForm();
    //串口通信
    serial = new QSerialPort(this);
    //设置窗口
    settings = new SettingsDialog;
    /*calls*/
    //连接按钮
    ui->actionConnect->setEnabled(true);
    //断开按钮
    ui->actionDisconnect->setEnabled(false);
    //退出按钮
    ui->actionQuit->setEnabled(true);
    /*tools*/
    //设置按钮
    ui->actionConfigure->setEnabled(true);
    //状态栏标签
    status = new QLabel;
    ui->statusBar->addWidget(status);

    initActionsConnections();
    //事件绑定,串口错误处理
    connect(serial, static_cast<void (QSerialPort::*)(QSerialPort::SerialPortError)>(&QSerialPort::error),
            this, &MainWindow::handleError);
    //串口接受显示
    connect(serial, &QSerialPort::readyRead, this, &MainWindow::readData);
    //控制台发送信号
    connect(console, &Console::getData, this, &MainWindow::writeStrData);
    //接收处理
    connect(this, &MainWindow::getBuffer, this, &MainWindow::receiveProcess);


    //checkCPU();
}

void MainWindow::checkCPU() {

    union test {
        int a;
        char b;

    }c;
    c.a = 0x1234;//大端:12,34;小端:34,12
    if (c.b == 0x34)
        cout << "xd" << endl;
    else
        cout << "dd" << endl;

    cout << "The size of union  test  is : " << sizeof(test) << "bytes" << endl;

}

/*释放资源*/
MainWindow::~MainWindow()
{
    delete settings;
    delete ui;
}

/*打开串口*/
void MainWindow::openSerialPort()
{
    /*QString ss1="FA A0 60 51 88 13 00 04 88 13 00 04 88 13 00 04 88 13 00 04 93 23";
    QByteArray data=console->HexStringToByteArray(ss1);
    QString ss=lidar->lidarDataProcess(data.data(),data.size()).c_str();
    QString strShow2 = QString("    【测试】%1").arg(ss);
    console->putData(strShow2);
    uint8_t data1=0xff;
    uint16_t data2=data1<<8;
    printf("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
    printf("yyyyyyyyyyyyyy%02Xzzzzzzzzzzzzzzzz%02X\n",data1,data2);
    return;*/

receiveBufferData.clear();
    //获取设置参数
    SettingsDialog::Settings p = settings->settings();
    //COM口
    serial->setPortName(p.name);
    //波特率
    serial->setBaudRate(p.baudRate);
    //数据位
    serial->setDataBits(p.dataBits);
    //校验位
    serial->setParity(p.parity);
    //停止位
    serial->setStopBits(p.stopBits);
    serial->setReadBufferSize(1000);
    //数据流量的控制
    serial->setFlowControl(p.flowControl);
    hexSend=p.hexSend;
    hexReceive=p.hexReceive;
    receiveBuffer=p.receiveBuffer;
    stringReceiveStart=p.stringReceiveStart;
    stringReceiveEnd=p.stringReceiveEnd;
    //打开串口
    if (serial->open(QIODevice::ReadWrite)) {
        //激活控制台
        console->setEnabled(true);
        console->setReadOnly(false);
        //
        console->setLocalEchoEnabled(p.localEchoEnabled);
        //按钮
        ui->actionConnect->setEnabled(false);
        ui->actionDisconnect->setEnabled(true);
        ui->actionConfigure->setEnabled(false);
        //状态栏显示
        showStatusMessage(tr("Connected to %1 : %2, %3, %4, %5, %6")
                          .arg(p.name).arg(p.stringBaudRate).arg(p.stringDataBits)
                          .arg(p.stringParity).arg(p.stringStopBits).arg(p.stringFlowControl));
    } else {
        QMessageBox::critical(this, tr("Error"), serial->errorString());

        showStatusMessage(tr("Open error"));
    }
}

/*关闭串口*/
void MainWindow::closeSerialPort()
{
    //关闭
    if (serial->isOpen())
        serial->close();
    console->setEnabled(false);
    ui->actionConnect->setEnabled(true);
    ui->actionDisconnect->setEnabled(false);
    ui->actionConfigure->setEnabled(true);
    showStatusMessage(tr("Disconnected"));
}

/*关于信息*/
void MainWindow::about()
{
    QMessageBox::about(this, tr("关于串口终端"),
                       tr("这个<b>串口终端</b>的例子演示了如何使用Qt在现代GUI应用程序中使用Qt串口模块,"
                          "包括一个菜单栏、一个工具栏和一个状态栏。"));
}

/*串口发送*/
void MainWindow::writeByteData(const QByteArray &data)
{
    if (!serial->isOpen())
    {
        console->putData(QString("串口未连接!"));
        return;
    }
    if(hexSend)
    {
        //以16进制发送,直接发
        serial->write(data);
    }
    else
    {
        //以字符发送,把16进制的每个字符都拆开发
        serial->write(data.toHex());
    }
    //显示
    QDateTime dateTime = QDateTime::currentDateTime();
    QString timestamp = dateTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
    QString strData =console->ByteArrayToHexString(data);
    QString strShow = QString("【%1发送】    %2").arg(timestamp).arg(strData);
    console->putData(strShow);
    //cout<<"hexSend"<<hexSend<<endl;
}

/*串口发送*/
void MainWindow::writeStrData(const QString &data)
{
    if (!serial->isOpen())
    {
        console->putData(QString("串口未连接!"));
        return;
    }
    if(hexSend)
    {
        //以16进制发送,字符串就是空格分割16进制
        QByteArray ret=console->HexStringToByteArray(data);
        serial->write(ret);
    }
    else
    {
        //以字符发送,拆分单个字符
        serial->write(data.toLocal8Bit());
    }
    //显示
    QDateTime dateTime = QDateTime::currentDateTime();
    QString timestamp = dateTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
    QString strData =data;
    QString strShow = QString("【%1发送】    %2").arg(timestamp).arg(strData);
    console->putData(strShow);
    //cout<<"hexSend"<<hexSend<<endl;
}

/*串口接收*/
void MainWindow::readData()
{

    //串口读取所有字符
    QByteArray data = serial->readAll();
    //qDebug("数据大小!!!%d\n",data.size());
    //printf("0000000000000000000000000000000000\n");
    //printf("接收%s",QString(data).data());
    //printf("1111111111111111111111111111/n");
    if(receiveBuffer && !QString(stringReceiveStart).isEmpty())
    {
        //printf("555555555555555555555\n");
        receiveBufferData.append(data);
        int iStart=-1;
        int iEnd=-1;
        do
        {
            iStart=-1;
            iEnd=-1;
        for (int i=0;i<receiveBufferData.size();i++) {
            char c=receiveBufferData.at(i);
            QString strChar=console->Char2Hex(c);
            //string
            //std::cout<<"++"<<strChar.data()<<"++"<<stringReceiveStart.data()<<endl;
            //printf("++%s++%s\n",strChar.toLatin1().data(),stringReceiveStart.toLatin1().data());
            if(strChar==stringReceiveStart)
            {
                //printf("22222222222222222222222222\n");
                iStart=i;
                break;
            }
        }
        for (int j=iStart+1;j<receiveBufferData.size();j++) {
            char c=receiveBufferData.at(j);
            QString strChar=console->Char2Hex(c);
            if(!QString(stringReceiveEnd).isEmpty())
            {
                if(strChar==stringReceiveEnd)
                {
                    //printf("333333333333333333333333333333333333333333\n");
                    iEnd=j;
                    break;
                }
            }
            else
            {
                //最后一组数据收不到
                if(strChar==stringReceiveStart)
                {
                    //printf("444444444444444444444444444444444444444444444444444444\n");
                    iEnd=j-1;
                    break;
                }
            }
        }
        if(iStart>=0 && iEnd>=0)
        {
            data=receiveBufferData.mid(iStart,iEnd-iStart+1);
            receiveBufferData.remove(0,iEnd+1);
            //qDebug("缓存大小%d\n",receiveBufferData.size());
            emit getBuffer(data);

        }
        }while(iStart>=0 && iEnd>=0);
    }
    else
    {
        emit getBuffer(data);
    }
}

/*接收处理*/
void MainWindow::receiveProcess(const QByteArray &data)
{
    //显示
    QDateTime dateTime = QDateTime::currentDateTime();
    QString timestamp = dateTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
    QString strData;
    if(hexReceive)
    {
        //以16进制接收
        strData =console->ByteArrayToHexString(data);
    }
    else
    {
        //以字符接收
        strData=data;
    }
    QString strShow = QString("    【%1接收】%2").arg(timestamp).arg(strData);
    console->putData(strShow);
    if(iDriveID==1)
    {
        /*QString ss=servoDataProcess(data);
        QString strShow1 = QString("    【%1解析】%2").arg(timestamp).arg(ss);
        console->putData(strShow1);*/
    }
    if(iDriveID==2)
    {

        //QString ss1="FA A0 60 51 88 13 00 04 88 13 00 04 88 13 00 04 88 13 00 04 93 23";
        //QByteArray ret=console->HexStringToByteArray(ss1);
        //QString ss=lidarDataProcess(ret);
        //malloc
        int firstAngle=0;
        QString ss=lidar->lidarDataProcess(data.data(),data.size(),&firstAngle).c_str();
        if(firstAngle==356)
        {
            //qDebug("好点:\n");
           qDebug("好点:%d\n",lidar->goodPointCount);
            lidar->goodPointCount=0;
            //qDebug("\n串口第一个%d\n",lidar->lidarPoint[359].distance);
            //qDebug("串口最小X%f\n",lidar->xxmin);
            point->showPoint(lidar->lidarPoint);
        }
        //QString ss=lidarDataProcess(data);
        QString strShow2 = QString("    【%1解析】%2").arg(timestamp).arg(ss);
        console->putData(strShow2);
    }
    //cout<<"hexReceive"<<hexReceive<<endl;
}

/*串口错误事件处理*/
void MainWindow::handleError(QSerialPort::SerialPortError error)
{
    if (error == QSerialPort::ResourceError) {
        QMessageBox::critical(this, tr("Critical Error"), serial->errorString());
        closeSerialPort();
    }
}

/*信号槽连接*/
void MainWindow::initActionsConnections()
{
    connect(ui->actionConnect, &QAction::triggered, this, &MainWindow::openSerialPort);
    connect(ui->actionDisconnect, &QAction::triggered, this, &MainWindow::closeSerialPort);
    connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::close);
    connect(ui->actionConfigure, &QAction::triggered, settings, &SettingsDialog::show);
    connect(ui->actionClear, &QAction::triggered, console, &Console::clear);
    connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::about);
    connect(ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt);
    connect(ui->actionServoInit, &QAction::triggered, this, &MainWindow::ServoInit);
    connect(ui->actionServoRelMove, &QAction::triggered, this, &MainWindow::ServoRelMove);
    connect(ui->actionLidarStart, &QAction::triggered, this, &MainWindow::LidarStart);
    connect(ui->actionPointShow, &QAction::triggered, this, &MainWindow::ShowPointForm);
    connect(ui->actionLidarStop, &QAction::triggered, this, &MainWindow::LidarStop);
}

/*状态栏显示*/
void MainWindow::showStatusMessage(const QString &message)
{
    status->setText(message);
}

/*雷达启动*/
void MainWindow::LidarStart()
{
    iDriveID=2;
    writeStrData("startlds$");
}

/*显示点*/
void MainWindow::ShowPointForm()
{
point->show();
}

/*雷达停止*/
void MainWindow::LidarStop()
{
    writeStrData("stoplds$");
}

串口设置

#include "settingsdialog.h"
#include "ui_settingsdialog.h"

#include <QtSerialPort/QSerialPortInfo>
#include <QIntValidator>
#include <QLineEdit>

QT_USE_NAMESPACE

static const char blankString[] = QT_TRANSLATE_NOOP("SettingsDialog", "N/A");

SettingsDialog::SettingsDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::SettingsDialog)
{
    ui->setupUi(this);

    intValidator = new QIntValidator(0, 4000000, this);

    ui->baudRateBox->setInsertPolicy(QComboBox::NoInsert);

    connect(ui->applyButton, &QPushButton::clicked,
            this, &SettingsDialog::apply);
    connect(ui->serialPortInfoListBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
            this, &SettingsDialog::showPortInfo);
    connect(ui->baudRateBox,  static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
            this, &SettingsDialog::checkCustomBaudRatePolicy);
    connect(ui->serialPortInfoListBox,  static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
            this, &SettingsDialog::checkCustomDevicePathPolicy);

    fillPortsParameters();
    fillPortsInfo();

    updateSettings();
}

SettingsDialog::~SettingsDialog()
{
    delete ui;
}

SettingsDialog::Settings SettingsDialog::settings() const
{
    return currentSettings;
}

void SettingsDialog::showPortInfo(int idx)
{
    if (idx == -1)
        return;

    QStringList list = ui->serialPortInfoListBox->itemData(idx).toStringList();
    ui->descriptionLabel->setText(tr("描述: %1").arg(list.count() > 1 ? list.at(1) : tr(blankString)));
    ui->manufacturerLabel->setText(tr("制造商: %1").arg(list.count() > 2 ? list.at(2) : tr(blankString)));
    ui->serialNumberLabel->setText(tr("序列号: %1").arg(list.count() > 3 ? list.at(3) : tr(blankString)));
    ui->locationLabel->setText(tr("地址: %1").arg(list.count() > 4 ? list.at(4) : tr(blankString)));
    ui->vidLabel->setText(tr("供应商标识符: %1").arg(list.count() > 5 ? list.at(5) : tr(blankString)));
    ui->pidLabel->setText(tr("产品标识符: %1").arg(list.count() > 6 ? list.at(6) : tr(blankString)));
}

void SettingsDialog::apply()
{
    updateSettings();
    hide();
}

void SettingsDialog::checkCustomBaudRatePolicy(int idx)
{
    bool isCustomBaudRate = !ui->baudRateBox->itemData(idx).isValid();
    ui->baudRateBox->setEditable(isCustomBaudRate);
    if (isCustomBaudRate) {
        ui->baudRateBox->clearEditText();
        QLineEdit *edit = ui->baudRateBox->lineEdit();
        edit->setValidator(intValidator);
    }
}

void SettingsDialog::checkCustomDevicePathPolicy(int idx)
{
    bool isCustomPath = !ui->serialPortInfoListBox->itemData(idx).isValid();
    ui->serialPortInfoListBox->setEditable(isCustomPath);
    if (isCustomPath)
        ui->serialPortInfoListBox->clearEditText();
}

void SettingsDialog::fillPortsParameters()
{
    ui->baudRateBox->addItem(QStringLiteral("9600"), QSerialPort::Baud9600);
    ui->baudRateBox->addItem(QStringLiteral("19200"), QSerialPort::Baud19200);
    ui->baudRateBox->addItem(QStringLiteral("38400"), QSerialPort::Baud38400);
    ui->baudRateBox->addItem(QStringLiteral("115200"), QSerialPort::Baud115200);
    ui->baudRateBox->addItem(tr("Custom"));
    ui->baudRateBox->setCurrentIndex(3);

    ui->dataBitsBox->addItem(QStringLiteral("5"), QSerialPort::Data5);
    ui->dataBitsBox->addItem(QStringLiteral("6"), QSerialPort::Data6);
    ui->dataBitsBox->addItem(QStringLiteral("7"), QSerialPort::Data7);
    ui->dataBitsBox->addItem(QStringLiteral("8"), QSerialPort::Data8);
    ui->dataBitsBox->setCurrentIndex(3);

    ui->parityBox->addItem(tr("None"), QSerialPort::NoParity);
    ui->parityBox->addItem(tr("Even"), QSerialPort::EvenParity);
    ui->parityBox->addItem(tr("Odd"), QSerialPort::OddParity);
    ui->parityBox->addItem(tr("Mark"), QSerialPort::MarkParity);
    ui->parityBox->addItem(tr("Space"), QSerialPort::SpaceParity);

    ui->stopBitsBox->addItem(QStringLiteral("1"), QSerialPort::OneStop);
#ifdef Q_OS_WIN
    ui->stopBitsBox->addItem(tr("1.5"), QSerialPort::OneAndHalfStop);
#endif
    ui->stopBitsBox->addItem(QStringLiteral("2"), QSerialPort::TwoStop);

    ui->flowControlBox->addItem(tr("None"), QSerialPort::NoFlowControl);
    ui->flowControlBox->addItem(tr("RTS/CTS"), QSerialPort::HardwareControl);
    ui->flowControlBox->addItem(tr("XON/XOFF"), QSerialPort::SoftwareControl);

    ui->receiveStartTextEdit->setText("FA");
    ui->receiveEndTextEdit->setText("");
}

void SettingsDialog::fillPortsInfo()
{
    ui->serialPortInfoListBox->clear();
    QString description;
    QString manufacturer;
    QString serialNumber;
    const auto infos = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &info : infos) {
        QStringList list;
        description = info.description();
        manufacturer = info.manufacturer();
        serialNumber = info.serialNumber();
        list << info.portName()
             << (!description.isEmpty() ? description : blankString)
             << (!manufacturer.isEmpty() ? manufacturer : blankString)
             << (!serialNumber.isEmpty() ? serialNumber : blankString)
             << info.systemLocation()
             << (info.vendorIdentifier() ? QString::number(info.vendorIdentifier(), 16) : blankString)
             << (info.productIdentifier() ? QString::number(info.productIdentifier(), 16) : blankString);

        ui->serialPortInfoListBox->addItem(list.first(), list);
    }

    ui->serialPortInfoListBox->addItem(tr("Custom"));
}

void SettingsDialog::updateSettings()
{
    currentSettings.name = ui->serialPortInfoListBox->currentText();

    if (ui->baudRateBox->currentIndex() == 4) {
        currentSettings.baudRate = ui->baudRateBox->currentText().toInt();
    } else {
        currentSettings.baudRate = static_cast<QSerialPort::BaudRate>(
                    ui->baudRateBox->itemData(ui->baudRateBox->currentIndex()).toInt());
    }
    currentSettings.stringBaudRate = QString::number(currentSettings.baudRate);

    currentSettings.dataBits = static_cast<QSerialPort::DataBits>(
                ui->dataBitsBox->itemData(ui->dataBitsBox->currentIndex()).toInt());
    currentSettings.stringDataBits = ui->dataBitsBox->currentText();

    currentSettings.parity = static_cast<QSerialPort::Parity>(
                ui->parityBox->itemData(ui->parityBox->currentIndex()).toInt());
    currentSettings.stringParity = ui->parityBox->currentText();

    currentSettings.stopBits = static_cast<QSerialPort::StopBits>(
                ui->stopBitsBox->itemData(ui->stopBitsBox->currentIndex()).toInt());
    currentSettings.stringStopBits = ui->stopBitsBox->currentText();

    currentSettings.flowControl = static_cast<QSerialPort::FlowControl>(
                ui->flowControlBox->itemData(ui->flowControlBox->currentIndex()).toInt());
    currentSettings.stringFlowControl = ui->flowControlBox->currentText();

    currentSettings.localEchoEnabled = ui->localEchoCheckBox->isChecked();

    currentSettings.hexSend=ui->hexSendCheckBox->isChecked();
    currentSettings.hexReceive=ui->hexReceiveCheckBox->isChecked();

    currentSettings.receiveBuffer=ui->receiveBufferCheckBox->isChecked();
    currentSettings.stringReceiveStart=ui->receiveStartTextEdit->toPlainText().toUpper();
    currentSettings.stringReceiveEnd=ui->receiveEndTextEdit->toPlainText().toUpper();
}

雷达点云显示

就是空白画布到时候绘制点云

showPoint方法是核心,它接收一个激光雷达点数组,并在 QGraphicsView上将其绘制成二维散点图。
        坐标转换:将极坐标(角度、距离)转换为视图内的直角坐标(X, Y)。
        颜色映射:根据点云中每个点的强度(strength),通过 hsb2rgb函数映射到颜色(从蓝色到高亮色),以可视化强度信息。
        背景绘制:绘制灰色的参考网格和红色的十字坐标轴。

hsb2rgb方法: 辅助函数,将HSB颜色值转换为RGB。

代码运行方式:每次调用 showPoint都会清除整个场景并重新绘制所有元素(网格、坐标轴、点)。

#include "pointForm.h"
#include "ui_pointForm.h"

pointForm::pointForm(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::pointForm)
{
    ui->setupUi(this);
    scene.setBackgroundBrush(Qt::black);
}

pointForm::~pointForm()
{
    delete ui;
}

void pointForm::showPoint(lidar_point_type* points)
{
    // 耗时检测
    struct timeval tv;
    long startTimeUsec         = 0;
    long endTimeUsec           = 0;
    gettimeofday(&tv,NULL);
    startTimeUsec              = tv.tv_sec*1000000 + tv.tv_usec;
    qDebug("开始时间:  秒 %d, 微秒 %d\n", tv.tv_sec, tv.tv_usec);

    //qDebug("宽%d高%d\n",ui->graphicsView->size().width(),ui->graphicsView->size().height());
    scene.clear();
    //画网格
    int grid=14;
    int w=ui->graphicsView->size().width();
    int h=ui->graphicsView->size().height();
    int r=w>h?h:w;
    double rate=(double)r/(1000*grid);
    QPen gpen;   // 定义一个画笔,设置画笔颜色和宽度
    gpen.setColor(QColor(128, 128, 128));
    gpen.setWidth(1);
    for (int x=0;x<=grid;x++) {
        QGraphicsLineItem *m_lineItem = new QGraphicsLineItem();
        m_lineItem->setLine(QLineF(x*1000* rate, 0, x*1000* rate, r));
        m_lineItem->setPen(gpen);
        scene.addItem(m_lineItem);
    }
    for (int y=0;y<=grid;y++) {
        QGraphicsLineItem *m_lineItem = new QGraphicsLineItem();
        m_lineItem->setLine(QLineF(0, y*1000* rate, r, y*1000* rate));
        m_lineItem->setPen(gpen);
        scene.addItem(m_lineItem);
    }

    QPen apen;   // 定义一个画笔,设置画笔颜色和宽度
    apen.setColor(QColor(160, 0, 0));
    apen.setWidth(3);
    QGraphicsLineItem *x_lineItem = new QGraphicsLineItem();
    x_lineItem->setLine(QLineF(0, grid*500* rate, r, grid*500* rate));
    x_lineItem->setPen(apen);
    scene.addItem(x_lineItem);
    QGraphicsLineItem *y_lineItem = new QGraphicsLineItem();
    y_lineItem->setLine(QLineF(grid*500* rate, 0, grid*500* rate, r));
    y_lineItem->setPen(apen);
    scene.addItem(y_lineItem);

    //画点
    QColor c=QColor(0, 160, 230);
    QPen pen;   // 定义一个画笔,设置画笔颜色和宽度
    pen.setColor(c);
    pen.setWidth(1);
    QBrush brush;   // 定义一个画笔,设置画笔颜色和宽度
    brush.setColor(c);
    brush.setStyle(Qt::BrushStyle::SolidPattern);
    //double xmin=10000;
    //qDebug("第一个!!!!!!!%d\n",DEFAULT_ANGLE_BUF_SIZE);
    for (int i=0;i<DEFAULT_ANGLE_BUF_SIZE;i++) {
        if(points[i].distance<=0)continue;
        int RGB[3] = {0, 0, 0};
        float h=points[i].strength*0.3f;
        h=h>300?300:h;
        hsb2rgb(h, 1.0f, 1.0f,RGB);
        //qDebug("S%dH%fR%dG%dB%d\n",points[i].strength,h,RGB[0], RGB[1], RGB[2]);
        QColor c=QColor(RGB[0], RGB[1], RGB[2]);
        pen.setColor(c);
        brush.setColor(c);
        // 计算当前角度、X、Y坐标
        double angle = ((points[i].angle+90) / 180.0f) * M_PI;
        double x = cos(angle) * points[i].distance * rate+r*0.5;
        double y = -sin(angle) * points[i].distance * rate+r*0.5;
        //double xx = cos(angle) * points[i].distance ;

        QGraphicsEllipseItem* m_circleItem = new QGraphicsEllipseItem();   // 定义一个圆图元
        m_circleItem->setRect(x,y,5,5);
        m_circleItem->setPen(pen);
        m_circleItem->setBrush(brush);
        //m_circleItem->setFlag(QGraphicsItem::ItemIsMovable);

        //circleItems[i] = m_circleItem;   // 定义一个圆
        scene.addItem(m_circleItem);      // 把矩形图元添加到场景
    }


    //int iii=points[0].distance;
    //qDebug("第er个!!!!!!!%d\n",iii);
    //qDebug("最小X%f\n",xmin);
    ui->graphicsView->setScene(&scene); // 把场景添加到视图
    //QRectF rectItem =scene.itemsBoundingRect();
    //ui->graphicsView->fitInView(rectItem,Qt::KeepAspectRatio);
    //scene.update();
    ui->graphicsView->viewport()->update();
    ui->graphicsView->update(0,0,w,h);
    //ui->graphicsView->repaint();

    //获取结束时间
    gettimeofday(&tv,NULL);
    endTimeUsec = tv.tv_sec*1000000 + tv.tv_usec;

    //计算时间消耗
    float time_cost = (endTimeUsec - startTimeUsec);
    time_cost = time_cost / 1000000.0f;
    qDebug("结束时间:  秒 %d, 微秒 %d\n", tv.tv_sec, tv.tv_usec);
    qDebug("时间消耗:  秒 %f\n",time_cost);
}

void pointForm::hsb2rgb(float h, float s, float v,int* RGB) {
  assert(h>=0.0f && h<=360.0f) ;
  assert(s>=0.0f && s<=1.0f) ;
  assert(v>=0.0f && v<=1.0f) ;
  h=h==360.0f?0.0f:h;
  float r = 0, g = 0, b = 0;
  int i =(int)(h / 60) % 6;
  float f = (h / 60) - i;
  float p = v * (1 - s);
  float q = v * (1 - f * s);
  float t = v * (1 - (1 - f) * s);
  switch (i) {
  case 0:
    r = v;
    g = t;
    b = p;
    break;
  case 1:
    r = q;
    g = v;
    b = p;
    break;
  case 2:
    r = p;
    g = v;
    b = t;
    break;
  case 3:
    r = p;
    g = q;
    b = v;
    break;
  case 4:
    r = t;
    g = p;
    b = v;
    break;
  case 5:
    r = v;
    g = p;
    b = q;
    break;
  default:
    break;
  }
  RGB[0] = (int) (r * 255.0);
  RGB[1] = (int) (g * 255.0);
  RGB[2] = (int) (b * 255.0);
}

雷达协议解析

#include "lidar_Driver.h"

lidar_Driver::lidar_Driver()
{
    goodPointCount=0;
}

/*雷达数据解析*/
string lidar_Driver::lidarDataProcess(const char data[],int len,int* firstAngle)
{
    lidar_message lm;
    stringstream ss;

    if(len!=DEFAULT_UART_BUF_SIZE)return "";

    for (int i=0;i<=DEFAULT_UART_BUF_SIZE;i++) {
        lm.storedInputData[i]=data[i];
    }

    if(lm.val.start!=0xFA)return "";
    uint8_t index=lm.val.id-0xA0;
    uint16_t angle =index*4;
    *firstAngle=angle;
    uint8_t speedx=lm.val.speed[0]&0x3F;
    uint16_t speedz=(lm.val.speed[0]>>6) + (lm.val.speed[1]<<2);
    float speed=speedz+speedz/100.0f;
    ss<<"【速度:"<<speed<<"Rpm"<<speed/60<<"Hz】\n";
    //return "123";

    for (int i=0;i<4;i++) {
        lidar_point_type lpt;
        uint8_t data0=lm.val.data[i][0];
        uint8_t data1=lm.val.data[i][1];
        uint8_t data2=lm.val.data[i][2];
        uint8_t data3=lm.val.data[i][3];
        uint16_t distance=data0 + ((data1&0x3F)<<8);
        //printf("111111111111111111\n");
        bool distanceError=(data1&0x80)>>7;
        bool strengthError=(data1&0x40)>>6;
        uint16_t strength=data2 + (data3<<8);
        //printf("222222222222222222222\n");
        lpt.angle=angle+i;
        lpt.speedz=speedz;
        lpt.speedx=speedx;
        lpt.distance=distance;
        lpt.strength=strength;
        lpt.distance_error=distanceError;
        lpt.strength_error=strengthError;
        string strdistance_error=!lpt.distance_error?"正常":"异常";
        string strstrength_error=!lpt.strength_error?"正常":"异常";
        ss<<"【角度"<<lpt.angle<<"°:";
        ss<<"距离"<<strdistance_error<<":"<<lpt.distance<<"mm";
        ss<<"强度"<<strstrength_error<<":"<<lpt.strength;
        //printf("3333333333333333333\n");
        if(lpt.distance_error || lpt.strength_error || lpt.strength==0 || lpt.distance>=6553){
            lpt.distance=0;
            ss<<"放弃】\n";
        }
        else
        {
            goodPointCount++;
            ss<<"】\n";
        }
        lidarPoint[lpt.angle]=lpt;

        //double angle1 = ((angle+90) / 180.0f) * M_PI;
        //double xx = cos(angle1) * lpt.distance ;
    }
    // 计算距离和强度信息
    /*for ( int j = 0; j < 4; ++j) {
        int tag = (lm.val.id - 160) * 4 + j;
        if (tag < 0 || tag >= 360) {
            break;
        }
        uint16_t aa = data[3 + j * 4 + 1] + 256 * (data[3 + j * 4 + 2] & 0x3F);
        uint16_t bb  = data[3 + j * 4 + 3] + 256 * data[3 + j * 4 + 4];
        ss<<"【t角度"<<tag<<"°:";
        ss<<"距离:"<<aa<<"mm";
        ss<<"强度:"<<bb<<"】\n";
    }*/

    return ss.str().data();
}

串口终端显示

继承自 QPlainTextEdit 并进行了重写和功能扩展的自定义控件,主要用于串口通信数据的显示和输入,提供了终端式的用户体验。

1. 显示功能

• putData():两种重载形式,用于显示接收到的数据(支持 QByteArray 和 QString)

• 显示时自动滚动到最新内容

• 限制最大显示行数(10000行)

2. 格式转换功能

• ByteArrayToHexString():将字节数组转为带空格的十六进制字符串

• HexStringToByteArray():将十六进制字符串转为字节数组

• Char2Hex():单个字符转十六进制

• bytesToIntLittle():小端字节序转换

3. 输入处理

• 重写 keyPressEvent(),支持 Enter 键发送当前行数据

• 发送数据时自动获取当前光标所在行的内容

4. 界面定制

背景:黑色
文字:绿色
最大行数限制:10000

5. 事件重写

• keyPressEvent:自定义回车键行为

• mousePressEvent:点击获取焦点

• contextMenuEvent:保留默认右键菜单

6. 信号机制

getData(strLine.toLocal8Bit());  // 发送用户输入的数据

#include "console.h"

#include <QScrollBar>

#include <QtCore/QDebug>
/*控制台初始化*/
//QPlainTextEdit 是一个多行文本编辑器,用于显示和编辑多行简单文本
Console::Console(QWidget *parent)
    : QPlainTextEdit(parent)
    , localEchoEnabled(false)
{
    //限制edit的行数
    document()->setMaximumBlockCount(10000);
    //调色板
    QPalette p = palette();
    p.setColor(QPalette::Base, Qt::black);
    p.setColor(QPalette::Text, Qt::green);
    //设置
    setPalette(p);

}

/*控制台显示数据--字符数组*/
void Console::putData(const QByteArray &data)
{
    setTextEditCursorPosLast();
    //追加内容
    insertPlainText(QString(data));
    insertPlainText("\n");
    //滚动到最后
    QScrollBar *bar = verticalScrollBar();
    bar->setValue(bar->maximum());
}

/*控制台显示数据--字符串*/
void Console::putData(const QString &data)
{
    setTextEditCursorPosLast();
    //追加内容
    insertPlainText(data);
    insertPlainText("\n");
    //滚动到最后
    QScrollBar *bar = verticalScrollBar();
    bar->setValue(bar->maximum());
}

/*字符数组转16进制字符串*/
QString Console::ByteArrayToHexString(QByteArray data){
    QString ret(data.toHex().toUpper());
    int len = ret.length()/2;
    //qDebug()<<len;
    for(int i=1;i<len;i++)
    {
        //qDebug()<<i;
        ret.insert(2*i+i-1," ");
    }

    return ret;
}

/*
 * 将16进制字符串转换为对应的字节序列
 */
QByteArray Console::HexStringToByteArray(QString HexString)
{
    bool ok;
    QByteArray ret;
    HexString = HexString.trimmed();
    HexString = HexString.simplified();
    QStringList sl = HexString.split(" ");

    foreach (QString s, sl) {
        if(!s.isEmpty())
        {
            char c = s.toInt(&ok,16)&0xFF;
            if(ok){
                ret.append(c);
            }else{
                putData(QString("非法的16进制字符: \"%1\"").arg(s));
            }
        }
    }
    //qDebug()<<ret;
    return ret;
}

QString Console::Char2Hex(char byte)
{
  std::string str("");
  std::string str2("0123456789ABCDEF");
     int b;
     b = 0x0f&(byte>>4);
     char s1 = str2.at(b);
     str.append(1,str2.at(b));
     b = 0x0f & byte;
     char s2 = str2.at(b);
     str.append(1,str2.at(b));
   return str.c_str();
}

void Console::setTextEditCursorPosLast()
{
    int iLineNum=QPlainTextEdit::document()->lineCount();
    const QTextBlock block = QPlainTextEdit::document()->findBlockByLineNumber(iLineNum-1);
    QString strLine=block.text();
    if(block.isValid())
    {
        QTextCursor cursor = QPlainTextEdit::textCursor();
        cursor.setPosition(block.position()+strLine.length());
        setTextCursor(cursor);
        ensureCursorVisible();
    }
}

void Console::setLocalEchoEnabled(bool set)
{
    localEchoEnabled = set;
}

/*按键事件*/
void Console::keyPressEvent(QKeyEvent *e)
{
    if(e->key()==Qt::Key_Enter || e->key()==16777220)
    {
        qDebug("enter");
        int iLineNum=QPlainTextEdit::document()->lineCount();
        QString strLine=QPlainTextEdit::document()->findBlockByLineNumber(iLineNum-1).text();

        emit getData(strLine.toLocal8Bit());
    }
    else
    {
        QPlainTextEdit::keyPressEvent(e);
    }
}

void Console::mousePressEvent(QMouseEvent *e)
{
    QPlainTextEdit::mousePressEvent(e);
    Q_UNUSED(e)
    setFocus();
}

void Console::mouseDoubleClickEvent(QMouseEvent *e)
{
    QPlainTextEdit::mouseDoubleClickEvent(e);
    Q_UNUSED(e)
}

void Console::contextMenuEvent(QContextMenuEvent *e)
{
    QPlainTextEdit::contextMenuEvent(e);
    Q_UNUSED(e)
}

/**
 * 以小端模式将byte[]/char转成int
 */
int Console::bytesToIntLittle(QByteArray src, int offset) {
    int value;
    value = (int) ((src[offset] & 0xFF)
            | ((src[offset + 1] & 0xFF) << 8)
            | ((src[offset + 2] & 0xFF) << 16)
            | ((src[offset + 3] & 0xFF) << 24));
    return value;
}

软硬件实测

编译运行

连接雷达

Logo

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

更多推荐