【Qt原创开源项目】多功能串口调试助手3.0(增加数据帧解析功能,适用于PSINS导航开发板)
</b>本项目在作者另一篇文章<b>[【Qt原创开源项目】多功能串口调试助手2.1 ](https://blog.csdn.net/weixin_53470752/article/details/148239443?spm=1011.2124.3001.6209)</b>的基础上增加数据帧解析功能,目前只能解析特定的一种数据帧格式数据。
概要
本项目为原创,代码免费开源,贴在最下方
本项目提供完整项目压缩包资源,可自行下载,同时欢迎交流
Qt版本6.81,项目类型:QMainWindow,用到的库有:QFileDialog、QMessageBox、QDebug、QString、QTimer、QSerialPort、QSerialPortInfo、QFile、QElapsedTimer、QtConcurrent、 QMutex,都是Qt自带的库,项目运行结果如下:
功能说明:参考项目运行界面显示的功能。
注:本项目在作者另一篇文章【Qt原创开源项目】多功能串口调试助手2.1 的基础上增加数据帧解析功能,目前只能解析特定的一种数据帧格式数据,具体格式如下图:
注:该格式是PSINS导航开发板输出的数据帧格式,开发板由西北工业大学严恭敏教授开发,这里不做过多介绍,后面将会更新专用于PSINS开发板的采数软件版本,以及功能更全面的通用版本。
项目组成
main.cpp文件不需要修改,Sport.pro需要加一行代码,见技术细节第一条。mainwindow.h和mainwindow.cpp的完整代码贴在最后面。
技术细节
基础功能实现的相关技术细节见先前版本文章:
【Qt原创开源项目】多功能串口调试助手1.0
【Qt原创开源项目】多功能串口调试助手2.1
- 数据帧的识别与处理
void SerialProcessor::processReceivedData(const QByteArray &data)
{
rxBuff.append(data); // 先将数据复制到缓冲区
// 检测到的第一个帧头位于已接收到的数据中的位置
int frameStart = -1;
// 帧处理循环
while(static_cast<size_t>(rxBuff.size()) >= sizeof(DataFrame))
{
for(int i = 0; i<= rxBuff.size() - static_cast<int>(sizeof(DataFrame)); i++)
{
if(static_cast<uint8_t>(rxBuff[i]) == 0xAA &&
static_cast<uint8_t>(rxBuff[i+1]) == 0x55 &&
static_cast<uint8_t>(rxBuff[i+2]) == 0xAA &&
static_cast<uint8_t>(rxBuff[i+3]) == 0x56)
{
frameStart = i;
break;
}
}
// 找到有效帧头
if(frameStart >=0)
{
// 检查是否包含完整一帧
if(frameStart + sizeof(DataFrame) <= static_cast<size_t>(rxBuff.size()))
{
// 提取帧数据
QByteArray frameData = rxBuff.mid(frameStart, sizeof(DataFrame));
// 处理帧数据
frameProcess(frameData);
// 从缓冲区移除已处理数据
rxBuff.remove(0, frameStart + sizeof(DataFrame));
}
else
{
// 数据不足一帧,等待更多数据
// 移除帧头前的无用数据后跳出while循环,
// 下一次满足进入while循环的数据正好是一帧完整数据。
rxBuff.remove(0, frameStart);
break;
}
}
else
{
// 未找到帧头,消除无效数据
int removeCount = rxBuff.size() - sizeof(DataFrame) + 1;
if(removeCount > 0 && removeCount <= rxBuff.size())
{
rxBuff.remove(0, removeCount);
}
else
{
// 如果没有足够数据,只保留可能构成帧头的部分
if(rxBuff.size() > 4)
{
rxBuff.remove(0, rxBuff.size() - 4);
}
break;
}
}
}
}
帧处理完成时发送完成信号给主线程,用于ui更新显示
void SerialProcessor::frameProcess(const QByteArray &frameData)
{
const DataFrame* frame = reinterpret_cast<const DataFrame*>(frameData.constData());
/* 校验和功能不可用
// 计算校验和(跳过帧头的4个字节)
uint32_t calculatedChecksum = calculateCRC32(frameData.constData() + 4, sizeof(DataFrame) - 8);
uint32_t receivedChecksum = qFromLittleEndian(frame->CheckSum);
// 验证校验和
if(calculatedChecksum != receivedChecksum)
{
qWarning() << "校验和错误!接收:" << receivedChecksum << "计算:" << calculatedChecksum;
emit checksumError();
return;
}
*/
// 拷贝帧数据
memcpy(&m_currentFrame, frame, sizeof(DataFrame));
// 发出帧处理完成信号
emit frameProcessed();
}
- 将经纬度信息的整数部分与小数部分(4个字节的16进制数据)合并成一个double型数据
double SerialProcessor::combinePosition(int32_t integer, uint32_t fraction) const
{
return integer + static_cast<double>(fraction) / 100000000.0;
}
- CRC32校验功能(暂不可用)
uint32_t SerialProcessor::calculateCRC32(const char* data, int length) const
{
// 简单的CRC32实现(实际应用中应使用更健壮的算法)
uint32_t crc = 0xFFFFFFFF;
for(int i = 0; i < length; i++)
{
crc ^= static_cast<uint8_t>(data[i]);
for(int j = 0; j < 8; j++)
{
if(crc & 1)
{
crc = (crc >> 1) ^ 0xEDB88320;
}
else
{
crc >>= 1;
}
}
}
return ~crc;
}
- 一点小小的bug
存在bug:当初始界面放置在第二页(解析数据页)时,运行程序后只打开串口(不点击tabwidget的页头),会出现卡顿崩溃问题。
解决方法:将第一页的显示数据勾选框设置为默认关闭。
疑问:第一页默认显示,即使当第二页不显示,也会崩溃。
但这个问题只在刚运行程序时出现,如果运行程序后,切换一下页头,或者点击一下页头(会让另一页的checkbox取消勾选),再打开串口,程序可正常运行。
猜测:可能跟tabwiget的初始化设置有关。
完整代码
- mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QString>
#include <QTimer>
#include <QFile>
#include <QElapsedTimer> // 高精度计时模块
#include <QtConcurrent> //并发编程模块
#include <QMutex> // 互斥锁
#include "serialprocessor.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
friend void frameProcess(MainWindow *window, const QByteArray &frameData);
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
long receiveNum, sendNum;
private slots:
void on_Key_open_COM_clicked();
void on_Key_send_COM_clicked();
void on_Key_send_COM_clear_clicked();
void on_Key_receive_COM_clear_clicked();
void on_Key_save_COM_clicked();
void on_Key_clearBytes_clicked();
void on_checkBox_timeSend_clicked();
void COM_DataReceive();
void on_Key_capture_COM_clicked();
void onFrameProcessed();
void onChecksumError();
// 点击下拉框刷新串口
void on_tabWidget_tabBarClicked(int index);
public slots:
bool eventFilter(QObject*, QEvent*);
private:
Ui::MainWindow *ui;
void UiInit();
void String2Hex(QString str, QByteArray &senddata);
char ConvertHexChar(char ch);
QTimer* timeSend;
const int MAX_DISPLAY_LINES = 30;
bool isCapturing = false; // 捕获状态标志
QFile captureFile; // 捕获文件对象
QTextStream fileStream; // 文件写入流
QElapsedTimer captureTimer; // 计时统计
QString FilePath; // 捕获数据保存路径
qint64 totalCaptured = 0; // 已捕获字节数
QMutex fileMutex; // 互斥锁成员变量
void startCapture(const QString &fileName);
void stopCapture();
void frameDisplay();
SerialProcessor *serialProcessor;
};
#endif // MAINWINDOW_H
- mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QSerialPort" //串口
#include "QSerialPortInfo" //串口信息
#include <QLabel>
/* 定义串口指针,实例化串口 */
QSerialPort* COM = new QSerialPort();
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
UiInit();
/* 数据接收信号槽链接 */
// 谁发出信号,什么信号(成功读取的信号),谁接收信号,执行信号的槽
connect(COM, SIGNAL(readyRead()), this, SLOT(COM_DataReceive()));
serialProcessor = new SerialProcessor(this);
connect(serialProcessor, &SerialProcessor::frameProcessed, this, &MainWindow::onFrameProcessed);
connect(serialProcessor, &SerialProcessor::checksumError, this, &MainWindow::onChecksumError);
}
MainWindow::~MainWindow()
{
delete ui;
}
/* 发送数据 */
void MainWindow::on_Key_send_COM_clicked()
{
QByteArray data_buf;
QString data_str;
data_buf = ui->textEdit->toPlainText().toLatin1();
int count;
// 发送成功返回发送字节长度,发送失败返回-1
//count = COM->write(ui->textEdit->toPlainText().toLatin1()); // 串口写数据(发送),获取发送框的数据发送
// 如果要正确发送中文字符,使用.toLocal8Bit()
if( !data_buf.isEmpty())
{
data_str = QString(data_buf);
if(ui->checkBox_send_Hex->isChecked())
{
QByteArray data_hex;
String2Hex(data_str, data_hex);
count = COM->write(data_hex);
}
else
{
count = COM->write(data_buf);
}
sendNum += count;
ui->sendBytes->setText(QString::number(sendNum));
ui->sendBytes->setAlignment(Qt::AlignCenter);
}
}
/* 清空发送 */
void MainWindow::on_Key_send_COM_clear_clicked()
{
ui->textEdit->clear(); // 清空发送区文本框
}
/* 串口开关 */
void MainWindow::on_Key_open_COM_clicked()
{
// QSerialPort::BaudRate baudRate; // 波特率
QSerialPort::DataBits dataBits; // 数据位
QSerialPort::StopBits stopBits; // 停止位
QSerialPort::Parity checkBits;// 校验位
if(ui->comboBox_dataBits->currentText() == "8")
dataBits = QSerialPort::Data8;
else if(ui->comboBox_dataBits->currentText() == "7")
dataBits = QSerialPort::Data7;
else if(ui->comboBox_dataBits->currentText() == "6")
dataBits = QSerialPort::Data6;
else if(ui->comboBox_dataBits->currentText() == "5")
dataBits = QSerialPort::Data5;
if(ui->comboBox_stopBits->currentText() == "1")
stopBits = QSerialPort::OneStop;
else if(ui->comboBox_stopBits->currentText() == "1.5")
stopBits = QSerialPort::OneAndHalfStop;
else if(ui->comboBox_stopBits->currentText() == "2")
stopBits = QSerialPort::TwoStop;
if(ui->comboBox_checkBits->currentText() == "无校验")
checkBits = QSerialPort::NoParity;
else if(ui->comboBox_checkBits->currentText() == "奇校验")
checkBits = QSerialPort::OddParity;
else if(ui->comboBox_checkBits->currentText() == "偶校验")
checkBits = QSerialPort::EvenParity;
// 串口属性初始化
COM->setPortName(ui->comboBox_COMnumber->currentText());
COM->setBaudRate(ui->comboBox_baudRate->currentText().toInt());
//COM->setBaudRate(baudRate);
COM->setDataBits(dataBits);
COM->setStopBits(stopBits);
COM->setParity(checkBits);
// 串口操作——打开/关闭
if(ui->Key_open_COM->text() == "打开串口")
{
if(COM->open(QIODevice::ReadWrite) == true) // 串口打开成功
{
ui->Key_send_COM->setEnabled(true);
ui->checkBox_timeSend->setEnabled(true);
ui->lineEdit->setEnabled(true);
ui->Key_open_COM->setText("关闭串口");
ui->Key_open_COM->setStyleSheet("background:lightgreen;border-right:2px solid gray;border-bottom:2px solid gray");
ui->comStatus->setText("打 开");
ui->comStatus->setAlignment(Qt::AlignCenter);
}
else
{
QMessageBox::critical(this, "错误提示", "打开串口失败!\n请检查串口连接是否正常");
}
}
else if(ui->Key_open_COM->text() == "关闭串口")
{
ui->Key_send_COM->setEnabled(false);
ui->checkBox_timeSend->setEnabled(false);
ui->lineEdit->setEnabled(false);
// 关闭定时发送
if(ui->checkBox_timeSend->isChecked())
{
ui->checkBox_timeSend->setCheckState(Qt::Unchecked);
timeSend->stop();
}
COM->close();
ui->Key_open_COM->setText("打开串口");
ui->Key_open_COM->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
ui->comStatus->setText("关 闭");
ui->comStatus->setAlignment(Qt::AlignCenter);
ui->statusbar->showMessage("串口已关闭");
}
}
/* 接收数据 */
void MainWindow::COM_DataReceive()
{
QByteArray data_buf; // 接收缓冲区
QByteArray DATA_BUF; // 接收缓冲区(用于数据解析)
QString data_str; // 定义一个字符变量
data_buf = COM->readAll(); //接收数据存储到缓存区
if( !data_buf.isEmpty() ) // 如果接收到数据
{
// 解析数据
if(ui->checkBox_frDisplay->isChecked())
{
ui->statusbar->showMessage("解析数据中...");
serialProcessor->processReceivedData(data_buf);
}
else
{
ui->statusbar->showMessage("接收数据中...");
}
data_str = tr(data_buf); //将接收数据从字节数据类型转为字符类型,用于显示
if(ui->checkBox_receive_Hex->isChecked())
{
QString data_hex = data_buf.toHex().data();
data_hex = data_hex.toUpper(); // 转化为大写
QString data_hex_str;
// 将data_hex按每两个字符一组进行分割
for(int i=0; i < data_hex.length(); i+=2)
{
QString str_temp = data_hex.mid(i, 2);
data_hex_str += str_temp;
data_hex_str += ' ';
}
if(isCapturing) // 捕获十六进制数据
{
// 异步写入(使用QtConcurrent防止阻塞UI)
QtConcurrent::run([this,data_buf,data_hex_str](){
QMutexLocker locker(&fileMutex);
QMutexLocker fileLocker(&fileMutex);
if(fileStream.device())
{
fileStream << data_hex_str;
totalCaptured += data_buf.size();
}
});
}
if(ui->checkBox_rcDisplay->isChecked()) // 显示数据
{
ui->plainTextEdit->insertPlainText(data_hex_str); // 在文本框显示
ui->plainTextEdit->moveCursor(QTextCursor::End); // 光标移到最后一行
}
}
else
{
if(isCapturing) // 捕获字符数据
{
// 异步写入(使用QtConcurrent防止阻塞UI)
QtConcurrent::run([this,data_buf,data_str](){
QMutexLocker locker(&fileMutex);
QMutexLocker fileLocker(&fileMutex);
if(fileStream.device())
{
fileStream << data_str;
totalCaptured += data_buf.size();
}
});
}
if(ui->checkBox_rcDisplay->isChecked()) // 显示数据
{
ui->plainTextEdit->insertPlainText(data_str); // 在文本框显示
ui->plainTextEdit->moveCursor(QTextCursor::End); // 光标移到最后一行
}
}
}
// 自动清理旧内容
int lineCount = ui->plainTextEdit->document()->lineCount();
if (lineCount > MAX_DISPLAY_LINES) {
ui->plainTextEdit->clear();
QTextCursor trimCursor(ui->plainTextEdit->document());
trimCursor.setPosition(0);
trimCursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor,
lineCount - MAX_DISPLAY_LINES/2);
trimCursor.removeSelectedText();
}
receiveNum += data_buf.size();
ui->receiveBytes->setText(QString::number(receiveNum));
ui->receiveBytes->setAlignment(Qt::AlignCenter);
// 捕获数据时,底部状态栏显示信息
if(isCapturing)
{
QString msg = QString("已捕获 %1 字节").arg(totalCaptured);
ui->statusbar->showMessage("正在捕获数据到:" + FilePath + " " + msg);
}
}
/* 清空接收 */
void MainWindow::on_Key_receive_COM_clear_clicked()
{
ui->plainTextEdit->clear();
}
/* 保存数据 */
void MainWindow::on_Key_save_COM_clicked()
{
// 第四个参数不写就表示所有文件
QString filename = QFileDialog::getSaveFileName(this, "选择保存位置",
QCoreApplication::applicationFilePath(), ".txt");
if(filename.isEmpty())
{
//QMessageBox::warning(this, "警告", "请选择一个文件夹");
}
else
{
QFile file(filename);
file.open(QIODevice::WriteOnly);
//ui->textEdit->toPlainText();
QByteArray ba;
// 把QString接在QByreArray后面,此时ba没东西,相当于将QByteArray转换成了QString
ba.append(ui->plainTextEdit->toPlainText().toLatin1());
file.write(ba);
file.close();
}
}
/* 给串口号下拉框添加事件过滤器,鼠标点击时刷新串口 */
bool MainWindow::eventFilter(QObject* object, QEvent* event)
{
if(event->type() == QEvent::MouseButtonPress)
{
if(object == ui->comboBox_COMnumber)
{
QComboBox* combobox = qobject_cast<QComboBox* >(object);
// combobox->clear();
// combobox->addItem("list");
combobox->clear();
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
ui->comboBox_COMnumber->addItem(info.portName());
}
}
}
return QWidget::eventFilter(object,event);
}
/* 清空计数 */
void MainWindow::on_Key_clearBytes_clicked()
{
receiveNum = 0;
sendNum = 0;
ui->receiveBytes->setText(QString::number(receiveNum));
ui->receiveBytes->setAlignment(Qt::AlignCenter);
ui->sendBytes->setText(QString::number(sendNum));
ui->sendBytes->setAlignment(Qt::AlignCenter);
}
void MainWindow::String2Hex(QString str, QByteArray &senddata)
{
int hexdata,lowhexdata;
int hexdatalen = 0;
int len = str.length();
senddata.resize(len/2);
char lstr,hstr;
for(int i=0; i<len; )
{
//char lstr,
hstr=str[i].toLatin1();
if(hstr == ' ')
{
i++;
continue;
}
i++;
if(i >= len)
break;
lstr = str[i].toLatin1();
hexdata = ConvertHexChar(hstr);
lowhexdata = ConvertHexChar(lstr);
if((hexdata == 16) || (lowhexdata == 16))
break;
else
hexdata = hexdata*16+lowhexdata;
i++;
senddata[hexdatalen] = (char)hexdata;
hexdatalen++;
}
senddata.resize(hexdatalen);
}
char MainWindow::ConvertHexChar(char ch)
{
if((ch >= '0') && (ch <= '9'))
return ch-0x30;
else if((ch >= 'A') && (ch <= 'F'))
return ch-'A'+10;
else if((ch >= 'a') && (ch <= 'f'))
return ch-'a'+10;
else return (-1);
}
/* 定时发送 */
void MainWindow::on_checkBox_timeSend_clicked()
{
int state = ui->checkBox_timeSend->checkState();
if(state == Qt::Checked) // 勾选
{
if(ui->lineEdit->text()==NULL)
{
QMessageBox::about(this, tr("提示"), tr("请设置时间间隔"));
ui->checkBox_timeSend->setCheckState(Qt::Unchecked);
}
else
{
// 勾选定时发送时,发送按键无法选中
ui->Key_send_COM->setEnabled(false);
ui->lineEdit->setEnabled(false);
int time=ui->lineEdit->text().toInt();
timeSend = new QTimer();
timeSend->setInterval(time);
connect(timeSend, &QTimer::timeout, this, [=](){on_Key_send_COM_clicked();});
timeSend->start();
}
}
else // 取消勾选
{
timeSend->stop();
ui->Key_send_COM->setEnabled(true);
ui->lineEdit->setEnabled(true);
}
}
/* 捕获数据 */
void MainWindow::on_Key_capture_COM_clicked()
{
if(!isCapturing)
{
// 首次点击:选择文件开始捕获
QString fileName = QFileDialog::getSaveFileName(this, tr("选择保存文件"), QCoreApplication::applicationFilePath(), "");
FilePath = fileName;
if(!fileName.isEmpty())
{
startCapture(fileName);
}
}
else
{
// 再次点击:停止捕获
stopCapture();
}
}
/* 开始捕获 */
void MainWindow::startCapture(const QString &fileName)
{
captureFile.setFileName(fileName);
if(!captureFile.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::critical(this, "错误", "无法创建文件!");
return;
}
isCapturing = true;
fileStream.setDevice(&captureFile);
captureTimer.start();
totalCaptured = 0;
ui->Key_capture_COM->setText("停止捕获");
ui->Key_capture_COM->setStyleSheet("background:lightcoral");
// ui->statusbar->showMessage("正在捕获数据到:" + fileName);
}
/* 停止捕获 */
void MainWindow::stopCapture()
{
if(captureFile.isOpen())
{
fileStream.flush();
captureFile.close();
}
isCapturing = false;
qint64 elapsed = captureTimer.elapsed();
QString msg = QString("数据捕获完成!共捕获 %1 字节,耗时 %2 秒").arg(totalCaptured).arg(elapsed/1000.0, 0, 'f', 1);
ui->Key_capture_COM->setText("捕获数据");
ui->Key_capture_COM->setStyleSheet("");
ui->statusbar->showMessage(msg);
}
/* Ui界面初始化 */
void MainWindow::UiInit()
{
this->setWindowTitle("Sport串口调试助手");
// ui->groupBox->setStyleSheet("border:1px solid gray");
// ui->groupBox_2->setStyleSheet("border:1px solid gray");
// ui->groupBox_3->setStyleSheet("border:1px solid gray");
// ui->groupBox_4->setStyleSheet("border:1px solid gray");
ui->label->setStyleSheet("border:none");
ui->label_2->setStyleSheet("border:none");
ui->label_3->setStyleSheet("border:none");
ui->label_4->setStyleSheet("border:none");
ui->label_5->setStyleSheet("border:none");
ui->label_6->setStyleSheet("border:none");
ui->label_7->setStyleSheet("border:none");
ui->label_8->setStyleSheet("border:none");
ui->label_9->setStyleSheet("border:none");
ui->checkBox_receive_Hex->setStyleSheet("border:none");
ui->checkBox_send_Hex->setStyleSheet("border:none");
ui->checkBox_timeSend->setStyleSheet("border:none");
ui->Key_open_COM->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
ui->Key_clearBytes->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
ui->Key_save_COM->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
ui->Key_send_COM->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
ui->Key_send_COM_clear->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
ui->Key_receive_COM_clear->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
ui->Key_send_COM->setEnabled(false);
ui->checkBox_timeSend->setEnabled(false);
ui->lineEdit->setEnabled(false);
receiveNum = 0;
sendNum = 0;
ui->receiveBytes->setText(QString::number(receiveNum));
ui->receiveBytes->setAlignment(Qt::AlignCenter);
ui->sendBytes->setText(QString::number(sendNum));
ui->sendBytes->setAlignment(Qt::AlignCenter);
ui->comboBox_COMnumber->installEventFilter(this);
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{ // 添加串口名
ui->comboBox_COMnumber->addItem(info.portName());
}
foreach(qint32 baud, QSerialPortInfo::standardBaudRates())
{ // 添加波特率
ui->comboBox_baudRate->addItem(QString::number(baud));
}
ui->comboBox_baudRate->addItem("230400");
ui->comboBox_baudRate->addItem("460800");
ui->comboBox_baudRate->addItem("921600");
ui->comboBox_baudRate->setCurrentText("460800");
}
/* 数据解析槽函数 */
void MainWindow::onFrameProcessed()
{
frameDisplay();
}
/* 校验失败槽函数 */
void MainWindow::onChecksumError()
{
ui->statusbar->showMessage("校验出现错误!");
}
/* 更新显示解析数据 */
void MainWindow::frameDisplay()
{
DataFrame frame = serialProcessor->currentFrame();
// 字节序转换,将数据从小端字节序转换为当前系统的字节序(默认下位机使用小端字节序)
double insLat = serialProcessor->combinePosition(qFromLittleEndian(frame.Lat1), qFromLittleEndian(frame.Lat2));
double insLon = serialProcessor->combinePosition(qFromLittleEndian(frame.Lon1), qFromLittleEndian(frame.Lon2));
double gpsLat = serialProcessor->combinePosition(qFromLittleEndian(frame.GpsLat1), qFromLittleEndian(frame.GpsLat2));
double gpsLon = serialProcessor->combinePosition(qFromLittleEndian(frame.GpsLon1), qFromLittleEndian(frame.GpsLon2));
ui->label_Gx->setText(QString::number(qFromLittleEndian(frame.Gx), 'f', 4));
ui->label_Gy->setText(QString::number(qFromLittleEndian(frame.Gy), 'f', 4));
ui->label_Gz->setText(QString::number(qFromLittleEndian(frame.Gz), 'f', 4));
ui->label_Ax->setText(QString::number(qFromLittleEndian(frame.Ax), 'f', 4));
ui->label_Ay->setText(QString::number(qFromLittleEndian(frame.Ay), 'f', 4));
ui->label_Az->setText(QString::number(qFromLittleEndian(frame.Az), 'f', 4));
ui->label_Mx->setText(QString::number(qFromLittleEndian(frame.Mx), 'f', 4));
ui->label_My->setText(QString::number(qFromLittleEndian(frame.My), 'f', 4));
ui->label_Mz->setText(QString::number(qFromLittleEndian(frame.Mz), 'f', 4));
ui->label_InsLon->setText(QString::number(insLon, 'f', 6));
ui->label_InsLat->setText(QString::number(insLat, 'f', 6));
ui->label_InsHgt->setText(QString::number(qFromLittleEndian(frame.Hgt), 'f', 6));
ui->label_VE->setText(QString::number(qFromLittleEndian(frame.VE), 'f', 4));
ui->label_VN->setText(QString::number(qFromLittleEndian(frame.VN), 'f', 4));
ui->label_VU->setText(QString::number(qFromLittleEndian(frame.VU), 'f', 4));
ui->label_GpsLon->setText(QString::number(gpsLon, 'f', 6));
ui->label_GpsLat->setText(QString::number(gpsLat, 'f', 6));
ui->label_GpsHgt->setText(QString::number(qFromLittleEndian(frame.GpsHgt), 'f', 6));
ui->label_GpsVE->setText(QString::number(qFromLittleEndian(frame.GpsVE), 'f', 4));
ui->label_GpsVN->setText(QString::number(qFromLittleEndian(frame.GpsVN), 'f', 4));
ui->label_GpsVU->setText(QString::number(qFromLittleEndian(frame.GpsVU), 'f', 4));
ui->label_Pitch->setText(QString::number(qFromLittleEndian(frame.Pitch), 'f', 4));
ui->label_Roll->setText(QString::number(qFromLittleEndian(frame.Roll), 'f', 4));
ui->label_Yaw->setText(QString::number(qFromLittleEndian(frame.Yaw), 'f', 4));
ui->label_BaroHgt->setText(QString::number(qFromLittleEndian(frame.BaroHgt), 'f', 2));
ui->label_Temp->setText(QString::number(qFromLittleEndian(frame.Temp), 'f', 1));
//ui->label_TimeStamp->setText(QString::number(qFromLittleEndian(frame.TimeStamp), 'f', 1));
}
/* tabBar点击切换页面槽函数 */
void MainWindow::on_tabWidget_tabBarClicked(int index)
{
switch(index)
{
case 0:
ui->checkBox_rcDisplay->setChecked(true);
ui->checkBox_frDisplay->setChecked(false);
break;
case 1:
ui->checkBox_rcDisplay->setChecked(false);
ui->checkBox_frDisplay->setChecked(true);
break;
default:
break;
}
}
- serialprocessor.h
#ifndef SERIALPROCESSOR_H
#define SERIALPROCESSOR_H
#include <QObject>
#include <cstdint> // 包含标准整数类型定义
#include <QtEndian>
struct DataFrame{
uint8_t Head;
uint32_t TimeStamp;
float Gx, Gy, Gz;
float Ax, Ay, Az;
float Mx, My, Mz;
float BaroHgt;
float Pitch, Roll, Yaw;
float VE, VN, VU;
float Lat1, Lat2, Lon1, Lon2, Hgt; // 惯导位置
float GpsVE, GpsVN, GpsVU;
float GpsLat1, GpsLat2, GpsLon1, GpsLon2, GpsHgt;
float GpsStatus;
float GpsDelay;
float Temp;
uint32_t CheckSum;
};
class SerialProcessor : public QObject
{
Q_OBJECT
public:
explicit SerialProcessor(QObject *parent = nullptr);
// 处理接收到的原始串口数据
void processReceivedData(const QByteArray &data);
// 获取当前帧数据
DataFrame currentFrame() const;
// 合并整数部分和小数部分
double combinePosition(int32_t integer, uint32_t fraction) const;
signals:
// 当完整帧被解析时发出信号
void frameProcessed();
// 校验错误信号
void checksumError();
private:
// 处理一帧数据
void frameProcess(const QByteArray &frameData);
// 计算CRC32校验和
uint32_t calculateCRC32(const char *data, int length) const;
QByteArray rxBuff; //接收数据缓冲区
DataFrame m_currentFrame; // 当前解析的帧数据
};
#endif // SERIALPROCESSOR_H
- serialprocessor.cpp
#include "serialprocessor.h"
#include <QDebug>
SerialProcessor::SerialProcessor(QObject *parent) : QObject(parent)
{
// 初始化数据帧
memset(&m_currentFrame, 0, sizeof(DataFrame));
}
void SerialProcessor::processReceivedData(const QByteArray &data)
{
rxBuff.append(data); // 先将数据复制到缓冲区
// 检测到的第一个帧头位于已接收到的数据中的位置
int frameStart = -1;
// 帧处理循环
while(static_cast<size_t>(rxBuff.size()) >= sizeof(DataFrame))
{
for(int i = 0; i<= rxBuff.size() - static_cast<int>(sizeof(DataFrame)); i++)
{
if(static_cast<uint8_t>(rxBuff[i]) == 0xAA &&
static_cast<uint8_t>(rxBuff[i+1]) == 0x55 &&
static_cast<uint8_t>(rxBuff[i+2]) == 0xAA &&
static_cast<uint8_t>(rxBuff[i+3]) == 0x56)
{
frameStart = i;
break;
}
}
// 找到有效帧头
if(frameStart >=0)
{
// 检查是否包含完整一帧
if(frameStart + sizeof(DataFrame) <= static_cast<size_t>(rxBuff.size()))
{
// 提取帧数据
QByteArray frameData = rxBuff.mid(frameStart, sizeof(DataFrame));
// 处理帧数据
frameProcess(frameData);
// 从缓冲区移除已处理数据
rxBuff.remove(0, frameStart + sizeof(DataFrame));
}
else
{
// 数据不足一帧,等待更多数据
// 移除帧头前的无用数据后跳出while循环,
// 下一次满足进入while循环的数据正好是一帧完整数据。
rxBuff.remove(0, frameStart);
break;
}
}
else
{
// 未找到帧头,消除无效数据
int removeCount = rxBuff.size() - sizeof(DataFrame) + 1;
if(removeCount > 0 && removeCount <= rxBuff.size())
{
rxBuff.remove(0, removeCount);
}
else
{
// 如果没有足够数据,只保留可能构成帧头的部分
if(rxBuff.size() > 4)
{
rxBuff.remove(0, rxBuff.size() - 4);
}
break;
}
}
}
}
void SerialProcessor::frameProcess(const QByteArray &frameData)
{
const DataFrame* frame = reinterpret_cast<const DataFrame*>(frameData.constData());
/* 校验和功能不可用
// 计算校验和(跳过帧头的4个字节)
uint32_t calculatedChecksum = calculateCRC32(frameData.constData() + 4, sizeof(DataFrame) - 8);
uint32_t receivedChecksum = qFromLittleEndian(frame->CheckSum);
// 验证校验和
if(calculatedChecksum != receivedChecksum)
{
qWarning() << "校验和错误!接收:" << receivedChecksum << "计算:" << calculatedChecksum;
emit checksumError();
return;
}
*/
// 拷贝帧数据
memcpy(&m_currentFrame, frame, sizeof(DataFrame));
// 发出帧处理完成信号
emit frameProcessed();
}
double SerialProcessor::combinePosition(int32_t integer, uint32_t fraction) const
{
return integer + static_cast<double>(fraction) / 100000000.0;
}
DataFrame SerialProcessor::currentFrame() const
{
return m_currentFrame;
}
uint32_t SerialProcessor::calculateCRC32(const char* data, int length) const
{
// 简单的CRC32实现(实际应用中应使用更健壮的算法)
uint32_t crc = 0xFFFFFFFF;
for(int i = 0; i < length; i++)
{
crc ^= static_cast<uint8_t>(data[i]);
for(int j = 0; j < 8; j++)
{
if(crc & 1)
{
crc = (crc >> 1) ^ 0xEDB88320;
}
else
{
crc >>= 1;
}
}
}
return ~crc;
}
- mainwindow.ui

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