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;…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...

云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...