QT串口助手:识别串口号,发送,接收,十六进制

news2024/11/29 6:31:03

1 摘要

本文主要讲述如何使用QT从零开始实现一个串口助手的基本功能,功能如标题所示,文末附有源码供大家参考。文中若有纰漏,烦请读者斧正。

2 环境

  • QT 5.14.1
  • Window 11

3 功能

  • 串口打开/关闭
    • 启动软件时识别串口号
    • 打开按键随串口打开状态而改变文字
    • 打开失败弹窗提示
  • 串口发送/接收
    • 字符和十六进制
  • 清除发送/接收

4 创建工程

  1. 下载QT
  2. 安装QT
  3. 创建工程

在这里插入图片描述

5 界面布局

5.1 用到的控件

  • Buttons
    • Push Button:即最常见的按键:打开串口、发送、清除接收、清除发送,都是用了这个控件
    • Check Box:即用于打钩的小方框,16进制显示和16进制接收用到了此控件
  • Input Widgets
    • Combo Box:点击有下拉菜单的控件,串口、波特率、停止位、数据位、奇偶校验的选择用到此控件
    • Text Edit:输入文本框,串口的发送内容的编辑用到此控件,串口接收内容的显示也用到此控件(此时该控件为只读)
  • Display Widgets
    • Label:串口、波特率、停止位、数据位、奇偶校验,这些文字的显示用到此控件
  • Layouts
    • Vertical Layout:垂直布局,在此布局框内的空间按垂直等间距排列
    • Horizontal Layout:水平布局,在此布局框内的控件按水平等间距排列
    • Form Layout:在此布局框内的控件按垂直两列等间距排列

5.2 摆放控件

将控件从左侧拖拽到窗口编辑处
在这里插入图片描述

5.3 控件编辑及布局

  • 修改Label/Push Button/Check Box控件的文本内容(双击控件即可修改)
  • 在Combo Box中添加菜单内容(双击控件即可添加)
    • 串口:不加,后面通过串口号识别,在代码里添加
    • 波特率:本文只添加9600/19200/38400/57600/115200
    • 停止位:1/1.5/2
    • 数据位:5/6/7/8
    • 校验位:无/奇校验/偶校验
  • 控件布局:调整控件位置,通过布局菜单对选中的控件进行布局
  • 修改控件名:按控件的实际用途修改控件名

控件布局:在这里插入图片描述

修改控件名:
在这里插入图片描述

6 添加库及头文件

本文的串口助手基于QT自带的QSerialPort类实现,故需要添加该类相关的宏和头文件,除此之外,本文用到的头文件也在此一并添加。

添加宏serialport:
在这里插入图片描述

添加头文件:
在这里插入图片描述
其中QSerialPort即QT自带的串口类,QSerialPortInfo用于获取串口号,QMessageBox用于实现弹窗提示,QDebug用于输出调试信息。

7 打开串口

7.1 槽函数

打开串口这个动作是在按下“打开串口”这个按键后进行的,因此必须建立按键跟动作之间的联系,在QT中,这种联系是通过“信号和槽”这样的机制来实现的,简单来说,“信号”就是按下按键这个事件,可以理解为一个标志,“槽”是指对这个事件所做的响应,可以理解为一个函数。

从控件转到槽函数(此处转到“按下时”的槽函数,即此函数是在按键按下时被调用):
在这里插入图片描述

选择clicked()后,QT将在cpp文件中生成槽函数(显然,函数里的内容是要程序员手写的,不是QT生成的):
在这里插入图片描述

槽函数所调用的子函数applySerialPortConfig(此函数实现获取combobox中的输入并设置到串口):
在这里插入图片描述

槽函数所调用的子函数setEnableSerialPortConfig(此函数实现combobox的屏蔽与打开):
在这里插入图片描述

