最近要用Nooploop的LinkTrack模块做定位,分享数据解析代码。
LinkTrack相关文档的下载地址:资料下载 – Nooploophttps://www.nooploop.com/download/
目录
UWB技术介绍
LinkTrack用到的是UWB技术。UWB 是一种无载波通信技术,利用纳秒至微秒级的非正弦波窄脉冲传输数据。UWB 具备时间分辨率高、穿透力强、功耗低、抗多径效果好、安全性高等优点,因此常被应用于通信与定位领域,尤其是在GNSS(如GPS、BDS、Glonass、Galileo)信号覆盖不到的场合。
UWB 定位原理与GPS 相似,其中:ANCHOR(基站)相当于天上的卫星,TAG(标签)相当于用户端的GNSS 接收机,CONSOLE(控制台)相当于地面的监控站。ANCHOR 一般作为参考位置点,一般安装于固定参考点;TAG 一般作为待定位点,一般安装于待定位载体(如无人机、无人车)上;CONSOLE 一般用于监控系统的运行状态并向其他节点(ANCHOR、TAG)下发指令,一般接到Terminal(终端),如计算机、平板电脑等。UWB 属于电磁波,其在真空中的传播速度与光速相同。通过测量TAG 到ANCHOR 的TOF(飞行时间),乘以光速后,TAG 可以获得到ANCHOR 的距离。通过到多个ANCHOR 距离与参考ANCHOR 的坐标,可以列出多组球面方程,进而由数学方法可以求解出标签的坐标。图7为常见的三边定位原理示意图。
用户手册的协议解析
http://ftp.nooploop.com/software/products/uwb/doc/LinkTrack_User_Manual_V2.2_zh.pdf
数据解析
https://github.com/nooploop-dev/nlink_unpack
官网给出了帧数据的解析代码,通过此代码可以很好地把串口传来的数据解析成我们想要的数据,从而大大减少开发时间。但是,在接收串口数据时,由于帧数据比较大,分两次才接收完,这对于数据处理很不友好。这个问题我花了一天时间才想出解决办法。在此之前,我们先看下串口通信为什么出现数据分包情况。
关于字节
下面是接收LinkTrack模块的数据并解析的核心代码
void MainWindow::ReadData()
{
/******************此处尽量不要放过多的函数处理,否则影响数据接收******************/
QByteArray arr;
static QByteArray buffer;
arr = m_port->readAll();
static bool flag = false;
//判断是不是55开头的数据,如果是的话,就存起来,然后设置flag位true,下次就接收另外一组数据
if(arr.toHex().startsWith("55"))
{
buffer.append(arr); //存储第一组数据
flag = true;
return;
}
if(flag)
{
buffer.append(arr); //存储第二组数据
char *string1 = buffer.toHex().data();
uint8_t data[1024];
size_t data_length;
data_length = NLink_StringToHex(string1, data); //将字符串转换为Hex格式,并将数据存储在data中
if (g_nlt_nodeframe2.UnpackData(data, data_length))
{
nlt_nodeframe2_result_t *result = &g_nlt_nodeframe2.result;
qDebug()<<"LinkTrack NodeFrame0 data unpack successfully:n";
// 和上位机对比,数据是正确的
qDebug()<< "位置:" <<result->pos_3d[0] <<result->pos_3d[1] <<result->pos_3d[2];
qDebug()<< "速度:" <<result->vel_3d[0] <<result->vel_3d[1] <<result->vel_3d[2];
qDebug()<< "加速度:" <<result->imu_acc_3d[0] <<result->imu_acc_3d[1] <<result->imu_acc_3d[2];
}
buffer.clear();//因为buffer是静态变量,用完一次后要记得清空,否则会使得内存溢出
flag=false;
}
}
此代码解析的是g_nlt_nodeframe2协议类型,如果需要解析其他协议,只需要修改三处位置即可。
完整Qt项目
首先配置pro文件,将用到的模块添加进去。
QT += core gui serialport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp \
nlink_linktrack_anchorframe0.c \
nlink_linktrack_aoa_nodeframe0.c \
nlink_linktrack_nodeframe0.c \
nlink_linktrack_nodeframe1.c \
nlink_linktrack_nodeframe2.c \
nlink_linktrack_nodeframe3.c \
nlink_linktrack_nodeframe5.c \
nlink_linktrack_nodeframe6.c \
nlink_linktrack_tagframe0.c \
nlink_tofsense_frame0.c \
nlink_utils.c
HEADERS += \
mainwindow.h \
nlink_linktrack_anchorframe0.h \
nlink_linktrack_aoa_nodeframe0.h \
nlink_linktrack_nodeframe0.h \
nlink_linktrack_nodeframe1.h \
nlink_linktrack_nodeframe2.h \
nlink_linktrack_nodeframe3.h \
nlink_linktrack_nodeframe5.h \
nlink_linktrack_nodeframe6.h \
nlink_linktrack_tagframe0.h \
nlink_tofsense_frame0.h \
nlink_utils.h \
nlink_typedef.h
FORMS += \
mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
main.cpp代码不变,就不展示了
mainwindow.h代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void _Init();//初始化函数声明
private slots:
void on_open_clicked();
void on_send_clicked();
void ReadData();//数据读取
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
//声明串口
QSerialPort *m_port;
};
#endif // MAINWINDOW_H
mainwindow.cpp代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QBuffer>
#include "nlink_linktrack_nodeframe0.h"
#include "nlink_linktrack_nodeframe1.h"
#include "nlink_tofsense_frame0.h"
#include "nlink_utils.h"
#include "nlink_linktrack_nodeframe2.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::_Init()
{
//查找可用的串口
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
QSerialPort port;
port.setPort(info);
if(port.open(QIODevice::ReadWrite))
{
ui->comboBox_port->addItem(port.portName());
port.close();
}
}
//设置波特率下拉菜单默认显示第0项
ui->comboBox_baud->setCurrentIndex(0);
//连接信号槽,用于接收信息
m_port = new QSerialPort();
connect(m_port, SIGNAL(readyRead()), this, SLOT(ReadData()));
}
void MainWindow::on_open_clicked()
{
if(ui->open->text() == tr("打开串口"))
{
//设置串口名
m_port->setPortName(ui->comboBox_port->currentText());
//打开串口
m_port->open(QIODevice::ReadWrite);
// 设置波特率
m_port->setBaudRate(ui->comboBox_baud->currentText().toInt());
// 设置数据位
switch(ui->comboBox_bit->currentText().toInt())
{
case 8:
m_port->setDataBits(QSerialPort::Data8);
break;
case 7:
m_port->setDataBits(QSerialPort::Data7);
break;
case 6:
m_port->setDataBits(QSerialPort::Data6);
break;
case 5:
m_port->setDataBits(QSerialPort::Data5);
break;
default:
break;
}
// 设置控制流
m_port->setFlowControl(QSerialPort::NoFlowControl);
// 关闭菜单使能
ui->comboBox_port->setEnabled(false);
ui->comboBox_baud->setEnabled(false);
ui->comboBox_bit->setEnabled(false);
ui->comboBox_parity->setEnabled(false);
ui->comboBox_stop->setEnabled(false);
ui->open->setText(tr("关闭串口"));
}
else
{
// 关闭串口
m_port->clear();
m_port->close();
// m_port->deleteLater(); //这句是删除串口
// 恢复菜单使能
ui->comboBox_port->setEnabled(true);
ui->comboBox_baud->setEnabled(true);
ui->comboBox_bit->setEnabled(true);
ui->comboBox_parity->setEnabled(true);
ui->comboBox_stop->setEnabled(true);
ui->open->setText(tr("打开串口"));
}
}
void MainWindow::on_send_clicked()
{
m_port->write(ui->lineEdit_send->text().toUtf8());
}
void MainWindow::ReadData()
{
/******************此处尽量不要放过多的函数处理,否则影响数据接收******************/
QByteArray arr;
static QByteArray buffer;
arr = m_port->readAll();
static bool flag = false;
//判断是不是55开头的数据,如果是的话,就存起来,然后设置flag位true,下次就接收另外一组数据
if(arr.toHex().startsWith("55"))
{
buffer.append(arr); //存储第一组数据
flag = true;
return;
}
if(flag)
{
buffer.append(arr); //存储第二组数据
char *string1 = buffer.toHex().data();
uint8_t data[1024];
size_t data_length;
data_length = NLink_StringToHex(string1, data); //将字符串转换为Hex格式,并将数据存储在data中
if (g_nlt_nodeframe2.UnpackData(data, data_length))
{
nlt_nodeframe2_result_t *result = &g_nlt_nodeframe2.result;
qDebug()<<"LinkTrack NodeFrame0 data unpack successfully:n";
// 和上位机对比,数据是正确的
qDebug()<< "位置:" <<result->pos_3d[0] <<result->pos_3d[1] <<result->pos_3d[2];
qDebug()<< "速度:" <<result->vel_3d[0] <<result->vel_3d[1] <<result->vel_3d[2];
qDebug()<< "加速度:" <<result->imu_acc_3d[0] <<result->imu_acc_3d[1] <<result->imu_acc_3d[2];
ui->textEdit_get->clear();
QString str1, str2, str3;
str1 ="位置:" + QString::number(result->pos_3d[0]) + " " + QString::number(result->pos_3d[1]) + " " + QString::number(result->pos_3d[2]);
str2 ="速度:" + QString::number(result->vel_3d[0]) + " " + QString::number(result->vel_3d[1]) + " " + QString::number(result->vel_3d[2]);
str3 ="加速度:" + QString::number(result->imu_acc_3d[0]) + " " + QString::number(result->imu_acc_3d[1]) + " " + QString::number(result->imu_acc_3d[2]);
ui->textEdit_get->append(str1);
ui->textEdit_get->append(str2);
ui->textEdit_get->append(str3);
}
buffer.clear();//因为buffer是静态变量,用完一次后要记得清空,否则会使得内存溢出
flag=false;
}
}
void MainWindow::on_check_clicked()
{
ui->comboBox_port->clear();
_Init();
}
Qt工程文件
在我的博客里面找一下,0积分下载