QT初体验:手把手带你写一个自己的串口助手

这篇具有很好参考价值的文章主要介绍了QT初体验:手把手带你写一个自己的串口助手。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本文记录一下用QT Creator 写一个基本功能齐全的串口助手的过程,整个工程只有几百行代码,跟着做下来对新手来说可以更快了解整个QT项目的开发过程和一些常用控件的使用方法。对新手学习QT能增强信心,话不多说,正文开始

先看成品:
QT初体验:手把手带你写一个自己的串口助手

制作过程

1. 布局UI界面

(1) 创建QMainWindow工程。这一步就不赘述了,参考:QT C++入门学习(1) QT Creator安装和使用

创建项目时项目名称可以设为Serial,基类可以选择QMainWindow也可以选择Qwiget。
注意默认勾选“Generate form”,生成 ui 窗体文件 mainwindow.ui 后面要用到。

完成后项目文件展示:
QT初体验:手把手带你写一个自己的串口助手

(2)双击mainwindow.ui打开UI布局界面,从左侧控件选择区找到需要的控件拖动到界面设计区的对应位置
(请注意:以下提到的摆放位置只放大概位置即可,因为必须借用布局工具才能规正)

找到Label控件:
QT初体验:手把手带你写一个自己的串口助手
用6个Lable控件分别按下图所示红框位置摆放,并且双击改显示的文字,或单击选择对应Lable后在其右边属性设置界面里的text属性更改
QT初体验:手把手带你写一个自己的串口助手

QT初体验:手把手带你写一个自己的串口助手

找到Combo Box控件:
QT初体验:手把手带你写一个自己的串口助手
用5个Combo Box控件分别按下图绿色框所示位置摆放
QT初体验:手把手带你写一个自己的串口助手

找到Push Button控件:
QT初体验:手把手带你写一个自己的串口助手
用5个Push Button控件分别按下图紫色框所示位置摆放,并且双击按钮改显示的文字
QT初体验:手把手带你写一个自己的串口助手
找到Check Box控件:
QT初体验:手把手带你写一个自己的串口助手
用6个Check Box控件分别按下图蓝色框所示位置摆放,并且双击更改显示的文字
QT初体验:手把手带你写一个自己的串口助手
用1个Spin Box控件分别按下图棕色框所示位置摆放并调整大小
QT初体验:手把手带你写一个自己的串口助手
最后两个大的白色区域就是接收框和发送输入框,上面接收框用Plain Text Edit控件,下面输入框用Text Edit控件
QT初体验:手把手带你写一个自己的串口助手
接收框用的Plain Text Edit控件需要更改属性为只读
QT初体验:手把手带你写一个自己的串口助手

2. 添加下拉列表项

5个Combo Box控件分别双击添加列表项(端口对应的Combo Box不用改)

波特率:
(点击绿色加号即可添加)
QT初体验:手把手带你写一个自己的串口助手

数据位:

QT初体验:手把手带你写一个自己的串口助手

停止位:

QT初体验:手把手带你写一个自己的串口助手

校验位:

QT初体验:手把手带你写一个自己的串口助手

对于波特率和数据位的下拉列表控件还需要通过更改属性currentIndex属性,改变默认值。波特率默认9600,数据位默认8。

QT初体验:手把手带你写一个自己的串口助手

3. 修改控件名称

下面对控件进行改名,以便对应程序中的对象名,用默认名不直观。

如何改名?
点击控件,找到界面右边属性栏的objectName。
QT初体验:手把手带你写一个自己的串口助手

名称参考下图:

QT初体验:手把手带你写一个自己的串口助手
注意这一步一定要把对象名设置对,否则在编译程序时会有问题。

4. 利用布局工具

第一步放置控件的时候,想必就会发现根本很难通过鼠标拖动的方法完成对齐,这时还得用上方菜单栏的布局工具:
QT初体验:手把手带你写一个自己的串口助手

下图中的红框就是布局后才显示的:
QT初体验:手把手带你写一个自己的串口助手
操作方法是先手动把控件摆放到大概位置,然后鼠标左键拉一个框选定几个控件,再点击上面的布局工具。
接收设置和发送设置就是用了栅格布局,发送和清空发送按钮是垂直布局,串口设置中是上面部分是栅格布局,然后整体再用垂直布局。布局后的红框还可以调整大小

最后这3个框是Group Box控件,双击就可以改文字。为啥把这步放最后了是因为发现Group Box控件放上去后直接选不了里面的控件了,暂时不知道怎么操作。这里跟VS里不一样,VS就比较方便
QT初体验:手把手带你写一个自己的串口助手

