前言

本次主要实现识别串口号,可以打开和关闭串口,设置波特率,最后会提供这次的源码。
下次计划:
        ①可以正常收发

        ②hex和ascii的转换

        ③增加关闭串口时,自动识别串口的功能,省去需要关闭重启软件的步骤

一、安装QT串口模块​​​​

        还是老样子,下载比较慢

  • 找到QT维护工具(MaintenanceTool.exe
    打开 Qt 安装目录(默认是 C:\Qt,如果按照我(一)里面写的步骤安装,是在D:\Qt,找对应的文件夹)。双击打开

  • 登录QT账号(必须)
    账号下载的时候已经登陆过,默认是存在的,直接下一步即可

  • 勾选串口模块并安装

    • 选择添加或移除的组件,下一步

      在组件列表中,找到你当前项目使用的 Qt 版本,必须和你 Qt Creator 中选择的套件版本一致。(进来以后会默认勾选自己当前的版本的)

      模块名是「Qt Serial Port」,勾选这个即可,我额外勾选了logs和build tools,其实还想勾选别的,但是太大了,珍惜电脑剩下的小小空间。直接下一步,然后更新即可,安装速度很慢,耐心等待,我大概安装了半个小时

二、串口基本信息窗口搭建

  • UI设计
     


    双击UI界面,进入,并放置对应需要的按键、标签、下拉框等

并在对象查看器内,修改需要用到的每个器件的名字(只需要修改那些需要调用器件的名字就行,如果只是标签显示用,不需要代码调用,就不用修改)

三、代码实现

  • 1.  QT.pro

      Qt .pro 配置文件中引入串口模块

core Qt 核心模块 提供 Qt 基础功能(如字符串、容器、事件循环等),所有 Qt 项目默认必选
gui Qt 图形界面模块 提供基础 GUI 相关功能(如窗口、控件渲染等),GUI 项目必备
serialport Qt 串口模块 提供串口通信相关 API(如 QSerialPortQSerialPortInfo 类),实现串口读写、参数配置等功能

  • 2.  头文件

       头文件 mainwindow.h 的核心是声明,它只定义 “存在什么”,不写具体的执行逻辑

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

// 引入Qt主窗口类的头文件:QMainWindow是Qt提供的标准主窗口基类
// 提供菜单栏、工具栏、状态栏等主窗口必备功能,我们的自定义窗口继承自它
#include <QMainWindow>

// 引入Qt串口核心操作类的头文件:必须包含,否则无法使用串口的打开、关闭、读写等功能
// QSerialPort是串口操作的核心类,负责串口参数配置、数据收发等核心逻辑
#include <QSerialPort>  // 串口相关头文件:必须包含,否则无法使用Qt串口功能

// 引入Qt串口信息查询类的头文件:用于获取系统中可用的串口列表、串口硬件信息等
// 比如自动扫描COM1/COM2、/dev/ttyUSB0等可用串口,依赖该类实现
#include <QSerialPortInfo>// 串口信息头文件:用于获取系统可用串口列表

// Qt命名空间开始宏:用于隔离Qt自动生成的Ui代码,避免与用户自定义代码发生命名冲突
// 该宏配合QT_END_NAMESPACE使用,包裹Qt Designer自动生成的Ui命名空间
QT_BEGIN_NAMESPACE
// 声明Ui命名空间下的MainWindow类:该类由Qt Designer根据.ui文件自动生成
// 用于管理可视化界面中的所有控件(如下拉框、按钮、文本框等),无需用户手动修改
namespace Ui {
class MainWindow;
}
// Qt命名空间结束宏:标志着Qt自动生成的Ui命名空间声明结束
QT_END_NAMESPACE




// 自定义主窗口类声明:继承自QMainWindow,拥有QMainWindow的所有特性
// 这是我们串口助手的主窗口类,所有业务逻辑(串口配置、数据收发等)都围绕该类展开
class MainWindow : public QMainWindow
{
    Q_OBJECT// Q_OBJECT宏:必须添加,否则无法使用Qt的信号与槽机制

public:
    // 类的构造函数声明:用于创建MainWindow对象时初始化窗口
    // 参数parent:父窗口指针,默认值为nullptr(表示无父窗口,该窗口是顶级窗口)
    // 遵循Qt父子对象机制:父窗口销毁时,会自动销毁子窗口,避免内存泄漏
    MainWindow(QWidget *parent = nullptr);// 构造函数:parent为父窗口指针(NULL表示无父窗口)

    // 类的析构函数声明:用于销毁MainWindow对象时释放占用的资源
    // 主要释放手动分配的内存(如ui指针、串口对象指针等),避免内存泄漏
    ~MainWindow();// 析构函数:用于释放内存,避免内存泄漏

private slots:
    // 自定义槽函数声明:按钮点击、串口接收数据等事件的处理函数
    // 打开/关闭串口按钮点击事件
    void onBtnOpenSerialClicked();
    // 发送数据按钮点击事件
    void onBtnSendDataClicked();
    // 串口有数据接收时的处理函数(绑定readyRead信号)
    void onSerialPortReadyRead();

private:
    Ui::MainWindow *ui;
    QSerialPort *serialPort;         // 串口对象指针:所有串口操作的核心对象

    // 初始化各参数下拉框的函数声明:拆分函数便于维护,每个函数只做一件事
    void initSerialPortComboBox();   // 初始化串口号下拉框(自动识别系统串口)
    void initBaudRateComboBox();     // 初始化波特率下拉框
    void initParityComboBox();       // 初始化校验位下拉框
    void initDataBitsComboBox();     // 初始化数据位下拉框
    void initStopBitsComboBox();     // 初始化停止位下拉框
    void initFlowControlComboBox();  // 初始化流控位下拉框
};
#endif // MAINWINDOW_H
  • 3.  源文件

源文件这里有两个,main.cpp 和 mainwindow.cpp

对比维度 main.cpp mainwindow.cpp
核心定位 程序全局入口文件(唯一) 主窗口功能实现文件(自定义)
核心作用 启动 Qt 应用、初始化全局环境、启动事件循环 实现 MainWindow 类的所有业务逻辑(串口配置、按钮点击、数据收发等)
对应头文件 无需专属 .h 文件(直接编写逻辑) 对应 mainwindow.h(实现头文件中的声明)
代码特性 代码极少、逻辑固定、几乎无需大幅修改 代码量大、逻辑灵活、随业务需求扩展(如串口扫描、数据收发等)
依赖关系 依赖 QApplication 和 MainWindow 类 依赖 mainwindow.h、UI 头文件和 Qt 功能模块(如 QSerialPort

数据收发功能、 HEX/ASCII格式切换、日志记录等模块的预留设计说明。
main.cpp

#include "mainwindow.h" // 引入自定义主窗口类的头文件,这个头文件通常由 Qt Creator 自动生成,
                        // 里面声明了 MainWindow 类(继承自 QMainWindow)

#include <QApplication> // 引入 Qt 应用程序核心类的头文件,QApplication 是所有 Qt GUI 程序的入口核心

int main(int argc, char *argv[])    // main 函数:C/C++ 程序的入口点,argc 是命令行参数个数,argv 是参数数组
{
    // 1. 创建 QApplication 对象 a:这是 Qt GUI 程序的核心,负责管理应用程序的全局资源、
    // 事件循环、命令行参数解析等,必须在创建任何 GUI 组件之前实例化
    QApplication a(argc, argv);
    // 2. 创建自定义主窗口对象 w:MainWindow 是你自己(或 Qt Creator)定义的主窗口类,
    // 继承自 QMainWindow,包含窗口的UI布局、控件、业务逻辑等
    MainWindow w;
    // 3. 显示主窗口:调用 QWidget 的 show() 方法,让主窗口从隐藏状态变为可见状态
    w.show();
    // 4. 启动应用程序的事件循环:a.exec() 会进入 Qt 的事件循环,一直运行直到调用 quit() 或窗口关闭,
    // 这个方法会处理用户的鼠标、键盘等事件,是 GUI 程序能持续运行的关键;
    // 返回值会作为 main 函数的返回值,通常表示程序的退出状态0
    return a.exec();
}

mainwindow.cpp

// 引入自定义主窗口类的头文件,声明了 MainWindow 类的结构
#include "mainwindow.h"
// 引入 Qt Creator 自动生成的 UI 界面头文件,里面包含了 Ui::MainWindow 类(由 .ui 文件编译生成)
#include "ui_mainwindow.h"

//#include "QSerialPort.h"        //串口
//#include "QSerialPortInfo.h"    //串口信息

// 调试用头文件:qDebug()输出日志
#include <QDebug>
// 消息框头文件:弹出提示、警告、错误窗口
#include <QMessageBox>

// MainWindow 类的构造函数:创建主窗口对象时执行,用于初始化窗口
// 参数 parent 是父窗口指针,遵循 Qt 的父子对象机制(父对象销毁时会自动销毁子对象)
MainWindow::MainWindow(QWidget *parent)
    // 初始化列表:先调用父类 QMainWindow 的构造函数,将 parent 传递给父类
    : QMainWindow(parent)
    // 初始化 ui 指针:创建 Ui::MainWindow 对象,用于管理界面上的控件(如按钮、文本框等)
    , ui(new Ui::MainWindow)
{
    // 关键步骤:将 Ui::MainWindow 中定义的界面布局、控件等“挂载”到当前 MainWindow 对象上
    // 执行后,你在 Qt Designer 中拖放的控件就会显示在主窗口中,且可以通过 ui->xxx 访问这些控件
    ui->setupUi(this);

    // 1. 创建串口对象:绑定父窗口(this),Qt父子对象机制会自动释放该对象,避免内存泄漏
    serialPort = new QSerialPort(this);

    // 2. 初始化所有参数下拉框:按顺序初始化,确保界面加载时已有默认选项
    initSerialPortComboBox();   // 串口号
    initBaudRateComboBox();     // 波特率
    initParityComboBox();       // 校验位
    initDataBitsComboBox();     // 数据位
    initStopBitsComboBox();     // 停止位
    initFlowControlComboBox();  // 流控位

    // 3. 信号与槽绑定:将控件事件与处理函数关联
    // 绑定“打开串口”按钮的点击信号到onBtnOpenSerialClicked函数
    connect(ui->btnOpenSerial, &QPushButton::clicked, this, &MainWindow::onBtnOpenSerialClicked);
    // 绑定“发送数据”按钮的点击信号到onBtnSendDataClicked函数
    connect(ui->btnSendData, &QPushButton::clicked, this, &MainWindow::onBtnSendDataClicked);
    // 4. 绑定串口接收数据信号:当串口有数据可读时,触发onSerialPortReadyRead函数
    connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::onSerialPortReadyRead);
}

// MainWindow 类的析构函数:销毁主窗口对象时执行,用于释放资源
MainWindow::~MainWindow()
{
    // 安全操作:如果串口处于打开状态,先关闭再销毁
    if (serialPort->isOpen()) {
        serialPort->close();
    }

    // 释放 ui 指针指向的 Ui::MainWindow 对象,避免内存泄漏
    // 因为 ui 是在构造函数中用 new 创建的,必须手动 delete
    delete ui;
}

// ===================== 下拉框初始化函数(核心)=====================
/**
 * @brief 初始化串口号下拉框
 * @details 自动遍历系统中所有可用的串口,填充到下拉框中,无需手动输入
 *          适配Windows(COM1/COM2)、Linux(/dev/ttyUSB0)、macOS
 */
void MainWindow::initSerialPortComboBox()
{
    // 清空下拉框原有内容:避免重复加载串口(比如窗口刷新时)
    ui->cbbSerialPort->clear();

    // 遍历系统所有可用串口:QSerialPortInfo::availablePorts()返回所有串口信息列表
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        // 向下拉框添加串口名(如COM1、/dev/ttyUSB0)
        // info.portName()获取串口的名称,是串口操作的唯一标识
        ui->cbbSerialPort->addItem(info.portName());
    }

    // 容错处理:如果系统无串口,提示用户
    if (ui->cbbSerialPort->count() == 0) {
        ui->cbbSerialPort->addItem("无串口");
        // 禁用打开串口按钮,避免用户点击报错
        ui->btnOpenSerial->setEnabled(false);
    }
}

