4 Star 37 Fork 15

求索er/QT串口采数软件

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
mainwindow.cpp 46.37 KB
一键复制 编辑 原始数据 按行查看 历史
求索er 提交于 2022-07-07 13:53 . 提交源码
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "quc/lightbutton.h"
#include "quc/switchbutton.h"
#include <QPoint>
#include <QDebug>
#include <QToolBox>
#include <QMessageBox>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QMetaType> //用来注册数据类型
#include<QElapsedTimer> //测量函数运行时间
QElapsedTimer debugTime;
/**************************************************** 主界面 *********************************************************/
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow) //构造主界面
{
frameLen = 0; // 帧信息
frameHeaderLen = 0;
frameDataTypeLen = 0;
checkSumPosOnframeDataType = -1;
TimeStampPosOnframeDataType = -1;
recv_frameCounter = 0;
XAxis = frameCount; // X轴绘图类型:帧计数/时间戳
curveRefreshCycle = 1; // 曲线刷新周期 10ms(默认)
clearCurveTiming_3min = 0;
timingPeriod10ms = 10;
timing10msCounter= 0;
isOpenSerialPort = false; // 串口打开
isSaveTxtFile = false; // 文件保存
isOpenCurvePlot = false; // 绘图
isStartCurvePlot = false; // 开始绘图
isOpenDashBoard = false; // 仪表盘
isOpenHelpUi = false; // 帮助面板
qRegisterMetaType<QVector<quint8>>("QVector<quint8>"); //注册信号与槽的传递参数类型
ui->setupUi(this);
this->setFixedSize(this->width(),this->height());
// qDebug() << "UI线程id" <<QThread::currentThreadId();
/**************************************** 选择面板 ******************************************/
//数据协议面板(默认显示)
ui->toolBox->setCurrentIndex(0);
ui->confirmDataFrame_btn->setChecked(false);
//串口设置面板
ui->setBaud_combox->setCurrentIndex(1);
ui->setDataBits_combox->setCurrentIndex(0);
ui->setParity_combox->setCurrentIndex(0);
ui->setStopBits_combox->setCurrentIndex(0);
ui->switchSerialPort_btn->setChecked(false);
ui->serialPortStauts_led->setBgColor(QColor("#6f7a7e"));
//串口线程
serialPortModule = new mySerialPort();
serialPortSubThread = new QThread();
serialPortModule->moveToThread(serialPortSubThread);
serialPortSubThread->start();
connect(serialPortSubThread,&QThread::started,serialPortModule,&mySerialPort::serialPortInfoInit); //线程初始化后 新建串口对象并初始化信息
connect(ui->setPort_combox,&mySerialCombox::detectSerialPorts,serialPortModule,&mySerialPort::updateAvailableSerialPorts); //点击串口号下拉框自动检测
connect(serialPortModule,&mySerialPort::sendAvailableSerialPortName,ui->setPort_combox,&mySerialCombox::updateSerialComboxItem); //检测后更新串口号
connect(ui->switchSerialPort_btn,&SwitchButton::checkedChanged,this,&MainWindow::slot_switchSerialPort); //打开串口
connect(this,&MainWindow::signal_openSerialPortx,serialPortModule,&mySerialPort::openSerialPortx); //绑定串口打开信号
connect(this,&MainWindow::signal_closeSerialPortx,serialPortModule,&mySerialPort::closeSerialPortx); //绑定关闭串口信号
connect(serialPortModule,&mySerialPort::isSerialPortxOpenSuccessfully,this,&MainWindow::slot_showSerialPortxOpenStatus); //显示串口打开状态
connect(serialPortModule,&mySerialPort::isSerialPortxCloseSuccessfully,this,&MainWindow::slot_showSerialPortxCloseStauts); //显示串口关闭状态
connect(this,&MainWindow::signal_sendFrameInfoToSerialSubThread,serialPortModule,&mySerialPort::slot_recvFrameInfoFromUiThread); //同步帧信息
// connect(serialPortModule,&mySerialPort::signal_sendFrameInfoToUiThread,ui->label_showRecvFrameNum,&QLabel::setText); //显示总帧数
//绘图功能
curvePlot = new myCurvePlot();
ui->curveShow_btn->setCheckable(true);
connect(curvePlot,&myCurvePlot::signal_clearCurvePlotUi,this,[=](){clearCurveTiming_3min = 0;});
connect(curvePlot,&myCurvePlot::signal_StartCurvePlot,this,[=](bool on){isStartCurvePlot = on;});
connect(curvePlot,&myCurvePlot::signal_hideCurvePlotUi,this,[=](){ui->curveShow_btn->setChecked(false);});
connect(curvePlot,&myCurvePlot::signal_sendCurveRefreshFreq,this,[=](int freq){curveRefreshCycle = 1000 / freq / timingPeriod10ms;});
//仪表盘
attDashBoard = new myDashBoard();
ui->dashboardShow_btn->setCheckable(true);
connect(attDashBoard,&myDashBoard::signal_hideDashBoardUi,this,[=](){ui->dashboardShow_btn->setChecked(false);});
//帮助面板
helpInfo = new help();
connect(helpInfo,&help::signal_hideHelpUi,this,[=](){isOpenHelpUi = false;});
//主线程定时器 10ms 任务调度
runTimer = new QTimer(this);
runTimer->setInterval(timingPeriod10ms); //10ms
connect(runTimer,&QTimer::timeout,this,&MainWindow::slot_taskScheduler);
/****************************************** 表格 *******************************************/
QStringList headerText;
headerText << "名称/Save" << "数据类型" << "数值 / Divisor" << "图1" << "图2" << "图3";
ui->tableWidget->setColumnCount(headerText.count()); //列数
ui->tableWidget->setHorizontalHeaderLabels(headerText); //行表头名
ui->tableWidget->verticalHeader()->setVisible(true); //表头
ui->tableWidget->horizontalHeader()->setVisible(true);
ui->tableWidget->setColumnWidth(colName,150); //列宽
ui->tableWidget->setColumnWidth(colType,141);
ui->tableWidget->setColumnWidth(colData,150);
ui->tableWidget->setColumnWidth(colIsCurve,33);
ui->tableWidget->setColumnWidth(colIsCurve1,33);
ui->tableWidget->setColumnWidth(colIsCurve2,33);
ui->tableWidget->setAlternatingRowColors(true); //隔行换色
ui->tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); //滚动条
ui->tableWidget->setItemDelegateForColumn(colType,&comBoxDelegate); //委托
// ui->tableWidget->setStyleSheet("QTableWidget::item:selected{background:lightblue}");
connect(ui->tableWidget,&QTableWidget::itemClicked,this,&MainWindow::slot_clickedItemAction);
ui->tableWidget->insertRow(0);
creatItemsARow(0,"Frame_Header","uint8_t(hex)","0xAA");
}
MainWindow::~MainWindow() //主界面析构
{
/* 结束子线程 并删除 不可使用deletelater(该函数是基于主线程事件循环的,在析构函数调用时,主线程的线程循环已经被终止了!) */
serialPortSubThread->quit();
if(serialPortSubThread->wait(5) == false){
serialPortSubThread->terminate();
}
delete serialPortSubThread;
delete serialPortModule;
delete helpInfo;
delete curvePlot;
delete attDashBoard;
// qDebug() << "主界面被销毁" <<endl;
delete ui;
}
void MainWindow::closeEvent(QCloseEvent *event) //重写主界面关闭事件
{
if(ui->switchSerialPort_btn->getChecked())
{
QMessageBox::critical(this,"@!!!!","请先关闭串口!");
event->ignore();
}
else if(isSaveTxtFile)
{
QMessageBox::critical(this,"@!!!!","请先关闭数据捕获功能!");
event->ignore();
}
else
{
event->accept();
}
}
/*************************************************** 其它设置 *********************************************************/
void MainWindow::on_comboBox_setEndianMode_currentIndexChanged(int index) //设置大小端模式
{
CV.updateEndianMode(index);
}
void MainWindow::on_comboBox_setXAxis_currentIndexChanged(int index) //设置曲线X轴
{
if(index == 0){
XAxis = frameCount;
}else if(index == 1){
XAxis = TimeStamp;
if(TimeStampPosOnframeDataType < 0){
QMessageBox::critical(this,"@!!!!","未定义数据戳或未确认数据帧");
ui->comboBox_setXAxis->setCurrentIndex(0);
XAxis = frameCount;
}
}else{
XAxis = frameCount;
}
}
void MainWindow::slot_clickedItemAction() //曲线绘制复选框点击事件
{
// currentItemIndex = ui->tableWidget->currentIndex(); //获取当前坐标
//绘图复选框
// QVector<quint8> isPlotXyz,isPlotxYz,isPlotxyZ;
isPlotXyz.clear();isPlotxYz.clear();isPlotxyZ.clear();
for(quint16 i=0; i<ui->tableWidget->rowCount(); i++){
if(ui->tableWidget->item(i,colIsCurve )->checkState() == Qt::Checked){ isPlotXyz.append(i); }
if(ui->tableWidget->item(i,colIsCurve1)->checkState() == Qt::Checked){ isPlotxYz.append(i); }
if(ui->tableWidget->item(i,colIsCurve2)->checkState() == Qt::Checked){ isPlotxyZ.append(i); }
}
if(isPlotXyz.size() >= 3){ isEnableCurvePlotCheckBox(isPlotXyz,colIsCurve,false); }//失能除选中之外的所有复选框
else{ isEnableCurvePlotCheckBox(isPlotXyz,colIsCurve,true); } //使能所有复选框
if(isPlotxYz.size() >= 3){ isEnableCurvePlotCheckBox(isPlotxYz,colIsCurve1,false); }//失能除选中之外的所有复选框
else{ isEnableCurvePlotCheckBox(isPlotxYz,colIsCurve1,true); } //使能所有复选框
if(isPlotxyZ.size() >= 3){ isEnableCurvePlotCheckBox(isPlotxyZ,colIsCurve2,false); }//失能除选中之外的所有复选框
else{ isEnableCurvePlotCheckBox(isPlotxyZ,colIsCurve2,true); } //使能所有复选框
}
void MainWindow::detectAllCurvePlotCheckBox() //检测曲线绘制复选框
{
// 清除
for(uint8_t i=0; i<3; i++){
xIsPlot1.clear(); xIsPlot2.clear(); xIsPlot3.clear();
xIsPlot1Name.clear(); xIsPlot2Name.clear(); xIsPlot3Name.clear();
xPlotKeys.clear();
xPlot1Value[i].clear(); xPlot2Value[i].clear(); xPlot3Value[i].clear();
}
// 遍历状态:是否需要曲线绘制
for(quint16 i=0; i<ui->tableWidget->rowCount(); i++)
{
//绘图1 一共3条
if(ui->tableWidget->item(i,colIsCurve)->checkState() == Qt::Checked){
xIsPlot1.append(i - frameHeaderLen); //对应了数据类型的位置
xIsPlot1Name.append( ui->tableWidget->item(i,colName)->text() ); //画图时的图例
}
//绘图2 一共3条
if(ui->tableWidget->item(i,colIsCurve1)->checkState() == Qt::Checked){
xIsPlot2.append(i - frameHeaderLen); //对应了数据类型的位置
xIsPlot2Name.append( ui->tableWidget->item(i,colName)->text() ); //画图时的图例
}
//绘图2 一共3条
if(ui->tableWidget->item(i,colIsCurve2)->checkState() == Qt::Checked){
xIsPlot3.append(i - frameHeaderLen); //对应了数据类型的位置
xIsPlot3Name.append( ui->tableWidget->item(i,colName)->text() ); //画图时的图例
}
}
}
void MainWindow::isEnableCurvePlotCheckBox(QVector<quint8> &plotXxx, MainWindow::FieldColNum colIsCurvex, bool checked) //使/失能曲线绘制复选框
{
quint8 len = 0;
if(plotXxx.empty())//全部修改为使能或失能
{
for(quint8 i=0; i<ui->tableWidget->rowCount(); i++)
{
Qt::ItemFlags f = ui->tableWidget->item(i,colIsCurvex)->flags();
if(checked){ ui->tableWidget->item(i,colIsCurvex)->setFlags(f | Qt::ItemIsEnabled); }//使能复选框
else{ ui->tableWidget->item(i,colIsCurvex)->setFlags(f &~ Qt::ItemIsEnabled); } //失能复选框
}
}
else
{
for(quint8 i=0; i<ui->tableWidget->rowCount(); i++)
{
if( i == plotXxx.at(len)){
len++;
if(len >= plotXxx.size()) len = plotXxx.size()-1; //防止索引越界
}else{
Qt::ItemFlags f = ui->tableWidget->item(i,colIsCurvex)->flags();
if(checked){ ui->tableWidget->item(i,colIsCurvex)->setFlags(f | Qt::ItemIsEnabled); }//使能复选框
else{ ui->tableWidget->item(i,colIsCurvex)->setFlags(f &~ Qt::ItemIsEnabled); } //失能复选框
}
}
}
}
void MainWindow::detectEulerAnglesToDashBoard() //检测是否有欧拉角,有则显示到仪表盘
{
pitchPosOnframeDataType = -1;
rollPosOnframeDataType = -1;
yawPosOnframeDataType = -1;
QString strEuler;
// 遍历名称:是否含欧拉角的字符串
for(quint16 i=0; i<ui->tableWidget->rowCount(); i++)
{
//绘图1 一共3条
strEuler = ui->tableWidget->item(i,colName)->text().toLower();
if( strEuler == "pitch" ) pitchPosOnframeDataType = i - frameHeaderLen;
else if( strEuler == "roll") rollPosOnframeDataType = i - frameHeaderLen;
else if( strEuler == "yaw" ) yawPosOnframeDataType = i - frameHeaderLen;
}
// qDebug() << pitchPosOnframeDataType << rollPosOnframeDataType << yawPosOnframeDataType;
}
/*************************************************** 任务调度 *********************************************************/
void MainWindow::slot_taskScheduler() //任务调度器
{
// debugTime.start();
timing10msCounter++;
quint8 yPacket = parseOrSaveDataTask(); //10ms 解数据-存文件
if(timing10msCounter % 100 == 0) //1s 计时 (不费时 放最开头)
{
QTime t = QTime::fromMSecsSinceStartOfDay(timing10msCounter*10);
QString str = t.toString("hh:mm:ss");
ui->label_showTiming->setText(str);
}
if(timing10msCounter % 50 == 0) //500ms 更新表格
{
ui->label_showRecvFrameNum->setText(QString::number(recv_frameCounter));
refreshTableTask(yPacket);
}
if(timing10msCounter % 25 == 0) //250ms 更新仪表盘
{
if(isOpenDashBoard && !outStr.empty() )
{
bool ok = false;
float eulerTmp[3] = {0.0f};
if(pitchPosOnframeDataType != -1) eulerTmp[0] = outStr.at(pitchPosOnframeDataType).toFloat(&ok);
if(rollPosOnframeDataType != -1) eulerTmp[1] = outStr.at(rollPosOnframeDataType).toFloat(&ok);
if(yawPosOnframeDataType != -1) eulerTmp[2] = outStr.at(yawPosOnframeDataType).toFloat(&ok);
attDashBoard->setValue(eulerTmp[0],eulerTmp[1],eulerTmp[2]);
}
}
if(isOpenCurvePlot) //10ms 刷新波形
{
if(yPacket == 0) return;
xIsPlotPacketCount = recv_frameCounter - yPacket + 1;
//根据选中的个数存数据(最多九条曲线) 根据选中状态抽数据 与解包周期一致
for(quint8 i=0; i<yPacket; ++i)
{
for(quint8 m=0; m<xIsPlot1.size(); m++){
xPlot1Value[m].append(outStr.at( xIsPlot1.at(m) + i*frameDataTypeLen ).toDouble());
}
for(quint8 m=0; m<xIsPlot2.size(); m++){
xPlot2Value[m].append(outStr.at( xIsPlot2.at(m) + i*frameDataTypeLen ).toDouble());
}
for(quint8 m=0; m<xIsPlot3.size(); m++){
xPlot3Value[m].append(outStr.at( xIsPlot3.at(m) + i*frameDataTypeLen ).toDouble());
}
if(XAxis == TimeStamp){ // 横轴为时间戳
xPlotKeys.append(outStr.at( TimeStampPosOnframeDataType + i*frameDataTypeLen ).toDouble());
}else{ // 横轴为帧计数
xPlotKeys.append(xIsPlotPacketCount); xIsPlotPacketCount++;
}
}
if( (timing10msCounter % curveRefreshCycle == 0) && isStartCurvePlot )
{
curvePlot->plot1_AddData(xPlotKeys,xPlot1Value[0],xPlot1Value[1],xPlot1Value[2],xIsPlot1Name);
curvePlot->plot2_AddData(xPlotKeys,xPlot2Value[0],xPlot2Value[1],xPlot2Value[2],xIsPlot2Name);
curvePlot->plot3_AddData(xPlotKeys,xPlot3Value[0],xPlot3Value[1],xPlot3Value[2],xIsPlot3Name);
xPlotKeys.clear();
for(uint8_t i=0; i<3; ++i){
xPlot1Value[i].clear();xPlot2Value[i].clear();xPlot3Value[i].clear();
}
}
else if(!isStartCurvePlot) //暂停显示
{
if(xPlotKeys.size() > 30000) //若下位机10ms发送一帧数据,则30000对应5min的数据 暂停了5分钟则自动清空
{
xPlotKeys.clear();
for(uint8_t i=0; i<3; ++i){
xPlot1Value[i].clear();xPlot2Value[i].clear();xPlot3Value[i].clear();
}
}
}
}
/* 5分钟清空一次绘图 */
if( clearCurveTiming_3min == 30000 )
{
// clearCurveTiming_3min = 0;
curvePlot->clearAllCurve_clicked();
}
clearCurveTiming_3min++;
outStr.clear();
// int debugTimeMs = debugTime.elapsed();
// qDebug() << debugTimeMs;
}
quint8 MainWindow::parseOrSaveDataTask() //解析并保存数据
{
// 线程资源同步
rwLock.lockForRead();
while (!threadBuffer.isEmpty())
recvThreadDataBuffer.append( threadBuffer.dequeue() ); //读写锁同时到达 写锁优先级最高 所以一定为帧长的整数倍(包括帧头)
rwLock.unlock();
quint32 packetLen = recvThreadDataBuffer.size();
if( packetLen == 0) return 0; //有数据才解包
QString str;
quint16 xPacket = packetLen/frameLen;
quint8* pBuf = recvThreadDataBuffer.data(); // 容器首地址 也就是帧头地址
for(quint16 m=0; m<xPacket; ++m) // 解m包
{
pBuf += frameHeaderLen; // 跳过帧头(没必要再判一次帧头了吧......)
for(quint16 i=0; i<frameDataTypeLen; ++i) // 解一包中的数据(类型)
{
quint8 tmp = frameDataType.at(i);
switch(tmp)
{
case 0:{ //myChar 需与mydatatype的值对应!!!
qint8 tmp0 = qint8(*pBuf); pBuf += 1;
str = QString::number((int)tmp0);
} break;
case 1:{ //myUint8_t
quint8 tmp1 = quint8(*pBuf); pBuf += 1;
str = QString::number((uint)tmp1);
} break;
case 2:{ //myShort
qint16 tmp2 = CV.func_2bytesToShort(pBuf); pBuf += 2;
str = QString::number((int)tmp2);
} break;
case 3:{ //myUint16_t
quint16 tmp3 = CV.func_2bytesToUint16(pBuf); pBuf += 2;
str = QString::number((uint)tmp3);
} break;
case 4:{ //myInt
qint32 tmp4 = CV.func_4bytesToInt(pBuf); pBuf += 4;
str = QString::number((int)tmp4);
} break;
case 5:{ //myUint32_t
quint32 tmp5 = CV.func_4bytesToUint32(pBuf); pBuf += 4;
str = QString::number((uint)tmp5);
} break;
case 6:{ //myFloat
float tmp6 = CV.func_4bytesToFloat(pBuf); pBuf += 4;
str = QString::number((double)tmp6,'g',7); //float最大有效位数7位
} break;
case 7:{ //myDouble
double tmp7 = CV.func_8bytesToDouble(pBuf); pBuf += 8;
str = QString::number((double)tmp7,'g',15); //double最大有效位数15位
} break;
case 8:{ //_3bytesToInt
qint32 tmp8 = CV.func_3bytesToInt(pBuf); pBuf += 3;
str = QString::number((int)tmp8);
} break;
case 9:{ //_2bytesToShort ? _2bytesToFloat
qint32 tmp9 = CV.func_2bytesToInt(pBuf); pBuf += 2;
str = QString::number((int)tmp9);
} break;
}
outStr.append(str); //存起来(没存帧头) ①保存到文件 ②更新到表格
}
recv_frameCounter++; //接收帧总计数
}
if(isSaveTxtFile) // 使能了文件保存
{
//解析数据保存为文本
QTextStream txtOut(&txtFile);
for(quint16 n=0; n<outStr.size(); ++n)
{
if( (n+1)%frameDataTypeLen == 0 )
{
txtOut << outStr.at(n) + "\n"; //全保存
// for(uint8_t i=0; i<saveXDataTypeToTxt.size(); ++i) //按选中保存逻辑还未实现
// {
// txtOut << "%"+frameDataName.at( saveXDataTypeToTxt.at(i) )+" ";
// }
}
else
{
txtOut << outStr.at(n) + " "; //全保存
}
}
//原始数据保存为文本
uint num = 0;
QString strHex;
QTextStream txtHexOut(&txtFileHex);
for(quint16 m=0; m<packetLen; ++m)
{
num = recvThreadDataBuffer.at(m);
if( num > 15){
strHex = QString::number(num,16).toUpper();
}else{
strHex = "0" + QString::number(num,16).toUpper();
}
if( (m+1)%frameLen == 0 ) {
txtHexOut << strHex + "\n"; //一帧的最后一个字节 补换行
}else{
txtHexOut << strHex + " ";
}
}
}
recvThreadDataBuffer.clear(); //保存完原始数据清空
return xPacket;
}
void MainWindow::refreshTableTask(quint8 xPacket) //刷新表格数据显示
{
if(xPacket == 0) return;
quint16 tmp = (xPacket - 1) * frameDataTypeLen; // 只取最后一包数据显示在表格内
for(quint16 i=frameHeaderLen,cnt = tmp; i<ui->tableWidget->rowCount(); i++,cnt++)
{
ui->tableWidget->item(i,colData)->setData(Qt::DisplayRole,outStr.at(cnt));
}
}
/**************************************************** 协议帧 *********************************************************/
void MainWindow::creatItemsARow(int curRow, QString name, QString type, QString data, QString Curve)
{
QFont font;
font.setFamily("Times New Roman");
font.setPointSize(13);
QTableWidgetItem *item;
//名称
item = new QTableWidgetItem(name,ctName);
ui->tableWidget->setItem(curRow,colName,item);
item->setFont(font); item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
if( (name == "Frame_Header") || (name == "Frame_Tail") || (name == "Check_Sum") || (name == "Time_Stamp")) // 固定名称不可编辑
{
item->setFlags( item->flags() & (~Qt::ItemIsEditable));
}
if( (name != "Frame_Header") && (name != "Frame_Tail") && (name != "Check_Sum")) //名称前加复选框 用于保存
{
item->setCheckState(Qt::Checked);
}
//数据类型
item = new QTableWidgetItem(type,ctType);
ui->tableWidget->setItem(curRow,colType,item);
item->setFont(font); item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
if( (name == "Frame_Header") || (name == "Frame_Tail")) // 固定名称不可编辑
{
item->setFlags( item->flags() & (~Qt::ItemIsEditable));
}
//数据
item = new QTableWidgetItem(data,ctData);
ui->tableWidget->setItem(curRow,colData,item);
item->setFont(font); item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
//绘图1
item = new QTableWidgetItem(Curve,ctIsCurve);
ui->tableWidget->setItem(curRow,colIsCurve,item);
if( (name == "Frame_Header") || (name == "Frame_Tail") || (name == "Check_Sum")){ // 固定名称不可绘图
item->setText("--");
item->setFlags(Qt::NoItemFlags);
}else{
item->setCheckState(Qt::Unchecked);
item->setFlags(item->flags() & ~Qt::ItemIsEditable & ~Qt::ItemIsSelectable);
}
item->setFont(font);
//绘图2
item = new QTableWidgetItem(Curve,ctIsCurve1);
ui->tableWidget->setItem(curRow,colIsCurve1,item);
if( (name == "Frame_Header") || (name == "Frame_Tail") || (name == "Check_Sum")){ // 固定名称不可绘图
item->setText("--");
item->setFlags(Qt::NoItemFlags);
}else{
item->setCheckState(Qt::Unchecked);
item->setFlags(item->flags()&~Qt::ItemIsEditable & ~Qt::ItemIsSelectable); //不可选中 不可编辑
}
item->setFont(font);
//绘图3
item = new QTableWidgetItem(Curve,ctIsCurve2);
ui->tableWidget->setItem(curRow,colIsCurve2,item);
if( (name == "Frame_Header") || (name == "Frame_Tail") || (name == "Check_Sum")){ // 固定名称不可绘图
item->setText("--");
item->setFlags(Qt::NoItemFlags);
}else{
item->setCheckState(Qt::Unchecked);
item->setFlags(item->flags()&~Qt::ItemIsEditable & ~Qt::ItemIsSelectable); //不可选中 不可编辑
}
item->setFont(font);
}
void MainWindow::on_confirmDataFrame_btn_clicked(bool checked) //确认数据帧
{
if(checked)
{
ui->confirmDataFrame_btn->setText("编辑数据帧");
/* 失能所有按键 失能表格编辑功能并遍历元素 */
isEnabledAllFrameBtn(false);
ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
bool ok;
int tmp = 0;
QString str;
frameLen = 0;
frameHeaderLen = 0;
frameDataTypeLen = 0;
checkSumPosOnframeDataType = -1; //默认没有校验和
TimeStampPosOnframeDataType = -1; //默认没有时间戳
frameTailNum.clear();
frameDataType.clear();
frameDataName.clear();
frameHeaderNum.clear();
dataTypeDivisorFactor.clear();
// 遍历表格所有行
for(uint8_t i=0; i<ui->tableWidget->rowCount(); i++)
{
str = ui->tableWidget->item(i,colName)->text();
// 帧头
if( str == "Frame_Header")
{
tmp = ui->tableWidget->item(i,colData)->text().toInt(&ok,16);
if((tmp > 255) || (ok == false)){
QMessageBox::critical(this,"@!!!!","帧头值设置错误..........");return;
}
frameHeaderNum.append( (uint8_t)tmp );
frameLen += 1;
}
// 帧尾 (用处不大)
else if( str == "Frame_Tail")
{
tmp = ui->tableWidget->item(i,colData)->text().toInt(&ok,16);
if((tmp > 255) || (ok == false)){
QMessageBox::critical(this,"@!!!!","帧尾值设置错误..........");return;
}
frameTailNum.append( (uint8_t)tmp );
frameLen += 1;
/* 检验帧尾是否处于最末端 */
if( i != (ui->tableWidget->rowCount()-1) ){
QMessageBox::critical(this,"@!!!!","帧尾不在末端?..........");return;
}
}
// 数据区(时间戳 + 数据 + 校验)
else
{
//将数据区的所有名字保存,用于作存储数据时的表头
frameDataName.append(str);
// 时间戳
if( str == "Time_Stamp" )
{
str = ui->tableWidget->item(i,colType)->text();
if (str == myDataTypeList.at(1)) { frameLen += 1; frameDataType.append(myUint8_t );}
else if(str == myDataTypeList.at(3)) { frameLen += 2; frameDataType.append(myUint16_t);}
else if(str == myDataTypeList.at(5)) { frameLen += 4; frameDataType.append(myUint32_t);}
else if(str == myDataTypeList.at(6)) { frameLen += 4; frameDataType.append(myFloat); }
else {
QMessageBox::critical(this,"@!!!!","时间戳数据类型不支持......");return;
}
TimeStampPosOnframeDataType = frameDataType.count() - 1; //时间戳在数据区中的位置
// qDebug() << "时间戳在数据区中的位置" << TimeStampPosOnframeDataType;
}
// 和校验
else if( str == "Check_Sum")
{
str = ui->tableWidget->item(i,colType)->text();
if (str == myDataTypeList.at(1)) { frameLen += 1; frameDataType.append(myUint8_t );}
// else if(str == myDataTypeList.at(3)) { frameLen += 2; frameDataType.append(myUint16_t);}
else if(str == myDataTypeList.at(5)) { frameLen += 4; frameDataType.append(myUint32_t);}
else {
QMessageBox::critical(this,"@!!!!","和校验数据类型不支持......");return;
}
checkSumPosOnframeDataType = frameDataType.count() - 1; //校验和在数据区中的位置
// qDebug() << "校验和在数据区中的位置" << checkSumPosOnframeDataType;
}
// 数据
else
{
str = ui->tableWidget->item(i,colType)->text();
if(str == myDataTypeList.at(0)) { frameDataType.append(myChar); frameLen += 1;}
else if(str == myDataTypeList.at(1)) { frameDataType.append(myUint8_t); frameLen += 1;}
else if(str == myDataTypeList.at(2)) { frameDataType.append(myShort); frameLen += 2;}
else if(str == myDataTypeList.at(3)) { frameDataType.append(myUint16_t);frameLen += 2;}
else if(str == myDataTypeList.at(4)) { frameDataType.append(myInt); frameLen += 4;}
else if(str == myDataTypeList.at(5)) { frameDataType.append(myUint32_t);frameLen += 4;}
else if(str == myDataTypeList.at(6)) { frameDataType.append(myFloat); frameLen += 4;}
else if(str == myDataTypeList.at(7)) { frameDataType.append(myDouble); frameLen += 8;}
else if(str == myDataTypeList.at(8)) //一般的数据手册 都会除以一个比例尺 这里对应dataTypeDivisorFactor
{
frameDataType.append(_3bytesToInt);
// tmp = ui->tableWidget->item(i,colData)->text().toInt(&ok,10);
// if((tmp > 32) || (tmp < -32) || (ok == false)){
// QMessageBox::critical(this,"@!!!!","_3bytesToInt类型的除数设置错误");return;
// }
// dataTypeDivisorFactor.append(tmp);
frameLen += 3;
}
else if(str == myDataTypeList.at(9))
{
frameDataType.append(_2bytesToInt);
// tmp = ui->tableWidget->item(i,colData)->text().toInt(&ok,10);
// if((tmp > 32) || (tmp < -32) || (ok == false)){
// QMessageBox::critical(this,"@!!!!","_2bytesToInt类型的除数设置错误");return;
// }
// dataTypeDivisorFactor.append(tmp);
frameLen += 2;
}
}
}
}
frameHeaderLen = frameHeaderNum.size();
frameDataTypeLen = frameDataType.size();
ui->label_showFrameLen->setText(QString::number(frameLen));
emit signal_sendFrameInfoToSerialSubThread(frameLen,frameHeaderNum);
/***************************************************帧信息*******************************************************/
// qDebug() << "帧长" << frameLen << "帧头长度" << frameHeaderLen << "数据区长度" << frameDataTypeLen;
// qDebug() << "帧头值";
// for(uint8_t i=0; i<frameHeaderNum.size(); ++i)
// {
// qDebug() << frameHeaderNum.at(i);
// }
// qDebug() << "数据名" << "数据类型";
// for(uint8_t i=0; i<frameDataName.size(); ++i)
// {
// qDebug() << frameDataName.at(i) << frameDataType.at(i);
// }
// qDebug() << "校验位在数据区的位置" << checkSumPosOnframeDataType << "校验类型" << frameDataType.at(checkSumPosOnframeDataType);
}
else
{
ui->confirmDataFrame_btn->setText("确认数据帧");
/* 使能所有按键 使能表格编辑功能 */
isEnabledAllFrameBtn(true);
ui->tableWidget->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked);
}
}
void MainWindow::isEnabledAllFrameBtn(bool checked) //使/失能协议帧相关按键
{
ui->addRow_btn->setEnabled(checked);
ui->deleteRow_btn->setEnabled(checked);
ui->insertRow_btn->setEnabled(checked);
ui->loadDataFrame_btn->setEnabled(checked);
ui->clearDataFrame_btn->setEnabled(checked);
ui->insertCheckSum_btn->setEnabled(checked);
ui->insertTimeStamp_btn->setEnabled(checked);
ui->insertFrameHeader_btn->setEnabled(checked);
// ui->insertFrameTail_btn->setEnabled(checked); //不失能插入帧尾
// ui->saveDataFrame_btn->setEnabled(checked);
}
void MainWindow::on_insertRow_btn_clicked()
{
int curRow = ui->tableWidget->currentRow();
ui->tableWidget->insertRow(curRow);
creatItemsARow(curRow);
}
void MainWindow::on_addRow_btn_clicked()
{
int curRow = ui->tableWidget->rowCount();
ui->tableWidget->insertRow(curRow);
creatItemsARow(curRow);
}
void MainWindow::on_insertFrameHeader_btn_clicked()
{
int curRow = ui->tableWidget->currentRow();
ui->tableWidget->insertRow(curRow);
creatItemsARow(curRow,"Frame_Header","uint8_t(hex)","<keyboard Input>");
}
void MainWindow::on_insertFrameTail_btn_clicked()
{
int curRow = ui->tableWidget->rowCount();
ui->tableWidget->insertRow(curRow);
creatItemsARow(curRow,"Frame_Tail","uint8_t(hex)","<keyboard Input>");
}
void MainWindow::on_insertTimeStamp_btn_clicked()
{
int curRow = ui->tableWidget->currentRow();
ui->tableWidget->insertRow(curRow);
creatItemsARow(curRow,"Time_Stamp");
}
void MainWindow::on_insertCheckSum_btn_clicked()
{
int curRow = ui->tableWidget->currentRow();
ui->tableWidget->insertRow(curRow);
creatItemsARow(curRow,"Check_Sum");
}
void MainWindow::on_deleteRow_btn_clicked()
{
int curRow = ui->tableWidget->currentRow();
ui->tableWidget->removeRow(curRow);
}
void MainWindow::on_clearDataFrame_btn_clicked() //清除所有数据帧
{
for(int i = (ui->tableWidget->rowCount()-1); i>=0; i--)
{
ui->tableWidget->removeRow(i);
}
}
/***************************************************** 串口 *********************************************************/
void MainWindow::slot_switchSerialPort(bool checked) //打开/关闭串口
{
//无论什么操作均将队列清空
threadBuffer.clear();
xPlotKeys.clear();
for(uint8_t i=0; i<3; ++i){
xPlot1Value[i].clear();xPlot2Value[i].clear();xPlot3Value[i].clear();
}
if(checked)//打开串口
{
QString portxName = ui->setPort_combox->currentText();
qint32 portxBaudrate = ui->setBaud_combox->currentText().toInt();
qint8 portxDataBits = ui->setDataBits_combox->currentText().toUShort();
qint8 portxStopBits = ui->setStopBits_combox->currentIndex();
qint8 portxParity = ui->setParity_combox->currentIndex();
switch(portxStopBits)
{
case 0: portxStopBits = 1; break; // 1停止位
case 1: portxStopBits = 3; break; // 1.5
case 2: portxStopBits = 2; break; // 2
default:portxStopBits = 1;
}
switch(portxParity)
{
case 0: portxParity = 0; break; //无校验
case 1: portxParity = 2; break; //偶校验
case 2: portxParity = 3; break; //奇
default:portxParity = 0;
}
emit signal_openSerialPortx(portxName,portxBaudrate,portxDataBits,portxParity,portxStopBits);
}
else //关闭串口
{
runTimer->stop(); //关闭串口停止计时
emit signal_closeSerialPortx();
ui->curveShow_btn->setChecked(false); //关闭串口-->关闭曲线Ui
ui->dashboardShow_btn->setChecked(false); //关闭串口-->关闭仪表盘Ui
// if(isSaveTxtFile){ } //关闭串口-->关闭数据捕获功能??
}
}
void MainWindow::slot_showSerialPortxOpenStatus(bool checked) //显示串口打开状态
{
if(checked) {
isEnabledSerialPortPanelBtn(false);
ui->serialPortStauts_led->setRed();
outStr.clear();
timing10msCounter = 0;
recv_frameCounter = 0;
runTimer->start(); //打开成功开始计时
}else{
isEnabledSerialPortPanelBtn(true);
ui->serialPortStauts_led->setBgColor(QColor("#6f7a7e"));
ui->switchSerialPort_btn->setChecked(false);
QMessageBox::critical(this,"@!!!!","串口打开失败或已被占用......");
}
}
void MainWindow::slot_showSerialPortxCloseStauts(bool checked) //显示串口关闭状态
{
if(checked) {
isEnabledSerialPortPanelBtn(true);
ui->serialPortStauts_led->setBgColor(QColor("#6f7a7e"));
}else{
isEnabledSerialPortPanelBtn(false);
ui->serialPortStauts_led->setRed();
QMessageBox::critical(this,"@!!!!","串口关闭失败......");
}
}
void MainWindow::isEnabledSerialPortPanelBtn(bool checked) //使/失能串口相关按钮
{
ui->setPort_combox->setEnabled(checked);
ui->setBaud_combox->setEnabled(checked);
ui->setParity_combox->setEnabled(checked);
ui->setDataBits_combox->setEnabled(checked);
ui->setStopBits_combox->setEnabled(checked);
ui->confirmDataFrame_btn->setEnabled(checked); //打开串口后 数据帧面板使/失能
ui->comboBox_setEndianMode->setEnabled(checked); //打开串口后 大小端模式使/失能
}
/***************************************************** 文件 *********************************************************/
void MainWindow::on_saveData_btn_clicked() //捕获数据
{
if(!isSaveTxtFile)
{
/* 建立一个子文件夹 用于存放txt文件 */
QString curPath = QDir::currentPath();
QDir dir(curPath);
QString savePath = curPath+ "/txtFile";
if(!dir.exists(savePath)) {
dir.mkdir("txtFile");
}
QString dlgTitle = tr("另存为");
// QString filter = "文本文件(*.txt);;所有文件(*.)";
QString filter = "文本文件(*.txt)";
QString saveFilename = QFileDialog::getSaveFileName(this,dlgTitle,savePath,filter);
if (saveFilename.isEmpty())
{
ui->saveData_btn->setChecked(false);
return ;
}
// saveXDataTypeToTxt.clear();
// // 遍历状态:该数据类型是否需要保存到txt
// for(quint16 i=0; i<ui->tableWidget->rowCount(); i++)
// {
// if(ui->tableWidget->item(i,colName)->checkState() == Qt::Checked){
// saveXDataTypeToTxt.append(ui->tableWidget->row(ui->tableWidget->item(i,colName)) - frameHeaderLen); //对应了数据类型的位置
// }
// }
txtFile.setFileName(saveFilename); //解析数据
txtFileHex.setFileName(saveFilename.remove(".txt") + "Hex.txt"); //原始数据
QTextStream txtOut(&txtFile);
// QTextStream txtHexOut(&txtFileHex); //这里只打开,不存数据
if(txtFile.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate )) //如果文件存在则打开,不存在创建并打开 打开后在原有内容后开始写入
{
ui->saveData_btn->setText("停止捕获");
ui->saveData_btn->setStyleSheet("QPushButton{color:rgb(170,0,0,255);}");
for(uint8_t i=0; i<frameDataName.size(); ++i)
{
txtOut << "%"+frameDataName.at(i)+" ";
}
txtOut << "\n";
// //按选中保存数据
// for(uint8_t i=0; i<saveXDataTypeToTxt.size(); ++i)
// {
// txtOut << "%"+frameDataName.at( saveXDataTypeToTxt.at(i) )+" ";
// }
// txtOut << "\n";
isSaveTxtFile = true;
ui->label_showFileInfo->setText("数据正在保存.........");
}
if(txtFileHex.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate )) //如果文件存在则打开,不存在创建并打开 打开后在原有内容后开始写入
{
ui->label_showFileInfo->setText("数据正在保存......!!!");
}
}
else{
int ret = QMessageBox::question(this, "question","确定要停止捕获数据???",QMessageBox::Yes|QMessageBox::No,QMessageBox::No);
if(ret == QMessageBox::Yes)
{
txtFile.close();
txtFileHex.close();
isSaveTxtFile = false;
ui->saveData_btn->setText("捕获数据");
ui->saveData_btn->setStyleSheet("QPushButton{color:rgb(0,0,0,255);}");
ui->label_showFileInfo->setText("数据保存成功.........");
}
}
}
void MainWindow::on_saveDataFrame_btn_clicked() //保存数据帧配置
{
/* 建立一个子文件夹 用于存放文件 */
QString curPath = QDir::currentPath();
QDir dir(curPath);
QString savePath = curPath+ "/Config";
if(!dir.exists(savePath)) {
dir.mkdir("Config");
}
QString dlgTitle = tr("另存为");
QString filter = "文本文件(*.txt);;所有文件(*.)";
QString saveFilename = QFileDialog::getSaveFileName(this,dlgTitle,savePath,filter);
if (saveFilename.isEmpty())
return ;
/* 写文件 */
QFile configFile(saveFilename);
if(configFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) //如果文件存在则打开,不存在创建并打开,打开后清空原有数据
{
//遍历表格
QString str;
QTextStream out(&configFile);
for(uint8_t i=0; i<ui->tableWidget->rowCount(); i++)
{
str = ui->tableWidget->item(i,colName)->text(); out << str << ",";
str = ui->tableWidget->item(i,colType)->text(); out << str << ",";
str = ui->tableWidget->item(i,colData)->text(); out << str << "\n";
}
configFile.close();
ui->label_showFileInfo->setText("数据协议帧已保存.........");
}
}
void MainWindow::on_loadDataFrame_btn_clicked() //加载数据帧配置
{
/* 建立一个子文件夹 用于存放文件 */
QString curPath = QDir::currentPath();
QDir dir(curPath);
QString savePath = curPath+ "/Config";
if(!dir.exists(savePath)) {
dir.mkdir("Config");
}
QString dlgTitle = tr("加载配置文件");
QString filter = "文本文件(*.txt);;所有文件(*.)";
QString loadFilename = QFileDialog::getOpenFileName(this,dlgTitle,savePath,filter);
if (loadFilename.isEmpty())
return ;
/* 写文件 */
QFile configFile(loadFilename);
if(configFile.open(QIODevice::ReadOnly | QIODevice::Text))
{
//遍历表格
QString strLine;
QTextStream in(&configFile);
/* 先清空表格 */
this->on_clearDataFrame_btn_clicked();
for(uint8_t i=0; in.atEnd() == false; i++)
{
strLine = in.readLine();
QStringList strList = strLine.split(",");
/* 添加表格 */
int curRow = ui->tableWidget->rowCount();
ui->tableWidget->insertRow(curRow);
this->creatItemsARow(i,strList.at(0),strList.at(1),strList.at(2));
}
configFile.close();
ui->label_showFileInfo->setText("数据协议帧加载成功.........");
}
}
/****************************************************** UI *********************************************************/
void MainWindow::on_curveShow_btn_toggled(bool checked) //曲线UI
{
QVector<quint8> VecEmpty;
//打开曲线显示 清除原有图像
if(checked){
if(!isOpenCurvePlot)
{
detectAllCurvePlotCheckBox();
curvePlot->clearAllCurve_clicked();
curvePlot->move(this->pos().x()+200,this->pos().y()+66);
curvePlot->resize(800,510);
curvePlot->show();
isStartCurvePlot = true;
isOpenCurvePlot = true;
curvePlot->resetAllBtn();
ui->comboBox_setXAxis->setEnabled(false); //X轴属性不可调节
isEnableCurvePlotCheckBox( VecEmpty,colIsCurve ,false); //失能所有复选框
isEnableCurvePlotCheckBox( VecEmpty,colIsCurve1,false); //失能所有复选框
isEnableCurvePlotCheckBox( VecEmpty,colIsCurve2,false); //失能所有复选框
}
}else{
if(isOpenCurvePlot)
{
// qDebug() << "关闭绘图显示";
isOpenCurvePlot = false;
isStartCurvePlot = false;
curvePlot->clearAllCurve_clicked();
curvePlot->close();
ui->comboBox_setXAxis->setEnabled(true); //X轴属性可调节
isEnableCurvePlotCheckBox( VecEmpty,colIsCurve ,true); //使能所有复选框
isEnableCurvePlotCheckBox( VecEmpty,colIsCurve1,true); //使能所有复选框
isEnableCurvePlotCheckBox( VecEmpty,colIsCurve2,true); //使能所有复选框
if(isPlotXyz.size() >= 3) isEnableCurvePlotCheckBox(isPlotXyz,colIsCurve,false); //失能除选中之外的所有复选框
if(isPlotxYz.size() >= 3) isEnableCurvePlotCheckBox(isPlotxYz,colIsCurve1,false); //失能除选中之外的所有复选框
if(isPlotxyZ.size() >= 3) isEnableCurvePlotCheckBox(isPlotxyZ,colIsCurve2,false); //失能除选中之外的所有复选框
}
}
}
void MainWindow::on_dashboardShow_btn_toggled(bool checked) //仪表盘UI
{
if(checked)
{
if(!isOpenDashBoard)
{
detectEulerAnglesToDashBoard();
attDashBoard->move(this->pos().x()+200,this->pos().y()+150);
attDashBoard->show();
attDashBoard->setValue(10.5,0.5,0.5);
isOpenDashBoard = true;
}
}
else
{
if(isOpenDashBoard)
{
// qDebug() << "关闭仪表盘显示";
attDashBoard->close();
isOpenDashBoard = false;
}
}
}
void MainWindow::on_showHelp_btn_clicked() //帮助UI
{
if(!isOpenHelpUi){
helpInfo->show();
isOpenHelpUi = true;
}else{
helpInfo->close();
isOpenHelpUi = false;
}
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C++
1
https://gitee.com/zhu_hong_bao/qt-serial-port.git
git@gitee.com:zhu_hong_bao/qt-serial-port.git
zhu_hong_bao
qt-serial-port
QT串口采数软件
master

搜索帮助

23e8dbc6 1850385 7e0993f3 1850385