“打开按键”的槽函数主要做这几件事情:

  1. 根据当前串口的打开状态,选择是要打开串口还是关闭串口
    • 通过自定义成员变量mIsOpen实现
  2. 若要打开串口,则从combobox中获取配置并打开串口
    • 先通过QComboBox的currentText方法获取当前输入,再把输入通过QSerialPort类的setXXX方法进行设置,再调open方法
  3. 若要关闭串口,则调用关闭串口函数
    • 调QSerialPort类的close方法
  4. 串口打开失败时,弹窗提示
    • 调QMessageBox类的warning方法
  5. 串口打开状态改变后,修改按键的文本内容
    • 调QPushButton的setText方法
  6. 串口打开状态改变后,修改combobox的激活状态
    • 调QComboBox的setText方法

注意:相关成员变量需先在头文件中定义好

7.2 串口列表的获取

前文中并没有在combobox中写死串口列表,是为了动态获取串口列表。本文只在软件打开的时候获取串口列表(更完善的做法是在点击combobox后更新,后面有时间会实现这个功能),只需在构造函数中添加以下代码。
在这里插入图片描述

foreach是QT中的一个关键字,其作用是对第二个参数中的对象进行遍历,把遍历过程中的每个对象依次赋给第一个参数,并执行花括号中的内容。在这里,就是把可获取的串口列表availablePorts()中的串口,逐个将其串口号添加到combobox中。

8 串口发送/接收

8.1 字符的收发

对于发送来说,其实现过程如下:

  1. 给发送按键创建槽函数
  2. 在槽函数中获取发送文本框中的数据
  3. 对获取到的数据进类型和格式的转换(如需)
  4. 发送数据到串口

代码实现:

void MainWindow::on_pushButtonSend_clicked()
{
    if(mIsOpen == true) {
        //mSerialPort.write(ui->textEditSend->toPlainText().toStdString().c_str());   //ENTER键:0A(即\n)
        mSerialPort.write(ui->textEditSend->toPlainText().replace("\n", "\r\n").toStdString().c_str()); //ENTER键:0D 0A(即\r\n)
    }
}

对于接收来说,由于不存在接收按键,其实现跟发送有些许不同,但本质还是一样的,都是QT中的信号和槽的机制:

  1. 通过connect方法,连接接收完成信号readyRead和自定义槽函数on_serialPort_readyRead(函数名字自定义)
  2. 在槽函数中读串口
  3. 对读到的串口数据进行类型和格式的转换(如需)
  4. 把数据显示在接收文本框中

代码实现:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //此处省略中间内容....

	//连接接收完成信号readyRead和自定义槽函数on_serialPort_readyRead
    connect(&mSerialPort, SIGNAL(readyRead()), this, SLOT(on_serialPort_readyRead()));
}

void MainWindow::on_serialPort_readyRead()
{
    if(mIsOpen == true) {
        QByteArray rxData = mSerialPort.readAll();
        ui->textEditReceive->insertPlainText(rxData);   //用append会多一个换行
        ui->textEditReceive->moveCursor(QTextCursor::End);  //调整光标位置到最新接收的尾部,避免看不到最新接收的数据
    }
}

8.2 十六进制收发

在实现了上面字符的收发基础上,再实现十六进制的收发,其实不难,无非多了显示方式的判断,以及相应格式和类型的转换罢了。这些格式和类型的转换通常都有现成的轮子,不需要再造轮子,这样节省了大量时间。

首先是checkbox控件的槽函数实现,无论是接收还是发送的16进制checkbox,其实现的都是以下两件事情:

  1. 根据当前checkbox状态,对文本框的数据进行字符和十六进制数之间的格式转换
  2. 记录当前checkbox状态(用于接收和发送函数的格式转换)

16进制接收显示checkbox的stateChanged槽函数:

void MainWindow::on_checkBoxHexDisplay_stateChanged(int arg1)
{
    if(arg1 == Qt::Checked) {
        QString *strHex = new QString;
        *strHex = ui->textEditReceive->toPlainText().replace("\n", "\r\n"); //QT中ENTER键为:\n(即0A),将其替换为Windows中的\r\n(即0D 0A)
        ui->textEditReceive->clear();
        ui->textEditReceive->insertPlainText(strHex->toUtf8().toHex(' ').append(' '));  //QString转QByteArray,QByteArray中的字符转16进制并追加空格,toHex在每个16进制数后加空格,append在最后加空格
        ui->textEditReceive->moveCursor(QTextCursor::End);
        delete strHex;

        mHexDisplay = true;
    } else {
        QString *strChar = new QString;
        *strChar = ui->textEditReceive->toPlainText().remove(QRegExp("\\s"));   //删除空格,空格的正则表达式为\s
        ui->textEditReceive->clear();
        ui->textEditReceive->insertPlainText(QByteArray::fromHex(strChar->toLatin1())); //toLatin1:按照ASCII编码把String转成ByteArray,fromHex:对ByteArray做16进制解码
        ui->textEditReceive->moveCursor(QTextCursor::End);
        delete strChar;

        mHexDisplay = false;
    }
}

16进制发送显示checkbox的stateChanged槽函数:

void MainWindow::on_checkBoxHexSend_stateChanged(int arg1)
{
    if(arg1 == Qt::Checked) {
        QString *strHex = new QString;
        *strHex = ui->textEditSend->toPlainText().replace("\n", "\r\n");
        ui->textEditSend->clear();
        ui->textEditSend->insertPlainText(strHex->toUtf8().toHex(' ').append(' '));
        ui->textEditSend->moveCursor(QTextCursor::End);
        delete strHex;

        mHexSend = true;
    } else {
        QString *strChar = new QString;
        *strChar = ui->textEditSend->toPlainText().remove(QRegExp("\\s"));
        ui->textEditSend->clear();
        ui->textEditSend->insertPlainText(QByteArray::fromHex(strChar->toLatin1()));
        ui->textEditSend->moveCursor(QTextCursor::End);
        delete strChar;

        mHexSend = false;
    }
}

而收发槽函数中也要相应加入格式转换的逻辑。

接收槽函数:

void MainWindow::on_serialPort_readyRead()
{
    if(mIsOpen == true) {
        QByteArray rxData = mSerialPort.readAll();

        if(ui->checkBoxHexDisplay->isChecked()) {
            ui->textEditReceive->insertPlainText(rxData.toHex(' ').append(' '));  //把ByteArray按16进制编码,toHex在每个16进制数后加空格,append在最后加空格
        } else {
            ui->textEditReceive->insertPlainText(rxData);
        }
        ui->textEditReceive->moveCursor(QTextCursor::End);  //调整光标位置到最新接收的尾部,避免看不到最新接收的数据
    }
}

发送槽函数:

void MainWindow::on_pushButtonSend_clicked()
{
    if(mIsOpen == true) {
        if(ui->checkBoxHexSend->isChecked()) {
            QByteArray* arrayTxData = new QByteArray;
            *arrayTxData = ui->textEditSend->toPlainText().remove(QRegExp("\\s")).toUtf8();
            mSerialPort.write(QByteArray::fromHex(*arrayTxData));

            delete arrayTxData;
        } else {
            //mSerialPort.write(ui->textEditSend->toPlainText().replace("\n", "\r\n").toStdString().c_str());
            mSerialPort.write(ui->textEditSend->toPlainText().replace("\n", "\r\n").toUtf8()); //QT中ENTER键为:\n(即0A),将其替换为Windows中的\r\n(即0D 0A)
        }
    }
}

也许有读者会对上述代码中那一连串的成员函数感到疑惑,不知其为何意,想要知道这些函数的作用,最好的办法是查阅QT的帮助文档。

比如要查fromHex这个函数的作用(对于QT中的类的搜索也是同理,查阅帮助文档和手册是学习QT乃至许多技术的必备技能):
在这里插入图片描述

9 清除发送/接收

这个非常简单,只需在槽函数中调用QTextEdit控件的clear方法即可。
在这里插入图片描述