5. 编辑代码

双击打开.pro文件

QT初体验:手把手带你写一个自己的串口助手

core gui后面添加serialport,即 QT += core gui serialport

QT初体验:手把手带你写一个自己的串口助手

mainwindow.h文件源码:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSerialPort>
#include <QString>
#include <QSerialPortInfo>
#include <QMessageBox>
#include <QTimer>
#include <QPainter>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    QSerialPort *serialPort;//定义串口指针

private slots:

    /*手动连接槽函数*/
    void manual_serialPortReadyRead();

    /*以下为mainwindow.ui文件中点击“转到槽”自动生成的函数*/
    void on_openBt_clicked();

    void on_sendBt_clicked();

    void on_clearBt_clicked();

    void on_btnClearSend_clicked();

    void on_chkTimSend_stateChanged(int arg1);

    void on_btnSerialCheck_clicked();

private:
    Ui::MainWindow *ui;

    // 发送、接收字节计数
    long sendNum, recvNum;
    QLabel *lblSendNum;
    QLabel *lblRecvNum;
    QLabel *lblPortState;
    void setNumOnLabel(QLabel *lbl, QString strS, long num);

    // 定时发送-定时器
    QTimer *timSend;
    //QTimer *timCheckPort;
};
#endif // MAINWINDOW_H

mainwindow.cpp文件源码:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QSerialPortInfo"
#include <QSerialPort>
#include <QMessageBox>
#include <QDateTime>

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

    QStringList serialNamePort;

    serialPort = new QSerialPort(this);
    connect(serialPort,SIGNAL(readyRead()),this,SLOT(manual_serialPortReadyRead()));/*手动连接槽函数*/

    /*找出当前连接的串口并显示到serailCb*/
    //foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
    //{
        //serialNamePort<<info.portName();// 自动扫描当前可用串口,返回值追加到字符数组中
    //}
    //ui->serailCb->addItems(serialNamePort);// 可用串口号,显示到串口选择下拉框中
    ui->serailCb->clear();
    //通过QSerialPortInfo查找可用串口
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        ui->serailCb->addItem(info.portName());
    }

    // 发送、接收计数清零
    sendNum = 0;
    recvNum = 0;
    // 状态栏
    QStatusBar *sBar = statusBar();
    // 状态栏的收、发计数标签
    lblSendNum = new QLabel(this);
    lblRecvNum = new QLabel(this);
    lblPortState = new QLabel(this);
    lblPortState->setText("Connected");
    //设置串口状态标签为绿色 表示已连接状态
    lblPortState->setStyleSheet("color:red");

    // 设置标签最小大小
    lblSendNum->setMinimumSize(100, 20);
    lblRecvNum->setMinimumSize(100, 20);
    lblPortState->setMinimumSize(550, 20);
    setNumOnLabel(lblSendNum, "S: ", sendNum);
    setNumOnLabel(lblRecvNum, "R: ", recvNum);
    // 从右往左依次添加
    sBar->addPermanentWidget(lblPortState);
    sBar->addPermanentWidget(lblSendNum);
    sBar->addPermanentWidget(lblRecvNum);

    // 定时发送-定时器
    timSend = new QTimer;
    timSend->setInterval(1000);// 设置默认定时时长1000ms
    connect(timSend, &QTimer::timeout, this, [=](){
        on_sendBt_clicked();
    });
}

MainWindow::~MainWindow()
{
    delete ui;
}

//检测通讯端口槽函数
void MainWindow::on_btnSerialCheck_clicked()
{
    ui->serailCb->clear();
    //通过QSerialPortInfo查找可用串口
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        ui->serailCb->addItem(info.portName());
    }
}

