• Qt串口基本设置与协议收发


    2023.10.23更新

    2023.9.18更新

    增加了一个PID调参的模块

    PID算法之前在验证电机时是可用的

    由于我没有实物可演示PID,所以写了个虚拟的PID,故调参效果看起来怪怪的

    不过重点不在PID调参,而是命令的下发与接收

    其他方面跟之前更新的波形显示没什么区别

    Qt上位机PID调参_哔哩哔哩_bilibili

    Qt上位机PID调参


    2023.9.17更新

    支持开始显示、停止显示

    波形数据写到TXT文件

    Excel、Python或MATLAB对数据进行进一步地分析

    1. QVector waveArray;
    2. QDateTime curTime = QDateTime::currentDateTime();
    3. QString date = curTime.toString("yyyy-MM-dd"); //设置显示格式
    4. QString time = curTime.toString("hh:mm:ss:zzz"); //设置显示格式
    5. waveArray<"\t"<"\t"<number(receiveParsingData)<<"\n";

    每接受一帧数据则对数据进行保存 

    1. void MainWindow::waveDataOutputToFile()
    2. {
    3. QFileDialog dlg(this);
    4. //获取内容的保存路径
    5. QString fileName = dlg.getSaveFileName(this, tr("Save As"), "./", tr("Text File(*.txt)"));
    6. if( fileName == "" )
    7. {
    8. return;
    9. }
    10. //内容保存到路径文件
    11. QFile file(fileName);
    12. //以文本方式打开
    13. if( file.open(QIODevice::WriteOnly | QIODevice::Text) )
    14. {
    15. QTextStream out(&file); //IO设备对象的地址对其进行初始化
    16. QVector::iterator iter;
    17. for (iter=waveArray.begin();iter!=waveArray.end();iter++)
    18. out << *iter << "\0";
    19. QMessageBox::warning(this, tr("Finish"), tr("Successfully save the file!"));
    20. file.close();
    21. waveArray.clear();
    22. }
    23. else
    24. {
    25. QMessageBox::warning(this, tr("Error"), tr("File to open file!"));
    26. }
    27. }

    文件输出

    Qt上位机波形显示与数据保存_哔哩哔哩_bilibili

    Qt上位机波形显示与数据保存


    2023.9.15更新

    将单片机ADC采集到的电压进行组包

    Qt收到后进行拆包

    1. void MainWindow::parsingData(QByteArray *protocalData)
    2. {
    3. uchar preFix = 0xA5;
    4. uchar cmd1Rcv = 0xAB;
    5. uchar cmd2Rcv = 0x11;
    6. uchar crc = 0;
    7. uchar temp = 0;
    8. temp = static_cast(protocalData->at(0));
    9. if(static_cast(protocalData->at(0)) == preFix)
    10. {
    11. for(int i=1; ilength()-2; i++)
    12. {
    13. temp = static_cast(protocalData->at(i));
    14. crc += static_cast(protocalData->at(i));
    15. }
    16. temp = static_cast(protocalData->at(protocalData->length()-2));
    17. if(crc != static_cast(protocalData->at(protocalData->length()-2)))
    18. {
    19. return;
    20. }
    21. uchar cmd1 = protocalData->at(2);
    22. uchar cmd2 = protocalData->at(3);
    23. uchar data = protocalData->at(4);
    24. if((cmd1==cmd1Rcv) && (cmd2 ==cmd2Rcv))
    25. {
    26. uchar uData[4];
    27. uData[0] = static_cast(protocalData->at(4));
    28. uData[1] = static_cast(protocalData->at(5));
    29. uData[2] = static_cast(protocalData->at(6));
    30. uData[3] = static_cast(protocalData->at(7));
    31. *(int *)&receiveParsingData = *(int *)&uData;
    32. ui->lcdnumber_vlotage->display(receiveParsingData);
    33. }
    34. }
    35. }

    LCD显示当前电压

    Qt上位机显示动态数据_哔哩哔哩_bilibili

    Qt上位机显示动态数据


    2023.9.14更新

    从协议中进行数据截取

    波形显示

    graphicsView放大缩小、原始大小


    2023.9.12更新

    中文显示

    发送/接受 帧计数

    文件输出

    支持时间戳模式自动换行

    HEX接收时自动用空格分隔


    前言

    1.一直都想要做一个Qt上位机,趁着这个周末有时间,动手写一下

    2.comboBox没有点击的信号,所以做了一个触发的功能

    3.Qt的数据类型很奇怪,转来转去的我也搞得很迷糊

    4.给自己挖个坑,下一期做一个查看波形的上位机

    有纰漏请指出,转载请说明。

    学习交流请发邮件 1280253714@qq.com

    串口功能

    波特率设置

    串口开关

    串口异常检测

    字符串/HEX收发

    定时发送

    接收数据分隔

    协议组包

    协议拆包

    源代码

    widget.h

    1. #ifndef WIDGET_H
    2. #define WIDGET_H
    3. #include
    4. #include // 提供访问串口的功能
    5. #include // 提供系统中存在的串口信息
    6. #include
    7. #include
    8. #include
    9. QT_BEGIN_NAMESPACE
    10. namespace Ui { class Widget; }
    11. QT_END_NAMESPACE
    12. class Widget : public QWidget
    13. {
    14. Q_OBJECT
    15. //用于表格设置
    16. enum FieldColNum {
    17. colTime,
    18. colCmd1,
    19. colCmd2,
    20. colData,
    21. };
    22. enum CellType {
    23. ctTime,
    24. ctCmd1,
    25. ctCmd2,
    26. ctData,
    27. };
    28. public:
    29. int tableRowCnt = 0;
    30. public:
    31. Widget(QWidget *parent = nullptr);
    32. ~Widget();
    33. void serialPortInit(); //串口初始化
    34. void windowInit(); //显示窗口初始化
    35. void refreshCom(); //刷新串口
    36. void tableInit(); //表格初始化
    37. void createItemsARow(int rowNum, QByteArray *protocalData); //表格新建一行
    38. QString ByteArrayToHexString(QByteArray &ba);
    39. private:
    40. Ui::Widget *ui;
    41. QSerialPort* serialPort;
    42. public slots:
    43. void comboBoxClicked(); //comboBox被点击
    44. private slots:
    45. void sendData(); //发送串口数据
    46. void receiveData(); //接收串口数据
    47. void openSerialport(); //串口开启
    48. void closeSerialport(); //串口关闭
    49. void setBuad(int); //设置波特率
    50. void clearRcv(); //清楚接收缓存
    51. void on_btnConvert_clicked(); //转换按钮被点击
    52. void on_btnClear_clicked(); //清楚转化的按钮被点击
    53. void sendProtocalHexData(); //以hex格式发送串口数据
    54. void handleSerialError(QSerialPort::SerialPortError serialPortErr); //串口异常捕获
    55. };
    56. #endif // WIDGET_H

    widget.cpp

    1. #include "widget.h"
    2. #include "ui_widget.h"
    3. #include "newcombobox.h"
    4. #include
    5. Widget::Widget(QWidget *parent)
    6. : QWidget(parent)
    7. , ui(new Ui::Widget)
    8. {
    9. ui->setupUi(this);
    10. //创建一个定时器用来定时发送
    11. QTimer *timer1 = new QTimer();
    12. timer1->start(1000);
    13. connect(timer1,&QTimer::timeout,[=](){
    14. int timed = ui->comboBox_timedSend->currentText().toInt();
    15. timer1->start(timed);
    16. if(ui->checkBox_timedSend->isChecked() == true)
    17. {
    18. sendData();
    19. }
    20. });
    21. windowInit();
    22. tableInit();
    23. serialPort = new QSerialPort();
    24. serialPortInit();
    25. connect(serialPort,SIGNAL(readyRead()),this,SLOT(receiveData()));
    26. connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));
    27. connect(ui->pushButton_sendData,SIGNAL(clicked()),this,SLOT(sendData()));
    28. connect(ui->pushButton_openSerialPort,SIGNAL(clicked()),this,SLOT(openSerialport()));
    29. connect(ui->pushButton_closeSerialPort,SIGNAL(clicked()),this,SLOT(closeSerialport()));
    30. connect(ui->comboBox_chooseCom,SIGNAL(clicked()),this,SLOT(comboBoxClicked()));
    31. connect(ui->pushButton_clearRcv,SIGNAL(clicked()),this,SLOT(clearRcv()));
    32. connect(ui->pushButton_convert,SIGNAL(clicked()),this,SLOT(on_btnConvert_clicked()));
    33. connect(ui->pushButton_clearConvertData,SIGNAL(clicked()),this,SLOT(on_btnClear_clicked()));
    34. connect(ui->pushButton_sendProtocalData,SIGNAL(clicked()),this,SLOT(sendProtocalHexData()));
    35. connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));
    36. connect(ui->comboBox_setBuad,SIGNAL(activated(int)),this,SLOT(setBuad(int)));
    37. }
    38. Widget::~Widget()
    39. {
    40. delete ui;
    41. }
    42. //串口异常捕获
    43. void Widget::handleSerialError(QSerialPort::SerialPortError serialPortErr)
    44. {
    45. if(serialPortErr == QSerialPort::ResourceError)
    46. {
    47. QMessageBox::critical(NULL, "critical", "设备拔出", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
    48. closeSerialport();
    49. }
    50. if(serialPortErr == QSerialPort::DeviceNotFoundError)
    51. {
    52. QMessageBox::critical(NULL, "critical", "找不到串口", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
    53. closeSerialport();
    54. }
    55. }
    56. void Widget::comboBoxClicked()
    57. {
    58. refreshCom();
    59. }
    60. void Widget::windowInit()
    61. {
    62. ui->pushButton_closeSerialPort->setEnabled(false);
    63. ui->pushButton_openSerialPort->setEnabled(false);
    64. ui->pushButton_sendData->setEnabled(false);
    65. setWindowTitle(tr("串口收发"));
    66. ui->comboBox_timedSend->addItem("10");
    67. ui->comboBox_timedSend->addItem("100");
    68. ui->comboBox_timedSend->addItem("1000");
    69. ui->comboBox_timedSend->setCurrentIndex(2);
    70. }
    71. void Widget::refreshCom()
    72. {
    73. //显示串口列表
    74. ui->comboBox_chooseCom->clear();
    75. foreach(QSerialPortInfo portInfo, QSerialPortInfo::availablePorts())
    76. ui->comboBox_chooseCom->addItem(portInfo.portName()+":"+portInfo.description());
    77. ui->pushButton_openSerialPort->setEnabled(ui->comboBox_chooseCom->count()>0); //
    78. }
    79. void Widget::serialPortInit(){
    80. refreshCom();
    81. ui->comboBox_setBuad->addItem("9600");
    82. ui->comboBox_setBuad->addItem("115200");
    83. ui->comboBox_setBuad->addItem("921600");
    84. ui->comboBox_setBuad->setCurrentIndex(1);
    85. }
    86. void Widget::tableInit()
    87. {
    88. QStringList headerText;
    89. headerText<<"时间"<<"命令1"<<"命令2"<<"数据";
    90. ui->tableWidget->setColumnCount(headerText.size()); //设置表格列数
    91. ui->tableWidget->resizeColumnsToContents();
    92. for (int i=0;itableWidget->columnCount();i++)
    93. {
    94. QTableWidgetItem *headerItem=new QTableWidgetItem(headerText.at(i));
    95. QFont font=headerItem->font(); //获取原有字体设置
    96. font.setBold(true); //设置为粗体
    97. font.setPointSize(10); //字体大小
    98. headerItem->setForeground(QBrush(Qt::black)); //设置文字颜色
    99. headerItem->setFont(font); //设置字体
    100. ui->tableWidget->setHorizontalHeaderItem(i,headerItem); //设置表头单元格的item
    101. }
    102. ui->tableWidget->setColumnWidth(0, 60);
    103. ui->tableWidget->setColumnWidth(1, 50);
    104. ui->tableWidget->setColumnWidth(2, 50);
    105. ui->tableWidget->setColumnWidth(3, 250);
    106. }
    107. //为一行的单元格创建 Items
    108. void Widget::createItemsARow(int rowNum, QByteArray *protocalData)
    109. {
    110. uchar preFix = 0xA5;
    111. uchar crc = 0;
    112. uchar temp = 0;
    113. temp = static_cast(protocalData->at(0));
    114. if(static_cast(protocalData->at(0)) == preFix)
    115. {
    116. for(int i=1; ilength()-2; i++)
    117. {
    118. temp = static_cast(protocalData->at(i));
    119. crc += static_cast(protocalData->at(i));
    120. }
    121. temp = static_cast(protocalData->at(protocalData->length()-2));
    122. if(crc != static_cast(protocalData->at(protocalData->length()-2)))
    123. {
    124. return;
    125. }
    126. uchar len = protocalData->at(1);
    127. uchar cmd1 = protocalData->at(2);
    128. uchar cmd2 = protocalData->at(3);
    129. QByteArray data = protocalData->mid(4,len-6);
    130. QDateTime curTime = QDateTime::currentDateTime();//获取系统现在的时间
    131. QString time = curTime.toString("hh:mm:ss"); //设置显示格式
    132. uint8_t str1 = static_cast<uint8_t>(cmd1);
    133. QString hexStr1 = QString("%1").arg(str1, 2, 16, QLatin1Char('0')).toUpper();
    134. uint8_t str2 = static_cast<uint8_t>(cmd2);
    135. QString hexStr2 = QString("%1").arg(str2, 2, 16, QLatin1Char('0')).toUpper();
    136. QString testdata = ByteArrayToHexString(data).toLatin1().toUpper();
    137. QTableWidgetItem *item = new QTableWidgetItem(time, ctTime);
    138. ui->tableWidget->setItem(rowNum, colTime, item);
    139. item = new QTableWidgetItem(hexStr1, ctCmd1);
    140. ui->tableWidget->setItem(rowNum, colCmd1, item);
    141. item = new QTableWidgetItem(hexStr2, ctCmd2);
    142. ui->tableWidget->setItem(rowNum, colCmd2, item);
    143. item = new QTableWidgetItem(testdata, ctData);
    144. ui->tableWidget->setItem(rowNum, colData, item);
    145. }
    146. auto lastRowIndex = ui->tableWidget->rowCount()-1; // 最后一行的索引
    147. auto lastModelIndex = ui->tableWidget->model()->index(lastRowIndex, 0);
    148. ui->tableWidget->scrollTo(lastModelIndex); // 滚动到最后一行
    149. }
    150. QString Widget::ByteArrayToHexString(QByteArray &ba)
    151. {
    152. QDataStream out(&ba,QIODevice::ReadWrite); //将str的数据 读到out里面去
    153. QString buf;
    154. while(!out.atEnd())
    155. {
    156. qint8 outChar = 0;
    157. out >> outChar; //每次一个字节的填充到 outchar
    158. QString str = QString("%1").arg(outChar&0xFF,2,16,QLatin1Char('0')).toUpper() + QString(" "); //2 字符宽度
    159. buf += str;
    160. }
    161. return buf;
    162. }
    163. void Widget::sendData()
    164. {
    165. QString message = ui->lineEdit_sendData->text();
    166. if(ui->checkBox_hexSend->isChecked() == true)
    167. {
    168. serialPort->write(QByteArray::fromHex(message.toLatin1()));
    169. }
    170. else
    171. {
    172. serialPort->write(message.toLatin1());
    173. }
    174. }
    175. void Widget::receiveData()
    176. {
    177. QByteArray message;
    178. QString hexMsg;
    179. message.append(serialPort->readAll());
    180. QDateTime time = QDateTime::currentDateTime(); //获取系统现在的时间
    181. QString date = time.toString("hh:mm:ss"); //设置显示格式
    182. if(ui->checkBox_hexRcv->isChecked() == true)
    183. {
    184. tableRowCnt++;
    185. ui->tableWidget->setRowCount(tableRowCnt);
    186. createItemsARow(tableRowCnt-1,&message);
    187. hexMsg = ByteArrayToHexString(message).toLatin1();
    188. ui->textEdit_RecData->append(date+QString("-> ")+hexMsg.toUpper());
    189. }
    190. else
    191. {
    192. ui->textEdit_RecData->append(date+QString("-> ")+message);
    193. }
    194. }
    195. void Widget::openSerialport()
    196. {
    197. ui->pushButton_closeSerialPort->setEnabled(true);
    198. ui->pushButton_openSerialPort->setEnabled(false);
    199. QList comList = QSerialPortInfo::availablePorts();
    200. QSerialPortInfo portInfo = comList.at(ui->comboBox_chooseCom->currentIndex());
    201. serialPort->setPort(portInfo); //设置使用哪个串口
    202. if(serialPort->open(QIODevice::ReadWrite) == false)
    203. {
    204. QMessageBox::critical(NULL, "critical", "找不到串口/串口被占用", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
    205. closeSerialport();
    206. }
    207. else
    208. {
    209. serialPort->setBaudRate(QSerialPort::Baud115200);
    210. serialPort->setDataBits(QSerialPort::Data8);
    211. serialPort->setParity(QSerialPort::NoParity);
    212. serialPort->setStopBits(QSerialPort::OneStop);
    213. serialPort->setFlowControl(QSerialPort::NoFlowControl);
    214. ui->pushButton_sendData->setEnabled(true);
    215. }
    216. }
    217. void Widget::closeSerialport()
    218. {
    219. if(serialPort->isOpen()){
    220. serialPort->clear();
    221. serialPort->close();
    222. }
    223. ui->pushButton_closeSerialPort->setEnabled(false);
    224. ui->pushButton_openSerialPort->setEnabled(true);
    225. }
    226. void Widget::setBuad(int buad)
    227. {
    228. QString str = ui->comboBox_setBuad->currentText();
    229. serialPort->setBaudRate(str.toInt());
    230. }
    231. void Widget::clearRcv()
    232. {
    233. ui->textEdit_RecData->clear();
    234. }
    235. void Widget::on_btnClear_clicked()
    236. {
    237. ui->lineEdit_protocalData->clear();
    238. }
    239. void Widget::on_btnConvert_clicked()
    240. {
    241. ui->lineEdit_protocalData->clear();
    242. bool ok;
    243. QString str = "A5";
    244. int val1= ui->lineEditCmd1->text().toInt(&ok,16); //以十六进制数读入
    245. QString str1 = QString("%1").arg(val1, 2, 16, QLatin1Char('0'));
    246. int val2= ui->lineEditCmd2->text().toInt(&ok,16); //以十六进制数读入
    247. QString str2 = QString("%1").arg(val2, 2, 16, QLatin1Char('0'));
    248. if ((str1.isEmpty())||(str2.isEmpty()))
    249. return;
    250. int val3= ui->lineEditData->text().toInt(&ok,16); //以十六进制数读入
    251. QString str3 = QString("%1").arg(val3, 2, 16, QLatin1Char('0'));
    252. uint8_t len = 6 + static_cast<uint8_t>(str3.length()/2);
    253. QString hexStr = QString("%1").arg(len, 2, 16, QLatin1Char('0'));
    254. str.append(hexStr);
    255. str.append(str1);
    256. str.append(str2);
    257. str.append(str3);
    258. uint8_t crc;
    259. QString tmp;
    260. for(int i=0; ilength(); i+=2)
    261. {
    262. tmp = ui->lineEditData->text()[i];
    263. tmp += ui->lineEditData->text()[i+1];
    264. crc+= tmp.toInt(&ok,16);
    265. tmp = "";
    266. }
    267. crc += len;
    268. crc += val1;
    269. crc += val2;
    270. QString hexCrc= QString("%1").arg(crc, 2, 16, QLatin1Char('0'));
    271. str.append(hexCrc);
    272. str.append("5A");
    273. str = str.toUpper();
    274. ui->lineEdit_protocalData->insert(str);
    275. }
    276. void Widget::sendProtocalHexData()
    277. {
    278. QString message = ui->lineEdit_protocalData->text();
    279. serialPort->write(QByteArray::fromHex(message.toLatin1()));
    280. }

    ui

    代码框架概览

    演示视频

    串口上位机(基本设置/协议收发)演示_哔哩哔哩_bilibili

    串口上位机(基本设置/协议收发)演示

  • 相关阅读:
    Leetcode刷题167. 两数之和 II - 输入有序数组
    数据库技术基础--数据库的控制功能
    Python3,这应该是,使用最广泛的6个内置函数,别说你还不知道。
    达梦数据库的名词解释
    基于LSTM的天气预测 - 时间序列预测 计算机竞赛
    记一次Java内存泄漏最终导致内存溢出的事故分析
    python中的属性管理机制
    Ubuntu ARMv8编译Qt源码以及QtCreator
    教你如何在优麒麟上搭建 RISC-V 交叉编译环境
    12000条招聘数据告诉Python的学习方向和就业方向
  • 原文地址:https://blog.csdn.net/weixin_45817947/article/details/132795563