/**
 * @brief 初始化波特率下拉框
 * @details 添加常用波特率选项,绑定Qt串口枚举值(避免手动转换数值)
 *          默认选中9600(串口通信最常用)
 */
void MainWindow::initBaudRateComboBox()
{
    // 清空原有选项:防止重复添加
    ui->cbbBaudRate->clear();

    // addItem(显示文本, 绑定数据):第二个参数是Qt串口库的波特率枚举值
    // 后续获取时直接用currentData()取枚举值,无需手动转int,更安全
    ui->cbbBaudRate->addItem("1200", QSerialPort::Baud1200);
    ui->cbbBaudRate->addItem("2400", QSerialPort::Baud2400);
    ui->cbbBaudRate->addItem("4800", QSerialPort::Baud4800);
    ui->cbbBaudRate->addItem("9600", QSerialPort::Baud9600); // 默认选项
    ui->cbbBaudRate->addItem("19200", QSerialPort::Baud19200);
    ui->cbbBaudRate->addItem("38400", QSerialPort::Baud38400);
    ui->cbbBaudRate->addItem("115200", QSerialPort::Baud115200);

    // 设置默认选中项:索引3对应9600(索引从0开始)
    ui->cbbBaudRate->setCurrentIndex(3);
}

/**
 * @brief 初始化校验位下拉框
 * @details 校验位用于检错,常用“无校验”,绑定Qt枚举值方便后续配置
 */