/*手动实现接收数据函数*/
void MainWindow::manual_serialPortReadyRead()
{
    QByteArray recBuf = serialPort->readAll();;
    QString str_rev;

    // 接收字节计数
    recvNum += recBuf.size();
    // 状态栏显示计数值
    setNumOnLabel(lblRecvNum, "R: ", recvNum);

    if(ui->chk_rev_hex->checkState() == false){
        if(ui->chk_rev_time->checkState() == Qt::Checked){
            QDateTime nowtime = QDateTime::currentDateTime();
            str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";
            str_rev += QString(recBuf).append("\r\n");
        }
        else{
	        // 在当前位置插入文本,不会发生换行。如果没有移动光标到文件结尾,会导致文件超出当前界面显示范围,界面也不会向下滚动。
	        //ui->recvEdit->appendPlainText(buf);
	       
	        if(ui->chk_rev_line->checkState() == Qt::Checked){
	            str_rev = QString(recBuf).append("\r\n");
	        }
	        else
	        {
	            str_rev = QString(recBuf);
	        }
        }
    }else{

        // 16进制显示,并转换为大写
        QString str1 = recBuf.toHex().toUpper();//.data();
        // 添加空格
        QString str2;
        for(int i = 0; i<str1.length (); i+=2)
        {
            str2 += str1.mid (i,2);
            str2 += " ";
        }
        if(ui->chk_rev_time->checkState() == Qt::Checked)
        {
            QDateTime nowtime = QDateTime::currentDateTime();
            str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";
            str_rev += str2.append("\r\n");
        }
        else
        {
            if(ui->chk_rev_line->checkState() == Qt::Checked)
                str_rev += str2.append("\r\n");
            else
                str_rev = str2;

        }
    }
    ui->recvEdit->insertPlainText(str_rev);
    ui->recvEdit->moveCursor(QTextCursor::End);

}

/*打开串口*/
void MainWindow::on_openBt_clicked()
{
    /*串口初始化*/
    QSerialPort::BaudRate baudRate;
    QSerialPort::DataBits dataBits;
    QSerialPort::StopBits stopBits;
    QSerialPort::Parity checkBits;

    // 获取串口波特率
    // baudRate = ui->baundrateCb->currentText().toInt();直接字符串转换为 int 的方法

    if(ui->baundrateCb->currentText()=="1200")
        baudRate=QSerialPort::Baud1200;
    else if(ui->baundrateCb->currentText()=="2400")
        baudRate=QSerialPort::Baud2400;
    else if(ui->baundrateCb->currentText()=="4800")
        baudRate=QSerialPort::Baud4800;
    else if(ui->baundrateCb->currentText()=="9600")
        baudRate=QSerialPort::Baud9600;
    else if(ui->baundrateCb->currentText()=="19200")
        baudRate=QSerialPort::Baud19200;
    else if(ui->baundrateCb->currentText()=="38400")
        baudRate=QSerialPort::Baud38400;
    else if(ui->baundrateCb->currentText()=="57600")
        baudRate=QSerialPort::Baud57600;
    else if(ui->baundrateCb->currentText()=="115200")
        baudRate=QSerialPort::Baud115200;

    // 获取串口数据位
    if(ui->databitCb->currentText()=="5")
        dataBits=QSerialPort::Data5;
    else if(ui->databitCb->currentText()=="6")
        dataBits=QSerialPort::Data6;
    else if(ui->databitCb->currentText()=="7")
        dataBits=QSerialPort::Data7;
    else if(ui->databitCb->currentText()=="8")
        dataBits=QSerialPort::Data8;

    // 获取串口停止位
    if(ui->stopbitCb->currentText()=="1")
        stopBits=QSerialPort::OneStop;
    else if(ui->stopbitCb->currentText()=="1.5")
        stopBits=QSerialPort::OneAndHalfStop;
    else if(ui->stopbitCb->currentText()=="2")
        stopBits=QSerialPort::TwoStop;

    // 获取串口奇偶校验位
    if(ui->checkbitCb->currentText() == "none"){
        checkBits = QSerialPort::NoParity;
    }else if(ui->checkbitCb->currentText() == "奇校验"){
        checkBits = QSerialPort::OddParity;
    }else if(ui->checkbitCb->currentText() == "偶校验"){
        checkBits = QSerialPort::EvenParity;
    }else{

    }

    // 初始化串口属性,设置 端口号、波特率、数据位、停止位、奇偶校验位数
    serialPort->setPortName(ui->serailCb->currentText());
    serialPort->setBaudRate(baudRate);
    serialPort->setDataBits(dataBits);
    serialPort->setStopBits(stopBits);
    serialPort->setParity(checkBits);

    // 根据初始化好的串口属性,打开串口
    // 如果打开成功,反转打开按钮显示和功能。打开失败,无变化,并且弹出错误对话框。
    if(ui->openBt->text() == "打开串口"){
        if(serialPort->open(QIODevice::ReadWrite) == true){
            //QMessageBox::
            ui->openBt->setText("关闭串口");
            // 让端口号下拉框不可选,避免误操作(选择功能不可用,控件背景为灰色)
            ui->serailCb->setEnabled(false);
        }else{
            QMessageBox::critical(this, "错误提示", "串口打开失败!!!\r\n该串口可能被占用\r\n请选择正确的串口");
        }
        //statusBar 状态栏显示端口状态
        QString sm = "%1 OPENED, %2, 8, NONE, 1";
        QString status = sm.arg(serialPort->portName()).arg(serialPort->baudRate());
        lblPortState->setText(status);
        lblPortState->setStyleSheet("color:green");
    }else{
        serialPort->close();
        ui->openBt->setText("打开串口");
        // 端口号下拉框恢复可选,避免误操作
        ui->serailCb->setEnabled(true);
        //statusBar 状态栏显示端口状态
        QString sm = "%1 CLOSED";
        QString status = sm.arg(serialPort->portName());
        lblPortState->setText(status);
        lblPortState->setStyleSheet("color:red");
    }

}