10 后记

QT中的各种控件类都是经过层层继承而来,调用某个控件类中的方法,不一定是定义在该类里面,而是定义在其父类中。这种套娃模式极好地用代码描述了真实世界,是面向对象的精髓之一。

如果要实现串口列表的实时更新,习惯了面向过程开发的朋友可能第一反应是用定时器去周期更新,而在面向对象的世界中有一个方法是把控件的方法给改写,在其中加入获取串口列表的逻辑,这是两种开发思想差异的一个体现。

本文中的串口助手其实还是有很多不完善之处,比如还缺少以下功能:

  • 16进制发送模式下,发送框的非法字符检测
  • 发送接收字节数统计
  • 自动发送
  • 保存上一次的串口配置

后续有时间将慢慢补上。

11 源码

懒得传git,先放某度云上
链接:Serial
提取码:hjq5

12 参考

  • QT帮助文档
  • QT中的foreach关键字
  • QComboBox点击时自动更新列表(自动刷新QSerialPort)
  • QT弹窗
  • QTextEdit追加纯文本(无额外的换行)
  • QString、QByteArray、ASCII码、16进制等类型转换和编码转换
  • QT 十六进制字符串与原数据字符串互转
  • QT QString去除空格
  • QT 正则表达式


SZ
2023.9.3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/967674.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python】可视化

柱状图 matplotlib之pyplot模块之柱状图(bar():基础参数、外观参数)_plt.bar_mighty13的博客-CSDN博客 bar()的基础参数如下: x:柱子在x轴上的坐标。浮点数或类数组结构。注意x可以为字符串数组! height&…

【MySQL】七种SQL优化方式 你知道几条

1.插入数据 1.1insert 如果我们需要一次性往数据库表中插入多条记录,可以从以下三个方面进行优化。 insert into tb_test values(1,tom); insert into tb_test values(2,cat); insert into tb_test values(3,jerry); 1). 优化方案一 批量插入数据 Insert into t…

CSS 一个好玩的卡片“开卡效果”

文章目录 一、用到的一些CSS技术二、实现效果三、代码 一、用到的一些CSS技术 渐变 conic-gradientbox-shadowclip-path变换、过渡 transform、transition动画 animation keyframes伪类、伪元素 :hover、::before、::after …绝对布局。。。 clip-path 生成网站 https://techb…

新手做TikTok适合哪些类目?

现在很多小伙伴争先恐后想要在TikTok入驻,开店开直播带货赚钱,但是又怕自己是小白,不好拿捏这个平台。TikTok平台,适合小白做吗?现在tiktok千亿级的流量还处于蓝海阶段,想入局要趁早。那么肯定又有小伙伴疑…

PostgreSQL本地化

本地化的概念 本地化的目的是支持不同国家、地区的语言特性、规则。比如拥有本地化支持后,可以使用支持汉语、法语、日语等等的字符集。除了字符集以外,还有字符排序规则和其他语言相关规则的支持,例如我们知道(‘a’,‘b’)该如何排序&…

Ubuntu 升级cuda版本与切换

下载cuda版本 进:CUDA Toolkit 12.2 Downloads | NVIDIA Developer wget https://developer.download.nvidia.com/compute/cuda/12.2.0/local_installers/cuda_12.2.0_535.54.03_linux.runsudo sh ./cuda_12.2.0_535.54.03_linux.run --toolkit --silent --overrid…

快速上手GIT命令,现学也能登堂入室

系列文章目录 手把手教你安装Git,萌新迈向专业的必备一步 GIT命令只会抄却不理解?看完原理才能事半功倍! 快速上手GIT命令,现学也能登堂入室 系列文章目录一、GIT HELP1. 命令文档2. 简要说明 二、配置1. 配置列表2. 增删改查3. …

【具身智能】论文系列解读-RL-ViGen ArrayBot USEEK