void MainWindow::initParityComboBox()
{
    ui->cbbParity->clear();

    // addItem(显示文本, Qt枚举值):
    // NoParity:无校验(最常用)、OddParity:奇校验、EvenParity:偶校验
    // MarkParity:标记校验、SpaceParity:空格校验(较少用)
    ui->cbbParity->addItem("无", QSerialPort::NoParity);       // 默认
    ui->cbbParity->addItem("奇校验", QSerialPort::OddParity);
    ui->cbbParity->addItem("偶校验", QSerialPort::EvenParity);
    ui->cbbParity->addItem("标记校验", QSerialPort::MarkParity);
    ui->cbbParity->addItem("空格校验", QSerialPort::SpaceParity);

    // 默认选中“无校验”(索引0)
    ui->cbbParity->setCurrentIndex(0);
}

/**
 * @brief 初始化数据位下拉框
 * @details 数据位是每个字符的二进制位数,常用8位
 */
void MainWindow::initDataBitsComboBox()
{
    ui->cbbDataBits->clear();

    // Data5/Data6/Data7/Data8:分别对应5/6/7/8位数据位
    ui->cbbDataBits->addItem("5", QSerialPort::Data5);
    ui->cbbDataBits->addItem("6", QSerialPort::Data6);
    ui->cbbDataBits->addItem("7", QSerialPort::Data7);
    ui->cbbDataBits->addItem("8", QSerialPort::Data8); // 默认(最常用)

    // 默认选中8位数据位(索引3)
    ui->cbbDataBits->setCurrentIndex(3);
}

