Qt 开发实战:从零打造一个跨平台串口调试助手
1. 引言串口通信在嵌入式、物联网、工业自动化等领域应用广泛。虽然市面上已有成熟的串口调试工具如 SSCOM、Putty 等但自己动手用 Qt 开发一个既能深入理解串口通信原理又能按需定制功能还能积累宝贵的项目经验。本文带你从零开始使用 Qt6 C 开发一个具备串口扫描、参数配置、数据收发、十六进制支持、日志保存等功能的轻量级串口调试助手并在虚拟串口环境下完成联调测试。2. 开发环境操作系统Windows 10 / 11也支持 Linux / macOS开发工具Qt Creator 13.0.2Qt 版本Qt 6.7.2 / 6.8.0支持 Qt 5.15构建系统CMake也可用 qmake编译器MinGW 13.1.0 / MSVC 2022额外工具LLCOM串口调试助手用于联调、com0com虚拟串口驱动用于无硬件测试3. 项目结构textserial-assistant/ ├── CMakeLists.txt ├── main.cpp ├── mainwindow.h ├── mainwindow.cpp ├── mainwindow.ui ├── resources.qrc └── style/ └── style.qssCMakeLists.txt项目构建配置需添加SerialPort组件。main.cpp程序入口加载样式表。mainwindow.h / .cpp主窗口逻辑串口操作核心。mainwindow.uiUI 布局含按钮、下拉框、文本编辑区等。resources.qrc资源文件打包样式表。style/style.qssQSS 样式表可选用于美化界面。4. 功能列表✅ 自动扫描并显示可用串口✅ 配置波特率9600~115200✅ 打开/关闭串口带状态提示✅ 异步接收数据文本 / HEX 两种显示模式✅ 发送数据文本 / HEX 两种模式✅ 清空接收区✅ 保存接收数据到.txt文件✅ 状态栏显示收发字节数✅ QSS 美化界面可选5. 核心实现步骤5.1 CMake 配置添加 SerialPort 模块cmake# CMakeLists.txt 片段 find_package(Qt6 REQUIRED COMPONENTS Core Widgets SerialPort) target_link_libraries(serial-assistant PRIVATE Qt6::Core Qt6::Widgets Qt6::SerialPort )5.2 扫描串口并填充下拉框在MainWindow构造函数中cppforeach (const QSerialPortInfo info, QSerialPortInfo::availablePorts()) { ui-comboBoxPort-addItem(info.portName()); }5.3 打开/关闭串口cppvoid MainWindow::on_pushButtonOpen_clicked() { if (serial-isOpen()) { serial-close(); ui-pushButtonOpen-setText(打开串口); ui-comboBoxPort-setEnabled(true); ui-comboBoxBaud-setEnabled(true); statusBar()-showMessage(串口已关闭, 2000); return; } QString portName ui-comboBoxPort-currentText(); qint32 baudRate ui-comboBoxBaud-currentText().toInt(); serial-setPortName(portName); serial-setBaudRate(baudRate); serial-setDataBits(QSerialPort::Data8); serial-setParity(QSerialPort::NoParity); serial-setStopBits(QSerialPort::OneStop); serial-setFlowControl(QSerialPort::NoFlowControl); if (serial-open(QIODevice::ReadWrite)) { ui-pushButtonOpen-setText(关闭串口); ui-comboBoxPort-setEnabled(false); ui-comboBoxBaud-setEnabled(false); connect(serial, QSerialPort::readyRead, this, MainWindow::handleReadyRead); statusBar()-showMessage(QString(已打开 %1 %2).arg(portName).arg(baudRate), 3000); } else { QMessageBox::critical(this, 错误, 打开串口失败 serial-errorString()); } }5.4 接收数据文本 / HEX 切换cppvoid MainWindow::handleReadyRead() { QByteArray data serial-readAll(); if (data.isEmpty()) return; if (ui-checkBoxHexDisplay-isChecked()) { QString hex data.toHex( ).toUpper(); ui-textEditReceive-appendPlainText(hex); } else { QString text QString::fromUtf8(data); ui-textEditReceive-appendPlainText(text); } }5.5 发送数据文本 / HEX 切换cppvoid MainWindow::on_pushButtonSend_clicked() { if (!serial-isOpen()) { QMessageBox::warning(this, 提示, 请先打开串口); return; } QString input ui-lineEditSend-text(); if (input.isEmpty()) return; QByteArray data; if (ui-checkBoxHexSend-isChecked()) { QString hex input; hex.remove( ); hex.remove(,); data QByteArray::fromHex(hex.toUtf8()); if (data.isEmpty()) { QMessageBox::warning(this, 警告, 无效的十六进制格式); return; } } else { data input.toUtf8(); } qint64 written serial-write(data); if (written -1) { QMessageBox::critical(this, 错误, 发送失败 serial-errorString()); } else { statusBar()-showMessage(QString(发送 %1 字节).arg(written), 2000); } }5.6 清空接收区cppvoid MainWindow::on_pushButtonClear_clicked() { ui-textEditReceive-clear(); }5.7 保存接收数据cppvoid MainWindow::on_pushButtonSave_clicked() { QString content ui-textEditReceive-toPlainText(); if (content.isEmpty()) { QMessageBox::information(this, 提示, 接收区为空); return; } QString fileName QFileDialog::getSaveFileName(this, 保存接收数据, QDateTime::currentDateTime().toString(yyyyMMdd_hhmmss) .txt, 文本文件 (*.txt)); if (fileName.isEmpty()) return; QFile file(fileName); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(file); out content; file.close(); statusBar()-showMessage(数据已保存, 3000); } else { QMessageBox::critical(this, 错误, 无法写入文件 file.errorString()); } }5.8 QSS 全局样式可选在resources.qrc中添加style/style.qss然后在main.cpp中加载cppint main(int argc, char *argv[]) { QApplication a(argc, argv); QFile file(:/style/style.qss); if (file.open(QFile::ReadOnly)) { QString style QTextStream(file).readAll(); a.setStyleSheet(style); file.close(); } MainWindow w; w.show(); return a.exec(); }样式表示例style.qsscssQMainWindow { background-color: #f5f5f5; } QPushButton { background-color: #5c9eff; color: white; border: none; border-radius: 6px; padding: 8px 16px; font-weight: bold; } QPushButton:hover { background-color: #3a7bd5; } QPushButton:pressed { background-color: #2c5fa3; } QComboBox, QLineEdit, QPlainTextEdit { border: 1px solid #ccc; border-radius: 4px; padding: 4px 6px; background-color: white; } QPlainTextEdit, QLineEdit { font-family: Consolas, monospace; font-size: 11px; }6. 测试验证6.1 虚拟串口环境搭建使用com0com创建一对虚拟串口如 COM5 ↔ COM6然后用LLCOM开源的 Lua 脚本串口工具作为对端。打开 com0com 的setupc命令行执行textinstall PortNameCOM5 PortNameCOM6在设备管理器中确认 COM5、COM6 已出现无黄色感叹号。6.2 联调测试步骤启动 Qt 程序选择COM5波特率 115200点击“打开”。启动 LLCOM选择COM6相同波特率点击“打开”。在 Qt 发送区输入Hello点击发送 → LLCOM 接收区应显示Hello。在 LLCOM 发送区输入World→ Qt 接收区应显示World。勾选 Qt 的“十六进制发送”输入01 02 03 FF发送 → LLCOM 勾选 HEX 显示应看到相同内容。点击“清空接收区” → 内容清空。点击“保存接收区” → 弹出保存对话框保存为.txt文件打开查看内容正确。7. 运行效果截图8. 常见问题与解决方案问题解决方法打开串口失败权限错误Windows以管理员身份运行Linux将用户加入dialout组接收区显示乱码检查波特率、数据位等参数是否匹配尝试 HEX 显示模式虚拟串口驱动安装失败使用com0com 2.2.2.0版本或禁用驱动签名临时打包发布后缺少 DLL使用windeployqt工具自动复制依赖库9. 后续拓展方向多线程接收将串口读取移到子线程防止界面卡顿适合大数据量场景。波形显示集成Qt Charts将传感器数值实时绘制曲线。定时自动发送增加QTimer周期性发送预设指令。协议解析支持 Modbus RTU、自定义帧头帧尾过滤。配置文件保存用QSettings记住最近使用的串口号和波特率。10. 项目总结通过本项目你不仅掌握 Qt 串口通信的完整开发流程还熟悉了 CMake 管理项目、QSS 美化界面、资源文件使用、虚拟串口调试等实用技能。整个项目代码清晰、可扩展性强可作为毕业设计、简历项目或日常开发工具。**技术栈**C17 / Qt6 / QSerialPort / CMake / QSS