/*发送数据*/
void MainWindow::on_sendBt_clicked()
{
    QByteArray array;

    //Hex复选框
    if(ui->chk_send_hex->checkState() == Qt::Checked){
        //array = QString2Hex(data);  //HEX 16进制
        array = QByteArray::fromHex(ui->sendEdit->toPlainText().toUtf8()).data();
    }else{
        //array = data.toLatin1();    //ASCII
        array = ui->sendEdit->toPlainText().toLocal8Bit().data();
    }

    if(ui->chk_send_line->checkState() == Qt::Checked){
        array.append("\r\n");
    }
    // 如发送成功,会返回发送的字节长度。失败,返回-1。
    int a = serialPort->write(array);
    // 发送字节计数并显示
    if(a > 0)
    {
        // 发送字节计数
        sendNum += a;
        // 状态栏显示计数值
        setNumOnLabel(lblSendNum, "S: ", sendNum);
    }
}
// 状态栏标签显示计数值
void MainWindow::setNumOnLabel(QLabel *lbl, QString strS, long num)
{
    // 标签显示
    QString strN;
    strN.sprintf("%ld", num);
    QString str = strS + strN;
    lbl->setText(str);
}
/*清空*/
void MainWindow::on_clearBt_clicked()
{
    ui->recvEdit->clear();
    // 清除发送、接收字节计数
    sendNum = 0;
    recvNum = 0;
    // 状态栏显示计数值
    setNumOnLabel(lblSendNum, "S: ", sendNum);
    setNumOnLabel(lblRecvNum, "R: ", recvNum);
}

void MainWindow::on_btnClearSend_clicked()
{
    ui->sendEdit->clear();
    // 清除发送字节计数
    sendNum = 0;
    // 状态栏显示计数值
    setNumOnLabel(lblSendNum, "S: ", sendNum);
}
// 定时发送开关 选择复选框
void MainWindow::on_chkTimSend_stateChanged(int arg1)
{
    // 获取复选框状态,未选为0,选中为2
    if(arg1 == 0){
        timSend->stop();
        // 时间输入框恢复可选
        ui->txtSendMs->setEnabled(true);
    }else{
        // 对输入的值做限幅,小于10ms会弹出对话框提示
        if(ui->txtSendMs->text().toInt() >= 10){
            timSend->start(ui->txtSendMs->text().toInt());// 设置定时时长,重新计数
            // 让时间输入框不可选,避免误操作(输入功能不可用,控件背景为灰色)
            ui->txtSendMs->setEnabled(false);
        }else{
            ui->chkTimSend->setCheckState(Qt::Unchecked);
            QMessageBox::critical(this, "错误提示", "定时发送的最小间隔为 10ms\r\n请确保输入的值 >=10");
        }
    }
}

如果一切顺利的话,点击左下角的三角符号进行编译运行就可以测试效果了。
QT初体验:手把手带你写一个自己的串口助手

打包可执行文件

当经过上面的步骤,编译后能成功运行。这时如果希望像我们平时用到的各种免安装的exe工具一样可以分享给其他小伙伴使用,就需要再打包一下才行。
传送门:QT如何打包生成独立可执行.exe文件

遗留问题
本来想定义一个定时器,然后在定时器触发的槽函数中进行扫描端口以达到自动更新端口的效果,就不需要在新插入串口设备时要点击一次检测串口的按钮。但如果槽函数中每次都需要先清除之前检测到的端口,再重新扫描。实际运行时就会出现刚想点开端口下拉列表还没选就刷新成COM1了,会干扰选择端口的操作。尝试了一番不能解决只能作罢。
定时器的槽函数类似下面这样(这是一个结果有问题的示范):

void MainWindow::slot_timCheckPort()
{
    if(ui->openBt->text() == "打开串口"){
        ui->serailCb->clear();
        //通过QSerialPortInfo查找可用串口
        foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
        {
            ui->serailCb->addItem(info.portName());
        }
    }
}

相关好文推荐:

https://blog.csdn.net/hanhui22/article/details/111594742

https://blog.csdn.net/weixin_46183891/article/details/124368488

