QT和C++实现串口激光雷达上位机
基于QT的串口激光雷达上位机开发摘要 本文实现了一个基于QT框架的串口激光雷达上位机软件,主要用于扫地机器人单线激光雷达的数据采集与可视化。系统采用模块化设计,包含串口通信、数据解析和点云显示三大功能模块。通过自定义协议解析激光雷达的极坐标数据,并实时转换为直角坐标系下的二维散点图。点云显示模块采用HSB色彩映射技术,根据反射强度实现从蓝色到高亮色的渐变效果。实测表明,该系统能够稳定接收11520
缘由
几年前做的一个串口激光雷达上位机,搞到一个扫地机器人上用的单线激光雷达,并且卖家有部分协议(强度部分协议没有,理论上可以显示彩色点云),自己做个上位机玩玩,准备装到小车玩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;
}
软硬件实测
编译运行

连接雷达

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


所有评论(0)