1. RL-ViGen:视觉泛化的强化学习基准 RL-ViGen: A Reinforcement Learning Benchmark for Visual Generalization 0 摘要与总结 视觉强化学习(Visual RL)与高维观察相结合,一直面临着分布外泛化的长期挑战。尽管重点关注旨在解…

从C语言到C++_37(特殊类设计和C++类型转换)单例模式

目录 1. 特殊类设计 1.1 不能被拷贝的类 1.2 只能在堆上创建的类 1.3 只能在栈上创建的类 1.4 不能被继承的类 1.5 只能创建一个对象的类(单例模式)(重点) 1.5.1 饿汉模式 1.5.2 懒汉模式 2. 类型转换 2.1 static_cast 2.2 reinterpret_cast 2.3 const_cast 2.4 d…

算法笔记——路径问题

在引入介绍如何写一个算法的时候,我们先引入一个题作为例子 1137. 第 N 个泰波那契数 - 力扣(LeetCode) 作为刚开始学习算法的我们,看到这个题目的时候,应该想好以下的问题: 1.状态表示 我们要用什么来表…

关于大模型参数微调的不同方法

Adapter Tuning 适配器模块(Adapter Moudle)可以生成一个紧凑且可扩展的模型;每个任务只需要添加少量可训练参数,并且可以在不重新访问之前任务的情况下添加新任务。原始网络的参数保持不变,实现了高度的参数共享 Pa…

android framework之Applicataion启动流程分析(三)

现在再回顾一下Application的启动流程,总的来说,虽然进程的发起是由ATMS服务发起的,但是进程的启动还是由AMS负责,所以需要调用AMS的startProcess()接口完成进程启动流程,AMS要处理的事情很多,它将事务交给…

代码随想录训练营第四十三天|1049. 最后一块石头的重量 II、 494. 目标和、 474.一和零

1049. 最后一块石头的重量 II 力扣题目链接(opens new window) 题目难度:中等 有一堆石头,每块石头的重量都是正整数。 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x &l…

小程序快速备案助手代备案小程序开发

小程序快速备案助手代备案小程序开发 用户注册与登录:用户可以通过手机号或其他方式进行注册和登录,以便进行备案相关操作。备案信息填写:用户可以填写小程序的备案信息,包括小程序名称、小程序服务类目、域名等。备案材料上传&a…

GA遗传算法

储备知识 GA算法主要解决数学模型中最优化的搜索算法,是进化算法中的一种,基因算法借鉴了自然界基因的遗传的主要现象,分别为遗传,变异,自然选择,杂交等。 GA算法参数 GA算法的参数如下所示。 种群规模…

c++ vs2019 cpp20规范的STL库的map与multimap源码分析

map就是一个红黑树。 标准平衡二叉树,要求左右子树的高度差不超过1 。红黑树只要求左右子树的高度差不超过一倍即可。兼顾了树平衡与效率。避免了AVL树的频繁调整树平衡。 b站 的“可雷曼土”大师,讲红黑树的理论讲的很透彻,再结合看代码&…

va_list使用及两个注意项(可能导致崩溃和少1个字符)

两个注意项: 1、linux平台上vsnprintf会破坏va_list变量,需要重新调用va_start,否则可能访问错位崩溃。 2、vsnprintf会留一个字节补0结束,但返回值不包含,所以必须判断返回值小于分配的空间。 具体代码分析&#xff1…

【包过滤防火墙——firewalld动态防火墙】的简单使用

文章目录 firewald与iptables区别firewalld九个区域firewalld配置方法firewalld参数和命令firewalld两种模式firewalld使用实验 firewalld不要与iptables混用 firewald与iptables区别 iptables 主要是基于接口,来设置规则,从而判断网络的安全性。firewa…

卡特兰数和算法

在组合数学中,卡特兰数是一系列自然数,出现在各种组合计数问题中,通常涉及递归定义的对象。它们以比利时数学家尤金查尔斯卡特兰(Eugne Charles Catalan)的名字命名。 卡特兰数序列是1, 1, 2, 5, 14, 42......&#xf…