资讯动态

Qt实战:从零开发串口调试助手,详解QSerialPort通信与界面设计

发布时间:2026/9/2 11:49:09 来源:尧图企业网站定制
简介一份基于Qt的串口调试助手完整源码项目面向Qt初学者与嵌入式调试人员可帮助理解串口通信原理、Qt界面开发流程及信号槽应用。资源压缩包共31个文件包含9个C源码文件、9个头文件、3个UI界面设计文件以及图标、背景图片、文本说明等辅助资源整体大小约234KB目录结构清晰规整便于按模块查找。该项目已吸引1164人学习下载。代码覆盖工程创建、串口参数配置、数据收发、接收区实时显示、日志记录等核心功能同时附带QextSerialPort扩展源码与自定义界面样式适合初学者对照实际代码掌握Qt事件循环、串口读写机制以及完整工具类项目的构建思路。从界面布局到异常处理均有体现既可用于课程设计或毕业设计参考也可作为日常串口调试工具的二次开发基础模板。 串口调试助手这东西做嵌入式开发的基本天天都在用。以前我也一直是打开现成工具直接用直到有一次需要定制一些特殊功能才发现现成的工具有多不顺手于是决定用 Qt 从零开始写一个属于自己的串口调试助手。这版 V1.0 是我第一个完整跑通的版本功能不算多但界面、串口收发、文件操作这些核心链路全走通了非常适合想入门 Qt 开发、或者对串口通信原理想有直观理解的人参考。这个项目用到的核心技术点其实很集中Qt 的 Widgets 界面框架、QSerialPort 串口模块、信号槽机制再加一点 QFile 文件读写和 QMessageBox 交互弹窗。做出来的效果就是一个小巧的桌面程序左侧配置串口参数右侧显示收发数据中间是发送区域整体布局很典型工程里的数据结构也拆得足够简单读起来不会觉得累。1. 整体设计思路与方案拆解1.1 为什么选 Qt 而不是其他框架做串口调试工具技术选型上无非就是几条路C# 的 WinForms、Python 的 PySerial 加 tkinter、还有 Qt。我最后选了 Qt主要是因为跨平台这一点。平时我既要在 Windows 上调试偶尔也要把程序拿到 Linux 机器上跑用 Qt 写一遍两边都能编译省下不少重复工作量。另外 Qt 的 QSerialPort 模块本身封装得很好。这个类把串口枚举、打开关闭、参数配置、数据读写全包了信号槽机制用来处理异步收数据简直顺手。你要知道串口收数据是事件驱动的数据什么时候来你根本不知道如果用 C# 或者原生 C 写得自己开线程去轮询或者阻塞读代码复杂不说还容易出各种玄学问题。Qt 里一个 readyRead 信号就把这事解决了数据来了自动触发槽函数完美契合图形界面程序的事件模型。图形界面的开发效率也是 Qt 的一大优势。Qt Designer 里拖拖拽拽就能搭出界面布局生成的 .ui 文件可以直接编译进工程里不用像原生 Win32 那样一个控件一个控件地手动创建和排版。虽然我实际开发中更喜欢手写代码搭界面但 Qt Designer 做原型非常快这个组合拳打下来开发效率比纯手写高出不止一个档次。1.2 V1.0 的功能边界与架构划分这版 V1.0 我给自己定的功能边界很明确不做大而全只做核心闭环端口配置、打开关闭、数据接收显示、数据发送、清空日志、保存接收数据到文件。定时发送这个功能其实在开发后期也顺手加上了属于意外之喜。整个程序按界面层、业务层、数据层三层来划分。界面层负责所有控件的布局和交互比如按钮点击、选项变更这些事件都在这层处理业务层封装了串口操作的完整流程包括端口扫描、参数校验、数据收发这一层对外提供简单的方法调用界面层不需要关心底层细节数据层处理文件的读写操作负责把保存数据写到磁盘、把文件内容读入发送区。这样分层的好处是后期想加功能时改动范围可控。比如 V1.0 只支持文本模式收发以后想加十六进制收发只需要在业务层增加编码转换逻辑界面层加一个模式切换的控件就行不需要推翻重写。我在项目里把 MainWindow 类控制得比较薄核心串口逻辑单独抽了一个 SerialManager 类出来虽然这个类在 V1.0 里还不算特别大但给后续扩展留好了空间。2. 开发环境准备与项目搭建2.1 Qt 安装与编译套件选择做这个项目我用的环境是 Qt 5.15.2 Qt Creator MinGW 64-bit。Qt 的版本选择上我特意避开了 Qt 6 用 Qt 5原因是 QSerialPort 模块在 Qt 5 里非常成熟稳定网上能搜到的参考代码也绝大部分是基于 Qt 5 写的对新手来说遇到问题更容易找到解决方案。等熟练了再迁移 Qt 6QSerialPort 的 API 变化很小迁移成本很低。Qt 官方下载页面是能拿到安装包的安装的时候要注意一点组件勾选时要选对编译器版本。如果跟我一样用 Windows建议选 MinGW 版本因为 Qt 官方安装包自带的 MinGW 编译器和 Qt Creator 是配套好的装完就能直接用省去自己配置的麻烦。如果你电脑上已经有 Visual Studio也可以选 MSVC 版本但那个需要额外装 Windows SDK新手很容易在环境问题上卡半天。安装完成后建议第一时间验证环境是否正常打开 Qt Creator新建一个空工程随便拖一个按钮进去编译运行。如果这个流程顺利走通说明开发环境没问题可以正式开始写串口助手了。这一步很关键很多人一上来就写串口代码结果编译报错后分不清是自己代码问题还是环境问题排查起来非常浪费时间。2.2 工程文件配置与串口模块引入Qt 的工程文件是 .pro 文件相当于构建配置中心。新建工程完成后我需要手动添加串口模块支持在 .pro 文件里加一行QT core gui serialportcore和gui是默认就有的serialport需要手动加。这行配置代表让 Qt 编译系统链接串口模块的库不加这一行你即使#include QSerialPort也会报“找不到头文件”的错这个坑很多新手会踩。更高版本的 Qt 工程可能还用 CMake 管理那就在 CMakeLists.txt 里加find_package(Qt5 COMPONENTS Core Gui SerialPort REQUIRED) target_link_libraries(你的工程名 Qt5::Core Qt5::Gui Qt5::SerialPort)CMake 的写法虽然不同但逻辑一样都是把 SerialPort 模块链接进来。我自己喜欢 .pro 的方式Qt Creator 对 .pro 的支持更原生语法也更简单。还有一个头文件包含的问题需要注意。QSerialPort 的头文件引用要写成#include QSerialPort #include QSerialPortInfoQSerialPort是串口对象本身QSerialPortInfo是用来枚举系统串口信息的辅助类。两个头文件要同时引入因为查询可用端口列表时必须用 QSerialPortInfo。3. 界面设计与交互实现3.1 用代码搭界面还是用 Qt Designer这个问题做 Qt 开发的人基本都会纠结过。我用实际体验来说控件多、布局复杂的界面用 Qt Designer 拖拽更快控件少、布局规整的界面直接手写代码更清爽。串口助手的界面属于中等复杂的类型最上面一排是串口参数配置区端口号、波特率、数据位等中间是接收区和发送区下面是操作按钮区。这种规规矩矩的布局我个人建议直接用代码写用 QVBoxLayout 和 QHBoxLayout 组合就能排得很整齐后续修改参数也方便。代码搭界面的核心思路是这样的QWidget 作为主窗口然后在上面建一个垂直总布局再按照从上到下的顺序往总布局里一层一层嵌套水平布局。一个参数行的结构大概是这样// 参数行一个水平布局里放标签、下拉框、按钮 QHBoxLayout *portRow new QHBoxLayout; portRow-addWidget(new QLabel(串口)); portRow-addWidget(portComboBox); portRow-addWidget(openButton);先建水平布局装一行控件再把水平布局塞进垂直布局多行参数就形成了从上到下的排列效果。这种布局方式的好处是窗口缩放时控件会自动伸缩不会像绝对定位那样错乱。3.2 参数配置区与收发显示区的核心控件参数配置区需要选择串口、波特率、数据位、停止位、校验位。我用 QComboBox 做下拉选择每一项都是一个枚举值的字符串表现。串口参数在 QSerialPort 里都是枚举类型比如数据位有 Data5、Data6、Data7、Data8 四种停止位有一位、一位半、两位三种校验位有无校验、奇校验、偶校验等。下拉框里的显示文本和枚举值是一一对应的用户选择时记录当前的索引打开串口时把这些索引转换成对应的枚举值。这块代码我用一个简单的 switch-case 做映射虽然笨但很直观读代码的人一眼就能看出每个选项对应什么。建议不要用什么奇淫技巧去搞映射表串口参数就这么多老老实实写 switch 反而最好维护。接收区我用 QTextEdit 控件设置成只读模式收到的数据往里面 append 或者 insert 就行。为什么用 QTextEdit 而不是 QPlainTextEdit虽然 QPlainTextEdit 在性能上更优但 QTextEdit 可以直接支持富文本以后想给收到的数据标个颜色什么的会方便很多V1.0 我用的是纯文本追加但控件选型上已经在为后续铺路。发送区也是一个 QTextEdit用户在这个输入框里编辑要发送的内容注意这个是要可编辑的。再往下是发送按钮和清空按钮这一块布局非常常规没什么特别要讲的。Qt 控件的中文显示和字体问题我用的是默认字体中文显示没问题但如果你觉得字体不好看可以在代码里设置setFont或者直接改主窗口的样式表。样式表可以加分但 V1.0 建议保持原生外观先把功能做跑通再谈美化。3.3 文件对话框与保存接收数据保存接收数据这个功能我用的是 QFileDialog 弹出文件选择对话框。这个类封装了系统原生的文件选择窗口用户不需要关心路径怎么拼选择好保存位置和文件名后返回一个完整的文件路径字符串给我。QString fileName QFileDialog::getSaveFileName( this, 保存接收数据, QDir::homePath() /未命名.txt, 文本文件 (*.txt);;所有文件 (*.*) );getSaveFileName静态方法弹出一个模态对话框用户点完确认后把路径返回给我。有了路径之后剩下的就交给 QFile 类处理QFile file(fileName); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { file.write(receiveTextEdit-toPlainText().toUtf8()); file.close(); }这里有个细节要注意保存文件之前先弹一个确认框问用户是否确认覆盖已存在的文件。QFileDialog 在部分系统上默认会提示覆盖但不是所有平台都这样稳妥起见自己处理一遍比较好。这里弹出的确认框用的就是 QMessageBox::question阻塞等待用户确认后会返回 Yes 或 No 的枚举值。4. 串口通信核心逻辑实现4.1 端口扫描与参数配置程序启动时需要枚举系统上所有可用的串口端口把列表填入端口选择下拉框。这个操作依赖 QSerialPortInfo 类void MainWindow::refreshPorts() { portComboBox-clear(); const auto ports QSerialPortInfo::availablePorts(); for (const QSerialPortInfo info : ports) { portComboBox-addItem(info.portName()); } }availablePorts()返回一个列表里面的每一个 QSerialPortInfo 对象代表检测到的一个串口设备。对 USB 转串口的设备portName()返回的通常是 COM3、COM4 这样的名字如果是 Linux 系统返回 ttyUSB0、ttyS0 等。这里有个小坑程序运行过程中插入新的串口设备时列表不会自动刷新。需要在界面上放一个“刷新端口”按钮点击时重新调用refreshPorts()。更好的做法是配合定时器定期刷新但这样会闪断用户正在操作的端口V1.0 我就只做手动刷新按钮简单可靠。打开串口时的参数配置是一个完整流程不是开了就行。正确顺序是先设置端口名再设置波特率、数据位、停止位、校验位最后调用 open 方法。如果参数设置顺序反了有些驱动会直接报参数无效比如在 Windows 上你打开串口后再设置波特率某些 COM 口会直接拒绝。我一般这样写serialPort-setPortName(portComboBox-currentText()); serialPort-setBaudRate(currentBaudRate); serialPort-setDataBits(currentDataBits); serialPort-setStopBits(currentStopBits); serialPort-setParity(currentParity); if (!serialPort-open(QIODevice::ReadWrite)) { QMessageBox::critical(this, 错误, 无法打开串口请检查端口是否被占用); return; }BaudRate 我用一个整型变量来存然后通过 QSerialPort::BaudRateType 强转。这样处理的好处是以后想在波特率下拉框里加自定义的非常规波特率比如 250000只需要在界面里把数值加进去逻辑代码不用改。4.2 数据接收的两种常见处理方式QSerialPort 读取数据的典型方式是通过 readyRead 信号驱动数据到达时自动触发槽函数。这个信号在底层是通过事件循环通知的QCoreApplication::exec() 运行期间它都会正常工作不需要额外处理。接收数据我在 V1.0 里写了两种方式按需选择。第一种是文本模式适合接收 AT 指令响应、NMEA 语句这类人类可读的字符串。核心代码就是直接读connect(serialPort, QSerialPort::readyRead, this, []() { QByteArray data serialPort-readAll(); receiveTextEdit-moveCursor(QTextCursor::End); receiveTextEdit-insertPlainText(QString::fromUtf8(data)); });这里有个细节readAll()会把当前缓冲区里所有数据一次性读出来。但串口数据是分帧到达的一次 readyRead 不一定能收到完整的一帧。所以文本模式下我会直接把数据追加到接收区不做帧拼接——显示层面这样做效果足够数据断帧了也基本能看懂。第二种是预留的 hex 模式把字节按十六进制显示每个字节两个字符空格分隔。这块我 V1.0 里实现了但没开放给用户代码逻辑是QByteArray data serialPort-readAll(); QString hexString data.toHex( ).toUpper(); receiveTextEdit-insertPlainText(hexString );toHex( )会在每两个十六进制字符之间插入空格输出格式类似 A5 5A 01 02看起来非常清晰。十六进制模式对调试 Modbus 协议、自定义串口协议特别有用我强烈建议每个串口助手都支持这个功能。4.3 发送数据的核心逻辑与定时发送发送数据很简单把发送区的文本转成 QByteArray然后调用 write 方法写到串口。V1.0 同时支持文本模式和十六进制模式发送。文本模式直接把文本转 UTF-8 编码十六进制模式则要把用户输入的空格去掉两个字符一组转成字节。void MainWindow::sendData() { QString sendText sendTextEdit-toPlainText(); if (sendText.isEmpty()) return; QByteArray data; if (sendModeComboBox-currentIndex() 1) { // 十六进制模式把 A5 5A 或 A55A 转换成字节数组 sendText.remove(QRegExp(\\s)); data QByteArray::fromHex(sendText.toLatin1()); } else { data sendText.toUtf8(); } serialPort-write(data); }QByteArray::fromHex是十六进制转字节的利器它会把输入的字符串按两个字符一组解析成字节。需要注意的是如果输入的是奇数个字符比如 A5 5最后的 5 会被当做一个字节处理还是忽略这个跟 Qt 版本有关所以我在解析前会检查字符串的长度是否为偶数不是就提示用户输入有误。这种小细节很影响体验。定时发送的实现用的是 QTimer。界面上有个复选框勾选“定时发送”旁边是时间间隔输入框。勾选时启动 QTimer取消时停止。QTimer 的 timeout 信号连接到发送数据的槽函数到期自动执行发送。timer new QTimer(this); connect(timer, QTimer::timeout, this, MainWindow::sendData); // 勾选框状态变化时触发 connect(autoSendCheckBox, QCheckBox::toggled, this, [](bool checked) { if (checked) timer-start(sendIntervalSpinBox-value()); else timer-stop(); });定时发送在调试需要周期性发送心跳包的设备时非常有用比如门禁控制器每隔几百毫秒需要发一个握手指令用定时发送就完全不用手动操作。5. 界面与串口的联动细节5.1 端口状态管理串口的打开和关闭是互斥状态界面上需要实时反映当前状态。我处理的原则是串口打开后端口参数区所有控件全部禁用避免用户误操作修改参数同时“打开串口”按钮文字变成“关闭串口”颜色也变一下。void MainWindow::onOpenButtonClicked() { if (serialPort-isOpen()) { serialPort-close(); openButton-setText(打开串口); // 恢复参数控件可用 setPortConfigEnabled(true); } else { // 设置参数、打开串口 // ... openButton-setText(关闭串口); setPortConfigEnabled(false); } }这个细节很多人容易忽略但实际使用中防止误操作比什么都重要。想象一下调试时不小心碰了下波特率下拉框整个串口通信立刻乱掉排查起来非常头疼。用状态控制控件可用性从根源上杜绝这个问题。初始状态下“发送”按钮和“清空接收区”按钮也是禁用的只有串口成功打开后才启用。接收区不用禁用因为没打开串口时它就是空的放着也不影响。5.2 接收数据的计数显示调试时经常需要知道总共收了多少字节、发了多少字节我就在状态栏加了一个只读的标签实时显示收发计数。收一个字节计数加一显示格式是statusBar()-showMessage(QString(接收: %1 字节 | 发送: %2 字节) .arg(bytesReceived) .arg(bytesSent));计数逻辑在接收槽函数和发送函数里各加一行bytesReceived data.size()。注意用 qint64 类型别用 int因为如果程序挂机调试一整天收发数据可能轻松超过 2GBint 会溢出变成负数这是我调试时实际踩过的坑印象很深。计数清零按钮也做上了点击后两个计数变量归零状态栏刷新一次。功能很小但实测使用频率非常高因为做连续长传测试时需要分阶段看字节数有清零功能就不用自己记着上次跑到多少了。5.3 关闭窗口时释放串口资源主窗口关闭的时候如果串口还开着必须先把串口关掉再退出程序。否则串口会被程序占用不释放下次打开程序时可能报端口被占用更麻烦的是其他工具也会连不上这个串口。在 mainwindow.cpp 里重写 MainWindow 的 closeEvent 事件void MainWindow::closeEvent(QCloseEvent *event) { if (serialPort-isOpen()) { serialPort-close(); } event-accept(); }实际测试中我还发现一个情况如果程序异常退出串口资源可能没有正常释放。Windows 下重启程序后系统一般会自动回收但有些 USB 转串口的驱动比较脆弱会出现程序崩溃后端口一直显示被占用的情况。这时候要么拔插 USB 设备要么打开设备管理器禁用再启用这个端口。这个经验我在博客里写出来过很多人反馈确实遇到过。6. 常见问题与排查技巧6.1 收不到数据或接收乱码收不到数据是串口调试中最常见的问题排查思路按顺序走基本能定位。先检查参数是否匹配波特率、数据位、停止位、校验位必须和设备的参数完全一致比如设备是 115200 8N1波特率 1152008 数据位无校验1 停止位只要有一项不一致就完全收不到正常数据。然后检查串口是否被其他程序占用关掉其他串口工具再试。接收乱码多半是波特率不匹配或者文本编码不对。如果设备发的是 GBK/GB2312 编码的中文文本而接收区用 UTF-8 解码就会出现中文乱码。V1.0 里我在文本显示部分做了简单的编码自动检测如果QString::fromUtf8解码出来包含无效字符就自动回退到 GBK 解码。但这块只是权宜之计做得并不完美后续版本我会做手动切换编码的功能。6.2 发送数据失败与设备无响应发送时报错最常见的是串口没打开就点发送。虽然我已做控件禁用但不排除有极端情况所以发送函数开头再加一层检查就非常有必要if (!serialPort-isOpen()) { QMessageBox::warning(this, 警告, 请先打开串口); return; }设备有收无应需要确认发送的格式是否匹配。很多设备需要以十六进制格式接收指令你用文本模式发一个 “hello”设备当然不认识。设备协议里如果要求发送时必须带回车换行 (\r\n)你没有加设备就一直等你的结束符。这个排查思路对调 Modbus、调雷达、调 GPS 模块都适用。还有一个坑我必须重点说Qt 的 serialPort-write() 返回的是实际写入的字节数。如果串口设备的数据缓冲区满了write 会返回 0但代码不会报错。调试时如果数据频繁发不出去写个日志记录一下 write 的返回值比瞎猜要高效得多。6.3 界面卡死与数据量过大时的优化如果设备每毫秒都回传大量数据接收区的 QTextEdit 会频繁触发重绘和滚动界面会越来越卡。V1.0 我加了两个适配措施只保留接收区最近 10000 个字符超出后自动清空增加了一个“暂停显示”复选框勾选后接收数据仍然计数但不再插入接收区。这两个功能的数据结构其实很简单10000 字符限制就是if (receiveTextEdit-toPlainText().size() 10000) { receiveTextEdit-clear(); }暂停显示的判断就在接收槽函数开头加一个 if完全不影响接收计数逻辑。虽然手段糙了点但实际测试效果很好长时间高频率收数据界面也依然流畅。如果后续要做更复杂的大数据量模式就该考虑 QPlainTextEdit 加批量追加了。7. 项目总结与后续扩展到这里我的串口调试助手 V1.0 已经完整跑通了。整个项目从搭建到完工实际编码时间大约两个晚上最难的部分不是串口怎么读怎么写而是界面状态管理——什么时候该禁用什么控件、什么时候该刷新什么信息这些逻辑理顺了整个程序就顺了。Qt 的 QSerialPort 模块封装很友好如果你之前写过串口程序转到 Qt 平台几乎是无缝衔接。后续我计划加的功能包括十六进制收发模式的完整支持、YModem 文件传输、数据波形显示、串口多通道同时监听、RTS/DTR 引脚控制。这些功能在 V2.0 里会逐步补上。个人经验是做这种工具类软件第一版千万不要贪多先把最核心的链路打通跑一跑真实设备你会发现各种细节问题比如某些 USB 转串口芯片会在断开时发出一个字节的 0x00这些只有实战中才能暴露出来。如果你也想用 Qt 做串口工具或者日常调试缺一个顺手的小软件这个 V1.0 的代码结构拿来之后可以直接用起来。先跑通基本收发再按照自己的需要改界面、加功能这个过程的收获比直接用现成工具大得多。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价