基于QT的跨平台串口调试工具开发实践

张开发
2026/4/13 9:25:14 15 分钟阅读

分享文章

基于QT的跨平台串口调试工具开发实践
1. 项目概述在嵌入式开发和硬件调试过程中串口通信是最基础也最常用的调试手段之一。虽然市面上已有不少现成的串口调试工具但往往存在功能单一、界面复杂或扩展性不足的问题。基于这个痛点我决定用QT框架开发一个开源的串口调试工具既能满足日常调试需求又能作为学习QT和串口通信的实践项目。这个工具的核心功能包括自动检测可用串口完整的串口参数配置波特率、数据位、校验位等支持ASCII和十六进制双模式收发带时间戳的收发记录数据统计功能收发字节计数周期发送和手动发送两种模式提示选择QT框架是因为其跨平台特性可以在Windows/Linux/macOS上运行而且对串口通信有良好的封装QSerialPort类。2. 开发环境准备2.1 工具链配置开发这个项目需要以下环境QT 5.15建议使用官方安装包包含Qt Creator IDE编译器WindowsMinGW或MSVCLinuxGCCmacOSClang虚拟串口工具调试用如Windows下的Virtual Serial Port Driver# Ubuntu下安装QT和开发工具 sudo apt install qt5-default qtcreator build-essential2.2 项目创建打开Qt Creator选择新建项目选择Application - Qt Widgets Application设置项目名称如SerialMaster和路径在Kit Selection页面选择你的编译器确保勾选了mainwindow.ui选项我们需要可视化设计界面注意创建项目时建议勾选使用影子构建这样可以保持源码目录整洁。3. 界面设计与实现3.1 主窗口布局基于QMainWindow设计主界面采用3行2列的网格布局------------------------------------------ | 串口配置区域 | 动态曲线显示区域 | ------------------------------------------ | 接收配置区域 | 接收数据显示区 | ------------------------------------------ | 发送配置区域 | 发送数据输入区 | ------------------------------------------关键控件说明串口配置区串口号下拉框QComboBox波特率选择QComboBox预设常用值数据位/校验位/停止位选择打开/关闭/刷新按钮接收显示区QTextBrowser支持富文本显示十六进制/ASCII切换复选框清空按钮发送输入区5个QLineEdit输入框对应5条预设命令发送按钮和自动发送复选框发送间隔设置QSpinBox3.2 UI与代码绑定建议在代码中初始化UI控件属性而不是完全依赖Qt Designer// 初始化串口参数下拉框 void MainWindow::InitUI() { // 波特率 ui-comboBoxBaud-addItems({9600, 19200, 38400, 57600, 115200}); ui-comboBoxBaud-setCurrentIndex(4); // 默认115200 // 数据位 ui-comboBoxData-addItems({5, 6, 7, 8}); ui-comboBoxData-setCurrentIndex(3); // 默认8 // 校验位 ui-comboBoxParity-addItems({None, Even, Odd}); // 停止位 ui-comboBoxStop-addItems({1, 1.5, 2}); }4. 核心功能实现4.1 串口管理4.1.1 自动检测串口使用QSerialPortInfo获取系统可用串口void MainWindow::SearchSerialPorts() { ui-comboBoxPort-clear(); foreach(const QSerialPortInfo info, QSerialPortInfo::availablePorts()) { QString portInfo info.portName(); if(!info.description().isEmpty()) portInfo ( info.description() ); ui-comboBoxPort-addItem(portInfo, info.portName()); } }4.1.2 串口开关控制关键点打开时设置所有参数连接readyRead信号到数据接收槽错误处理要完善void MainWindow::onOpenPort() { serialPort-setPortName(ui-comboBoxPort-currentData().toString()); if(serialPort-open(QIODevice::ReadWrite)) { // 波特率 serialPort-setBaudRate(ui-comboBoxBaud-currentText().toInt()); // 数据位 switch(ui-comboBoxData-currentIndex()) { case 0: serialPort-setDataBits(QSerialPort::Data5); break; // ...其他数据位设置 } // 连接数据接收信号 connect(serialPort, QSerialPort::readyRead, this, MainWindow::onReadyRead); // 更新UI状态 UpdatePortState(true); } else { QMessageBox::critical(this, 错误, QString(打开串口失败%1).arg(serialPort-errorString())); } }4.2 数据收发实现4.2.1 数据发送支持两种发送模式手动发送点击按钮发送对应输入框内容自动发送定时轮询发送选中的命令// 定时发送处理 void MainWindow::onTimeout() { if(!autoSend) return; for(int i 0; i 5; i) { if(sendEnable[i]) { SendData(sendLines[i]-text()); break; } } } // 实际发送函数 void MainWindow::SendData(const QString data) { QByteArray sendBytes; if(hexSend) { sendBytes QByteArray::fromHex(data.toLatin1()); } else { sendBytes data.toUtf8(); } qint64 written serialPort-write(sendBytes); if(written -1) { statusBar()-showMessage(发送失败: serialPort-errorString()); } else { sentBytes written; UpdateCounters(); } }4.2.2 数据接收关键功能支持ASCII/Hex双模式显示带时间戳的记录字节计数统计void MainWindow::onReadyRead() { QByteArray data serialPort-readAll(); if(data.isEmpty()) return; receivedBytes data.size(); QString displayText; if(hexDisplay) { displayText data.toHex( ).toUpper(); } else { displayText QString::fromUtf8(data); } // 添加时间戳 QString line QString([%1] RX: %2) .arg(QDateTime::currentDateTime().toString(hh:mm:ss.zzz)) .arg(displayText); // 在UI线程更新显示 QMetaObject::invokeMethod(ui-textBrowser, append, Qt::QueuedConnection, Q_ARG(QString, line)); UpdateCounters(); }5. 进阶功能实现5.1 数据持久化增加日志保存功能void MainWindow::onSaveLog() { QString fileName QFileDialog::getSaveFileName(this, 保存日志, , 文本文件 (*.txt);;所有文件 (*)); if(!fileName.isEmpty()) { QFile file(fileName); if(file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(file); out ui-textBrowser-toPlainText(); file.close(); } } }5.2 动态曲线显示使用QCustomPlot库实现数据可视化// 初始化曲线图 void MainWindow::InitPlot() { ui-plot-addGraph(); ui-plot-xAxis-setLabel(时间); ui-plot-yAxis-setLabel(值); // 设置滚动条 ui-plot-axisRect()-setupFullAxesBox(); connect(ui-plot-xAxis, SIGNAL(rangeChanged(QCPRange)), ui-plot-xAxis2, SLOT(setRange(QCPRange))); } // 更新曲线数据 void MainWindow::UpdatePlot(const QByteArray data) { static QVectordouble x, y; static double time 0; // 解析数据示例假设接收的是数值 double value data.toDouble(); x.append(time); y.append(value); time 0.1; // 限制数据点数量 if(x.size() 1000) { x.removeFirst(); y.removeFirst(); } ui-plot-graph(0)-setData(x, y); ui-plot-rescaleAxes(); ui-plot-replot(); }6. 常见问题与调试技巧6.1 串口无法打开可能原因及解决方案问题现象可能原因解决方案打开失败返回false串口被其他程序占用关闭占用程序或重启系统打开后无法收发数据参数配置错误检查波特率等参数是否匹配设备偶尔数据丢失缓冲区溢出增加读取频率或减小数据包大小6.2 数据收发异常调试建议先用简单的ASCII字符测试如发送123开启十六进制显示模式检查原始数据使用虚拟串口工具自发自收测试检查线缆和接口物理连接6.3 性能优化当处理高速数据时如115200以上波特率减少UI更新频率如每100ms更新一次使用QByteArray代替QString处理二进制数据关闭不必要的调试输出7. 项目扩展方向这个基础版本还可以进一步扩展多串口支持同时管理多个串口连接协议解析添加MODBUS、NMEA等常见协议解析脚本功能支持Python/Lua脚本自动化测试网络转发将串口数据通过TCP/UDP转发插件系统通过插件扩展功能// 插件系统接口示例 class SerialPluginInterface { public: virtual void onDataReceived(const QByteArray data) 0; virtual void onDataSent(const QByteArray data) 0; virtual QWidget *createControlWidget() 0; };8. 开发心得在开发这个工具的过程中有几个关键点值得分享线程安全串口的数据接收是在子线程中进行的所有UI操作必须通过信号槽或QMetaObject::invokeMethod转到主线程执行。资源管理QSerialPort对象要在同一个线程中创建和使用否则会出问题。我选择在主线程中创建和管理串口对象。性能平衡既要保证数据实时性又要避免频繁的UI更新导致界面卡顿。我的解决方案是使用一个100ms的定时器来批量更新UI。编码规范良好的代码组织很重要。我把不同功能模块分成单独的源文件serialmanager.cpp, datadisplayer.cpp等便于维护。这个项目虽然不大但涵盖了QT开发的多个重要方面UI设计、信号槽机制、多线程、硬件交互等。对于想学习QT或嵌入式开发的朋友实现一个自己的串口调试工具是个不错的练手项目。

更多文章