Qt串口基本设置与协议收发
前言
1.一直都想要做一个Qt上位机,趁着这个周末有时间,动手写一下
2.comboBox没有点击的信号,所以做了一个触发的功能
3.Qt的数据类型很奇怪,转来转去的我也搞得很迷糊
4.给自己挖个坑,下一期做一个查看波形的上位机
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com
串口功能
波特率设置
串口开关
串口异常检测
字符串/HEX收发
定时发送
接收数据分隔
协议组包
协议拆包
源代码
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QtSerialPort/QSerialPort> // 提供访问串口的功能
#include <QtSerialPort/QSerialPortInfo> // 提供系统中存在的串口信息
#include <QTime>
#include <QtCharts>
#include <QTimer>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECT//用于表格设置enum FieldColNum {colTime,colCmd1,colCmd2,colData,};enum CellType {ctTime,ctCmd1,ctCmd2,ctData,};
public:int tableRowCnt = 0;
public:Widget(QWidget *parent = nullptr);~Widget();void serialPortInit(); //串口初始化void windowInit(); //显示窗口初始化void refreshCom(); //刷新串口void tableInit(); //表格初始化void createItemsARow(int rowNum, QByteArray *protocalData); //表格新建一行QString ByteArrayToHexString(QByteArray &ba);
private:Ui::Widget *ui;QSerialPort* serialPort;
public slots:void comboBoxClicked(); //comboBox被点击
private slots:void sendData(); //发送串口数据void receiveData(); //接收串口数据void openSerialport(); //串口开启void closeSerialport(); //串口关闭void setBuad(int); //设置波特率void clearRcv(); //清楚接收缓存void on_btnConvert_clicked(); //转换按钮被点击void on_btnClear_clicked(); //清楚转化的按钮被点击void sendProtocalHexData(); //以hex格式发送串口数据void handleSerialError(QSerialPort::SerialPortError serialPortErr); //串口异常捕获
};#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "newcombobox.h"
#include <algorithm>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//创建一个定时器用来定时发送QTimer *timer1 = new QTimer();timer1->start(1000);connect(timer1,&QTimer::timeout,[=](){int timed = ui->comboBox_timedSend->currentText().toInt();timer1->start(timed);if(ui->checkBox_timedSend->isChecked() == true){sendData();}});windowInit();tableInit();serialPort = new QSerialPort();serialPortInit();connect(serialPort,SIGNAL(readyRead()),this,SLOT(receiveData()));connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));connect(ui->pushButton_sendData,SIGNAL(clicked()),this,SLOT(sendData()));connect(ui->pushButton_openSerialPort,SIGNAL(clicked()),this,SLOT(openSerialport()));connect(ui->pushButton_closeSerialPort,SIGNAL(clicked()),this,SLOT(closeSerialport()));connect(ui->comboBox_chooseCom,SIGNAL(clicked()),this,SLOT(comboBoxClicked()));connect(ui->pushButton_clearRcv,SIGNAL(clicked()),this,SLOT(clearRcv()));connect(ui->pushButton_convert,SIGNAL(clicked()),this,SLOT(on_btnConvert_clicked()));connect(ui->pushButton_clearConvertData,SIGNAL(clicked()),this,SLOT(on_btnClear_clicked()));connect(ui->pushButton_sendProtocalData,SIGNAL(clicked()),this,SLOT(sendProtocalHexData()));connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));connect(ui->comboBox_setBuad,SIGNAL(activated(int)),this,SLOT(setBuad(int)));
}Widget::~Widget()
{delete ui;
}//串口异常捕获
void Widget::handleSerialError(QSerialPort::SerialPortError serialPortErr)
{if(serialPortErr == QSerialPort::ResourceError){QMessageBox::critical(NULL, "critical", "设备拔出", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);closeSerialport();}if(serialPortErr == QSerialPort::DeviceNotFoundError){QMessageBox::critical(NULL, "critical", "找不到串口", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);closeSerialport();}
}void Widget::comboBoxClicked()
{refreshCom();
}void Widget::windowInit()
{ui->pushButton_closeSerialPort->setEnabled(false);ui->pushButton_openSerialPort->setEnabled(false);ui->pushButton_sendData->setEnabled(false);setWindowTitle(tr("串口收发"));ui->comboBox_timedSend->addItem("10");ui->comboBox_timedSend->addItem("100");ui->comboBox_timedSend->addItem("1000");ui->comboBox_timedSend->setCurrentIndex(2);
}void Widget::refreshCom()
{//显示串口列表ui->comboBox_chooseCom->clear();foreach(QSerialPortInfo portInfo, QSerialPortInfo::availablePorts())ui->comboBox_chooseCom->addItem(portInfo.portName()+":"+portInfo.description());ui->pushButton_openSerialPort->setEnabled(ui->comboBox_chooseCom->count()>0); //
}void Widget::serialPortInit(){refreshCom();ui->comboBox_setBuad->addItem("9600");ui->comboBox_setBuad->addItem("115200");ui->comboBox_setBuad->addItem("921600");ui->comboBox_setBuad->setCurrentIndex(1);}void Widget::tableInit()
{QStringList headerText;headerText<<"时间"<<"命令1"<<"命令2"<<"数据";ui->tableWidget->setColumnCount(headerText.size()); //设置表格列数ui->tableWidget->resizeColumnsToContents();for (int i=0;i<ui->tableWidget->columnCount();i++){QTableWidgetItem *headerItem=new QTableWidgetItem(headerText.at(i));QFont font=headerItem->font(); //获取原有字体设置font.setBold(true); //设置为粗体font.setPointSize(10); //字体大小headerItem->setForeground(QBrush(Qt::black)); //设置文字颜色headerItem->setFont(font); //设置字体ui->tableWidget->setHorizontalHeaderItem(i,headerItem); //设置表头单元格的item}ui->tableWidget->setColumnWidth(0, 60);ui->tableWidget->setColumnWidth(1, 50);ui->tableWidget->setColumnWidth(2, 50);ui->tableWidget->setColumnWidth(3, 250);
}//为一行的单元格创建 Items
void Widget::createItemsARow(int rowNum, QByteArray *protocalData)
{uchar preFix = 0xA5;uchar crc = 0;uchar temp = 0;temp = static_cast<uchar>(protocalData->at(0));if(static_cast<uchar>(protocalData->at(0)) == preFix){for(int i=1; i<protocalData->length()-2; i++){temp = static_cast<uchar>(protocalData->at(i));crc += static_cast<uchar>(protocalData->at(i));}temp = static_cast<uchar>(protocalData->at(protocalData->length()-2));if(crc != static_cast<uchar>(protocalData->at(protocalData->length()-2))){return;}uchar len = protocalData->at(1);uchar cmd1 = protocalData->at(2);uchar cmd2 = protocalData->at(3);QByteArray data = protocalData->mid(4,len-6);QDateTime curTime = QDateTime::currentDateTime();//获取系统现在的时间QString time = curTime.toString("hh:mm:ss"); //设置显示格式uint8_t str1 = static_cast<uint8_t>(cmd1);QString hexStr1 = QString("%1").arg(str1, 2, 16, QLatin1Char('0')).toUpper();uint8_t str2 = static_cast<uint8_t>(cmd2);QString hexStr2 = QString("%1").arg(str2, 2, 16, QLatin1Char('0')).toUpper();QString testdata = ByteArrayToHexString(data).toLatin1().toUpper();QTableWidgetItem *item = new QTableWidgetItem(time, ctTime);ui->tableWidget->setItem(rowNum, colTime, item);item = new QTableWidgetItem(hexStr1, ctCmd1);ui->tableWidget->setItem(rowNum, colCmd1, item);item = new QTableWidgetItem(hexStr2, ctCmd2);ui->tableWidget->setItem(rowNum, colCmd2, item);item = new QTableWidgetItem(testdata, ctData);ui->tableWidget->setItem(rowNum, colData, item);}auto lastRowIndex = ui->tableWidget->rowCount()-1; // 最后一行的索引auto lastModelIndex = ui->tableWidget->model()->index(lastRowIndex, 0);ui->tableWidget->scrollTo(lastModelIndex); // 滚动到最后一行
}QString Widget::ByteArrayToHexString(QByteArray &ba)
{QDataStream out(&ba,QIODevice::ReadWrite); //将str的数据 读到out里面去QString buf;while(!out.atEnd()){qint8 outChar = 0;out >> outChar; //每次一个字节的填充到 outcharQString str = QString("%1").arg(outChar&0xFF,2,16,QLatin1Char('0')).toUpper() + QString(" "); //2 字符宽度buf += str;}return buf;
}void Widget::sendData()
{QString message = ui->lineEdit_sendData->text();if(ui->checkBox_hexSend->isChecked() == true){serialPort->write(QByteArray::fromHex(message.toLatin1()));}else{serialPort->write(message.toLatin1());}
}void Widget::receiveData()
{QByteArray message;QString hexMsg;message.append(serialPort->readAll());QDateTime time = QDateTime::currentDateTime(); //获取系统现在的时间QString date = time.toString("hh:mm:ss"); //设置显示格式if(ui->checkBox_hexRcv->isChecked() == true){tableRowCnt++;ui->tableWidget->setRowCount(tableRowCnt);createItemsARow(tableRowCnt-1,&message);hexMsg = ByteArrayToHexString(message).toLatin1();ui->textEdit_RecData->append(date+QString("-> ")+hexMsg.toUpper());}else{ui->textEdit_RecData->append(date+QString("-> ")+message);}
}
void Widget::openSerialport()
{ui->pushButton_closeSerialPort->setEnabled(true);ui->pushButton_openSerialPort->setEnabled(false);QList<QSerialPortInfo> comList = QSerialPortInfo::availablePorts();QSerialPortInfo portInfo = comList.at(ui->comboBox_chooseCom->currentIndex());serialPort->setPort(portInfo); //设置使用哪个串口if(serialPort->open(QIODevice::ReadWrite) == false){QMessageBox::critical(NULL, "critical", "找不到串口/串口被占用", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);closeSerialport();}else{serialPort->setBaudRate(QSerialPort::Baud115200);serialPort->setDataBits(QSerialPort::Data8);serialPort->setParity(QSerialPort::NoParity);serialPort->setStopBits(QSerialPort::OneStop);serialPort->setFlowControl(QSerialPort::NoFlowControl);ui->pushButton_sendData->setEnabled(true);}
}void Widget::closeSerialport()
{if(serialPort->isOpen()){serialPort->clear();serialPort->close();}ui->pushButton_closeSerialPort->setEnabled(false);ui->pushButton_openSerialPort->setEnabled(true);
}void Widget::setBuad(int buad)
{QString str = ui->comboBox_setBuad->currentText();serialPort->setBaudRate(str.toInt());
}void Widget::clearRcv()
{ui->textEdit_RecData->clear();
}void Widget::on_btnClear_clicked()
{ui->lineEdit_protocalData->clear();
}void Widget::on_btnConvert_clicked()
{ui->lineEdit_protocalData->clear();bool ok;QString str = "A5";int val1= ui->lineEditCmd1->text().toInt(&ok,16); //以十六进制数读入QString str1 = QString("%1").arg(val1, 2, 16, QLatin1Char('0'));int val2= ui->lineEditCmd2->text().toInt(&ok,16); //以十六进制数读入QString str2 = QString("%1").arg(val2, 2, 16, QLatin1Char('0'));if ((str1.isEmpty())||(str2.isEmpty()))return;int val3= ui->lineEditData->text().toInt(&ok,16); //以十六进制数读入QString str3 = QString("%1").arg(val3, 2, 16, QLatin1Char('0'));uint8_t len = 6 + static_cast<uint8_t>(str3.length()/2);QString hexStr = QString("%1").arg(len, 2, 16, QLatin1Char('0'));str.append(hexStr);str.append(str1);str.append(str2);str.append(str3);uint8_t crc;QString tmp;for(int i=0; i<str3.length(); i+=2){tmp = ui->lineEditData->text()[i];tmp += ui->lineEditData->text()[i+1];crc+= tmp.toInt(&ok,16);tmp = "";}crc += len;crc += val1;crc += val2;QString hexCrc= QString("%1").arg(crc, 2, 16, QLatin1Char('0'));str.append(hexCrc);str.append("5A");str = str.toUpper();ui->lineEdit_protocalData->insert(str);
}void Widget::sendProtocalHexData()
{QString message = ui->lineEdit_protocalData->text();serialPort->write(QByteArray::fromHex(message.toLatin1()));
}
ui


代码框架概览

演示视频
串口上位机(基本设置/协议收发)演示_哔哩哔哩_bilibili
串口上位机(基本设置/协议收发)演示
相关文章:
Qt串口基本设置与协议收发
前言 1.一直都想要做一个Qt上位机,趁着这个周末有时间,动手写一下 2.comboBox没有点击的信号,所以做了一个触发的功能 3.Qt的数据类型很奇怪,转来转去的我也搞得很迷糊 4.给自己挖个坑,下一期做一个查看波形的上位…...
interview3-微服务与MQ
一、SpringCloud篇 (1)服务注册 常见的注册中心:eureka、nacos、zookeeper eureka做服务注册中心: 服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、…...
kafka详解一
kafka详解一 1、消息引擎背景 根据维基百科的定义,消息引擎系统是一组规范。企业利用这组规范在不同系统之间传递语义准确的消息,实现松耦合的异步式数据传递. 即:系统 A 发送消息给消息引擎系统,系统 B 从消息引擎系统中读取 A…...
Flutter yuv 转 rgb
1、引用yuv_converter库 yuv_converter: ^0.0.1 2、导入头文件: import package:yuv_converter/yuv_converter.dart;3、yuv转rgb YuvConverter.yuv420NV21ToRgba8888(yuvRawData, 512, 512) 根据yuv格式选择不同的api。 举个例子: void initState() …...
MySQL——子查询
2023.9.8 相关学习笔记: #子查询 /* 含义: 出现在其他语句中的select语句,称为子查询或内查询 外部的查询语句,称为主查询或外查询分类: 按子查询出现的位置:select后面:仅仅支持标量子查询fro…...
Java学习笔记---多态
面向对象三大特征之一(继承,封装,多态) 多态的应用场景:根据传递对象的不同,调用不同的show方法 一、多态的定义 同类型的对象,表现出的不同形态(对象的多种形态) 二…...
2023-09-10 LeetCode每日一题(课程表 II)
2023-09-10每日一题 一、题目编号 210. 课程表 II二、题目链接 点击跳转到题目位置 三、题目描述 现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] [ai, bi] ,表示在…...
合并区间【贪心算法】
合并区间 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 class Solution {public int[][] merge(int[…...
2023,软件测试人的未来在哪里?
2023年,IT行业出现空前的萧条,首先是年初一开始各大厂像着了魔似的不约而同的纷纷裁员、降薪、奖金包缩水,随之而来的是需求萎缩,HC减少或封锁等等。 而有幸未被列入裁员名单的在职人员,庆幸之余也心有余悸࿰…...
Python中的Numpy向量计算(R与Python系列第三篇)
目录 一、什么是Numpy? 二、如何导入NumPy? 三、生成NumPy数组 3.1利用序列生成 3.2使用特定函数生成NumPy数组 (1)使用np.arange() (2)使用np.linspace() 四、NumPy数组的其他常用函数 (1)np.z…...
LeetCode刷题笔记【27】:贪心算法专题-5(无重叠区间、划分字母区间、合并区间)
文章目录 前置知识435. 无重叠区间题目描述参考<452. 用最少数量的箭引爆气球>, 间接求解直接求"重叠区间数量" 763.划分字母区间题目描述贪心 - 建立"最后一个当前字母"数组优化marker创建的过程 56. 合并区间题目描述解题思路代码① 如果有重合就合…...
nvidia-smi 命令详解
nvidia-smi 命令详解 1. nvidia-smi 面板解析2. 显存与GPU的区别 Reference: nvidia-smi命令详解 相关文章: nvidia-smi nvcc -V 及 CUDA、cuDNN 安装 nvidia-smi(NVIDIA System Management Interface) 是一种命令行实用程序,用于监控和管理 NVIDIA G…...
fork()函数的返回值
在程序中,int pd fork() 是一个典型的 fork() 调用。fork() 函数会创建一个新的进程,然后在父进程中返回子进程的进程ID(PID),在子进程中返回0。所以 pd 的值会根据当前进程是父进程还是子进程而有所不同:…...
Stable Diffusion WebUI挂VPN不能跑图解决办法(Windows)
如何解决SD在打开VPN的状态不能运行的问题 在我们开VPN的时候会出现无法生成图片,也无法做其他任何事,这个时候是不是很着急呢? 别急,我这里会说明如何解决。 就像这样,运行半天生成不了图,有时还会出现…...
Android的本地数据
何为本地,即写完之后除非手动修改,否像嘎了一样在那固定死了 有些需求可能也会要求我们去写死数据,因为这需求是一成不变的,那么你通常会用什么方法写死呢? 1. 本地存储-SharedPreferences 此方法可以长时间保存于手…...
android NDK 开发包,网盘下载,不限速
记录下ndk 开发包的地址,分享给大家。 另外有Android studio的下载包, 在另一篇文章 链接:http://t.csdn.cn/JSr9x Android Studio.exe 下载 2023 最新更新,网盘下载_hsj-obj的博客-CSDN博客 主要是19-25,其他的没有…...
【每日一题Day320】LC2651计算列车到站时间 | 数学
计算列车到站时间【LC2651】](https://leetcode.cn/problems/calculate-delayed-arrival-time/) 给你一个正整数 arrivalTime 表示列车正点到站的时间(单位:小时),另给你一个正整数 delayedTime 表示列车延误的小时数。 返回列车实…...
C语言柔性数组详解:让你的程序更灵活
柔性数组 一、前言二、柔性数组的用法三、柔性数组的内存分布四、柔性数组的优势五、总结 一、前言 仔细观察下面的代码,有没有看出哪里不对劲? struct S {int i;double d;char c;int arr[]; };还有另外一种写法: struct S {int i;double …...
Redis-带你深入学习数据类型list
目录 1、list列表 2、list相关命令 2.1、添加相关命令:rpush、lpush、linsert 2.2、查找相关命令:lrange、lindex、llen 2.3、删除相关命令:lpop、rpop、lrem、ltrim 2.4、修改相关命令:lset 2.5、阻塞相关命令:…...
react拖拽依赖库react-dnd
注:对于表格自定义行可以拖拽和树自定义节点可以拖拽等比较适用,其余的拖拽处理可以使用dragstart,drop等js原生事件来实现 react-dnd使用方法很简单,直接上干货 第一步安装依赖并引入 import { DndProvider } from react-dnd;…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
