嵌入式开发者从0用QT开发串口调试助手(二)
本次主要实现识别串口号,可以打开和关闭串口,设置波特率,最后会提供这次的源码。下次计划:①可以正常收发②hex和ascii的转换③增加关闭串口时,自动识别串口的功能,省去需要关闭重启软件的步骤。
前言
本次主要实现识别串口号,可以打开和关闭串口,设置波特率,最后会提供这次的源码。
下次计划:
①可以正常收发
②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(如 QSerialPort、QSerialPortInfo 类),实现串口读写、参数配置等功能 |

-
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
更多推荐



所有评论(0)