/**
 * @brief 初始化停止位下拉框
 * @details 停止位用于标识一个字符的结束,常用1位
 */
void MainWindow::initStopBitsComboBox()
{
    ui->cbbStopBits->clear();

    // OneStop:1位停止位(最常用)、OneAndHalfStop:1.5位、TwoStop:2位
    ui->cbbStopBits->addItem("1", QSerialPort::OneStop);          // 默认
    ui->cbbStopBits->addItem("1.5", QSerialPort::OneAndHalfStop);
    ui->cbbStopBits->addItem("2", QSerialPort::TwoStop);

    // 默认选中1位停止位(索引0)
    ui->cbbStopBits->setCurrentIndex(0);
}

/**
 * @brief 初始化流控位下拉框
 * @details 流控用于控制数据传输速率,防止数据丢失,常用“无流控”
 */
void MainWindow::initFlowControlComboBox()
{
    ui->cbbFlowControl->clear();

    // NoFlowControl:无流控(最常用)、HardwareControl:硬件流控(RTS/CTS)
    // SoftwareControl:软件流控(XON/XOFF)
    ui->cbbFlowControl->addItem("无", QSerialPort::NoFlowControl); // 默认
    ui->cbbFlowControl->addItem("硬件流控", QSerialPort::HardwareControl);
    ui->cbbFlowControl->addItem("软件流控", QSerialPort::SoftwareControl);

    // 默认选中“无流控”(索引0)
    ui->cbbFlowControl->setCurrentIndex(0);
}

// ===================== 按钮点击事件处理函数 =====================
/**
 * @brief 打开/关闭串口按钮点击事件
 * @details 核心逻辑:判断串口状态,已打开则关闭,未打开则配置参数并打开
 */