https://blog.csdn.net/qq_30255657/article/details/125247114

https://blog.csdn.net/zzssdd2/category_10730183.html

https://blog.csdn.net/Mark_md/article/details/108928314文章来源地址https://www.toymoban.com/news/detail-454643.html

到了这里,关于QT初体验:手把手带你写一个自己的串口助手的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Golang项目实战】手把手教你写一个备忘录程序|附源码——建议收藏

    博主简介: 努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:数据结构、Go,Java等相关知识。 博主主页: @是瑶瑶子啦 所属专栏: Go语言核心编程 近期目标: 写好专栏的每一篇文章 前几天瑶瑶子学习了Go语言的基础语法知识,那么今天我们就写个

    2024年02月06日
    浏览(54)
  • 真手把手带你跑r3live by 自己设备 (二)

    前面详细讲了如何给电脑连接avia 和 海康工业相机,连接后想跑r3live呢,则需要标定好相机内参和avia 和相机之间的外参。 首先标定海康工业相机的内参,则可以用到livox_camera_lidar_calibration帮助我们得到相机内参 by cameraCalib.launch。官方写的很详细,直接跑就可以得到参数了。

    2024年02月17日
    浏览(53)
  • 手把手带你配置一个DHCP服务器

    最近部门内部成立一个网络兴趣小组,初衷是通过网络知识学习,在遇到网络问题时能够承担起一个与网络侧同学有效沟通的“连接人”的角色,求学这么多年其实也陆续学了不少的网络相关课程,本科的计算机网络、硕士的高等计网等,不过当时大多都停留在理论层面,趁

    2024年02月05日
    浏览(91)
  • 实战项目:手把手带你实现一个高并发内存池

    1.这个项目做的是什么? 当前项目是实现一个高并发的内存池,他的原型是google的一个开源项目tcmalloc,tcmalloc全称Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数(malloc、free)。 2.项目目标 模拟实现出一个自己的高

    2023年04月26日
    浏览(74)
  • 手把手自己制作一个飞书机器人

    飞书机器人 如果你想添加新的对话逻辑,你只需要在 ChatApi.py 中添加新的if条件判断语句即可。如果你能开发出新功能,请在仓库中提出Pull requests合并请求,我将感激不尽! 2023年9月1日更新 更新了对话机器人返回帮助界面功能,用户发送“帮助”,返回机器人使用说明 20

    2024年02月03日
    浏览(138)
  • 云原生应用开发,通过一个案例手把手带你入门

    针对云势所趋的市场发展。云计算和云原生应用已经成为主流技术趋势,学习这类技能有远见。可以开发出符合云原生运营模式的应用,满足企业业务发展需要。 实现资源的高效利用和弹性扩展。通过微服务架构、容器技术、弹性计算等手段,构建出计算资源利用高、扩展灵活的

    2024年02月06日
    浏览(69)
  • 手把手教你写go单元测试

    ​ 在 Go 语言中,单元测试是一种测试方法,用于验证代码的某个独立单元是否按预期功能,它的目的是确保代码的每个组成部分都在独立测试的情况下运行正常。 ​ 在我们对项目新增一个新功能时,最好就要养成写单元测试的好习惯,这样可以有助于提高我们代码的质量、

    2024年04月14日
    浏览(47)
  • 零基础手把手教你写NFT抢购软

    由于之前写过几篇文章,但是个人不是很满意,所以这次我打算把所有的知识汇总成一篇文章。可能会有很多地方写的不是很好,欢迎大家评论指出,我后续修正。 个人vx:http://wc.ljlju.cn/4ZGETK 我们在点击抢购按钮的时候,会向平台的服务器发送HTTP请求,这条HTTP请求会携带你

    2024年02月06日
    浏览(61)
  • 手把手教你实现一个JavaWeb项目:创建一个自己的网页博客系统(前端+后端)(一)

    一篇博客带你实现一个真正的项目!  先来看看它是什么样式的: 目录: 1、大体步骤🦖:         1、创建Maven项目🦕         2、引入依赖🦕         3、创建必要的目录🦕         4、编写代码🦕         5、打包部署(基于SmartTomcat)🦕         

    2024年02月06日
    浏览(57)
  • 手把手教你写stm32f103智能风扇

    本系统可以分为两个模式来进行运行,分别为手动模式和自动模式,同时,在上电进入系统后,还会有一个模式选择的界面产生。 模式选择:在此界面中,可以通过按键K1来控制模式选择,两个模式分别为手动模式和自动模式;通过按键K2可以进入模式。 手动模式:在手动模

    2023年04月17日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包