void MainWindow::onBtnOpenSerialClicked()
{
    // 第一步:判断串口当前状态
    if (serialPort->isOpen()) {
        // 情况1:串口已打开 → 关闭串口
        serialPort->close();
        // 更新按钮文本为“打开串口”,提示用户当前状态
        ui->btnOpenSerial->setText("打开串口");
        // 弹出提示框:告知用户串口已关闭
        QMessageBox::information(this, "提示", "串口已关闭");
        // 提前返回:避免执行后续的“打开串口”逻辑
        return;
    }

    // 情况2:串口未打开 → 配置参数并尝试打开
    // 1. 获取用户选中的所有串口参数(从下拉框中取值)
    QString portName = ui->cbbSerialPort->currentText(); // 串口号(如COM1)
    // 波特率:currentData()获取绑定的枚举值,static_cast转换为Qt波特率类型
    QSerialPort::BaudRate baudRate = static_cast<QSerialPort::BaudRate>(ui->cbbBaudRate->currentData().toInt());
    // 校验位:同理转换为Qt校验位类型
    QSerialPort::Parity parity = static_cast<QSerialPort::Parity>(ui->cbbParity->currentData().toInt());
    // 数据位:同理转换为Qt数据位类型
    QSerialPort::DataBits dataBits = static_cast<QSerialPort::DataBits>(ui->cbbDataBits->currentData().toInt());
    // 停止位:同理转换为Qt停止位类型
    QSerialPort::StopBits stopBits = static_cast<QSerialPort::StopBits>(ui->cbbStopBits->currentData().toInt());
    // 流控位:同理转换为Qt流控位类型
    QSerialPort::FlowControl flowControl = static_cast<QSerialPort::FlowControl>(ui->cbbFlowControl->currentData().toInt());

    // 2. 配置串口参数:按顺序设置,必须在打开串口前完成
    serialPort->setPortName(portName);        // 设置串口号(核心,唯一标识串口)
    serialPort->setBaudRate(baudRate);        // 设置波特率
    serialPort->setParity(parity);            // 设置校验位
    serialPort->setDataBits(dataBits);        // 设置数据位
    serialPort->setStopBits(stopBits);        // 设置停止位
    serialPort->setFlowControl(flowControl);  // 设置流控位

    // 3. 尝试打开串口:QIODevice::ReadWrite表示读写模式(最常用)
    if (serialPort->open(QIODevice::ReadWrite)) {
        // 打开成功:更新按钮文本,弹出成功提示
        ui->btnOpenSerial->setText("关闭串口");
        QMessageBox::information(this, "提示",
                                 "串口打开成功!\n串口:" + portName + "\n波特率:" + ui->cbbBaudRate->currentText());
    } else {
        // 打开失败:弹出错误提示,显示失败原因(errorString()返回具体错误信息)
        QMessageBox::critical(this, "错误", "串口打开失败!\n原因:" + serialPort->errorString());
    }
}

/**
 * @brief 发送数据按钮点击事件
 * @details 核心逻辑:校验串口状态 → 获取输入数据 → 发送数据 → 容错处理
 */
void MainWindow::onBtnSendDataClicked()
{
    // 第一步:校验串口状态,未打开则提示用户
    if (!serialPort->isOpen()) {
        QMessageBox::warning(this, "警告", "请先打开串口!");
        return;
    }
/*
    // 第二步:获取输入框中的数据
    QString sendText = ui->leSendData->text();
    // 容错:如果输入为空,提示用户
    if (sendText.isEmpty()) {
        QMessageBox::warning(this, "警告", "发送数据不能为空!");
        return;
    }

    // 第三步:发送数据
    // QString转QByteArray:串口发送的是字节数据,需转换编码(Utf8兼容大部分场景)
    QByteArray sendData = sendText.toUtf8();
    // write()返回发送的字节数,-1表示发送失败
    qint64 sendBytes = serialPort->write(sendData);

    // 第四步:容错处理
    if (sendBytes == -1) {
        QMessageBox::critical(this, "错误", "数据发送失败!");
    } else {
        // 调试日志:输出发送的数据和字节数,方便调试
        qDebug() << "[发送数据]:" << sendText << " | 发送字节数:" << sendBytes;
        // 可选:清空输入框,方便下次输入
        // ui->leSendData->clear();
    }
*/
}

// ===================== 串口接收数据处理函数 =====================
/**
 * @brief 串口接收数据处理函数
 * @details 当串口有数据可读时触发,读取所有数据并显示到接收文本框
 */

void MainWindow::onSerialPortReadyRead()
{
/*
    // 读取串口所有可用数据:readAll()返回接收到的字节数组
    QByteArray recvData = serialPort->readAll();

    // 容错:如果数据为空,直接返回
    if (recvData.isEmpty()) {
        return;
    }

    // 显示数据到接收文本框:appendPlainText是追加模式,保留历史数据
    // 拼接“[接收]”标识,方便区分发送/接收数据
    ui->teRecvData->appendPlainText("[接收]:" + QString(recvData));

    // 调试日志:输出接收的数据,方便调试
    qDebug() << "[接收数据]:" << recvData;
*/
}

四、源码


        链接: https://pan.baidu.com/s/1XeQRw5DANoLTLCt3gkYIUg?pwd=ffll 提取码: ffll 


Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