ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Qt Creator的串口调试助手开发实战:环境配置、驱动识别与乱码处理

基于Qt Creator的串口调试助手开发实战:环境配置、驱动识别与乱码处理 简介基于Qt Creator的Serial Port串口调试助手项目代码是一份可直接参考的完整Qt工程面向嵌入式开发者、硬件调试人员及Qt初学者主要解决串口设备的数据收发、监控与可视化问题。项目不仅实现常规的串口打印还仿照VOFA软件加入Plot波形实时显示功能可在调试过程中直观观察信号变化适用于传感器数据采集、电机控制等场景。包内共53个文件以C源码9个cpp、7个h、界面文件2个ui、1个qrc、构建产物pro、makefile、debug/release、exe及说明图片15个png为主压缩包大小22.31MB体积适中便于直接导入Qt Creator编译运行或按需修改。已有378人学习浏览。代码结构清晰包含主窗口、串口配置、波形绘制等模块并附带可执行文件方便快速体验对于积分不足的开发者博主以无偿方式分享降低了学习门槛适合用作Qt SerialPort编程和QCustomPlot绘图入门参考。1. 从驱动识别失败说起为什么我决定自己写一个串口调试助手前段时间调试一块工控板手头USB转串口芯片是PL2303GS插上电脑后设备管理器里一直是黄色感叹号系统认不出COM口。换了几款常见的串口调试助手有的能扫描到设备有的干脆闪退折腾半天分不清到底是驱动问题还是助手自身问题。后来索性用Qt Creator自己撸了一个串口调试助手把串口枚举、打开、收发、Hex显示、日志分析全做了连驱动识别状态也直接在程序里暴露出来。这个项目做完省下的时间远超写代码的时间而且后面再遇到类似设备排查链路清晰多了。这也是这篇文章想分享的核心不光是给你一份能跑的串口调试助手代码而是把基于Qt Creator开发串口工具过程中真正绕不开的问题——编译器配置、驱动识别、乱码处理、缓冲区清理、编译窗口乱码——全部拆开讲清楚。适合正在学Qt串口编程的初学者也适合工作中需要快速搭建调试工具的嵌入式工程师。1.1 市面工具那么多为什么还要自己写SSCOM、XCOM、友善串口助手这些工具其实都很好但实际用起来有几个痛点界面固定没法按自己的习惯改日志保存格式不灵活遇到设备识别异常时提示信息不够直接。自研串口助手的意义不是替代它们而是让自己完全掌握这个工具。比如我可以在程序里把每个串口的VID、PID、驱动描述都打出来一眼判断是不是设备没被系统识别而不是在工具界面里瞎猜。1.2 功能范围与开发路线这个项目的功能范围很明确串口枚举与选择、波特率/数据位/校验位/停止位可配置、打开关闭串口、文本和Hex收发、定时发送、接收区清空与保存日志、异常状态提示。整个开发路线分五步环境准备、界面框架、串口模块、真机调试、完善细节。下面的内容就沿着这条路线展开每一步对应文章的一个章节代码可以直接参考。2. 环境准备Qt Creator 安装、编译器配置和CL报错自救Qt Creator本身只是一个IDE真正编译代码的是背后的编译器。很多初学Qt串口的人卡住不是卡在串口代码而是卡在环境准备。尤其是Windows上最容易遇见的两个问题一个是qt creator 编译器里面没内容另一个是cannot run compiler cl。这两个问题我都会展开讲。2.1 安装时最容易漏掉的组件Qt官方安装包分很多组件装的时候不要只勾Qt库。以Qt 6.x为例至少要勾选对应编译器的KitMinGW 11.2.0 64-bit这一类MinGW工具链MSVC 2019/2022 64-bit这类MSVC工具链Qt Debugger、CMake、Ninja如果只安装了Qt库没装工具链打开Qt Creator后在“工具 - 选项 - Kits - 编译器”里就是空的新建项目时也看不到合适的Kit这就是“编译器里面没内容”的直接原因。解决办法很简单重新运行安装包勾上MinGW或MSVC组件不需要重装Qt库。2.2 “cannot run compiler cl”到底是谁在捣乱MSVC的编译器cl.exe不像MinGW那样能直接被IDE调用它依赖Visual Studio的编译环境变量。Qt Creator如果直接调cl经常报qt creator:-1: error: cannot run compiler cl. output:这个报错十有八九是环境变量没初始化。网上很多教程让人手动去设置Path治标不治本。更稳的办法是在“开始菜单 - Visual Studio 2022 - Developer Command Prompt”里启动或者直接用Qt Creator自带的“Qt 6.x.x (MSVC 64-bit)”命令提示符它会自动调用vcvars64.bat把环境变量配好然后再打开Qt Creator加载项目。如果你不想折腾MSVC直接在Kits里选择MinGW编译器套件同样能编译完全绕开这个问题。2.3 我的选择MinGW 还是 MSVC串口调试助手这种纯桌面工具我个人的选择是MinGW。原因有三配置简单开源免费编译输出不会因为代码页问题变成乱码这点后面细说。MSVC的优势在于调试信息更全、和Windows底层API结合更好如果你还要调Windows驱动或MFC代码再用MSVC。串口开发本身跨平台MinGW加Qt在Windows和Linux下都能顺畅编译所以项目的.pro文件里我用的QT core gui serialport没有平台差异。3. 核心代码结构从串口枚举到收发完成的完整链路这个项目代码量不大但结构上一定要分清楚串口管理类单独拆出来界面只负责显示和交互。我本地工程的目录大概是serial_assistant/ serial_assistant.pro main.cpp mainwindow.h/cpp serialworker.h/cppSerialWorker封装QSerialPort的所有操作MainWindow只管界面。这样做的好处是将来要给助手加自动测试、转发、大模型分析时不需要改UI。3.1 串口信息枚举QSerialPortInfo 的正确用法项目启动时第一件事就是扫描串口。QSerialPortInfo提供了静态方法用起来极其简单#include QSerialPortInfo QListQSerialPortInfo portList QSerialPortInfo::availablePorts(); for (const QSerialPortInfo info : portList) { qDebug() 端口: info.portName() 描述: info.description() 厂商: info.manufacturer() VID: info.vendorIdentifier() PID: info.productIdentifier(); }这里有个容易被忽视的点availablePorts()返回的列表是动态快照。如果你在程序运行过程中插拔USB转串口设备这个列表不会自动更新必须在串口状态变化时重新枚举。我通常用一个刷新按钮同时在窗口激活事件里重新扫描保证下拉框里的端口列表始终最新。3.2 打开串口与参数配置顺序不能错打开串口时参数配置的顺序会影响某些驱动行为。最规范的做法是先设置端口名再配置波特率、数据位、停止位、校验位和流控最后调用open()m_serial new QSerialPort(this); m_serial-setPortName(ui-comboPort-currentText()); m_serial-setBaudRate(115200); m_serial-setDataBits(QSerialPort::Data8); m_serial-setParity(QSerialPort::NoParity); m_serial-setStopBits(QSerialPort::OneStop); m_serial-setFlowControl(QSerialPort::NoFlowControl); if (!m_serial-open(QIODevice::ReadWrite)) { QMessageBox::critical(this, 打开失败, m_serial-errorString()); return; }很多新手会先open()再设置参数这在部分驱动下确实也能工作但规范上是反的。还有如果打开失败errorString()一定要显示出来它可能告诉你“Access denied”表示端口被占用也可能告诉你“No such file”表示设备不存在。把错误字符串直接透出到界面上是排查驱动问题的最短路径。3.3 数据接收、粘包与发送逻辑接收数据用readyRead信号这是QSerialPort最核心的事件。收到数据后一次性读完connect(m_serial, QSerialPort::readyRead, this, []() { QByteArray data m_serial-readAll(); if (data.isEmpty()) return; // 这里做解码、显示、日志保存 });注意readAll()每次返回的不一定是一整帧数据。串口是字节流对方可能分几次发过来也可能一次发好几帧。这个“粘包/拆包”问题出现频率极高我的经验是纯文本协议先按\r\n分帧二进制协议用帧头长度字段解析不要拿到什么就立刻全部append到接收区。显示格式上我做了“文本模式”和“Hex模式”切换Hex模式直接把每个字节转成两位十六进制方便看不可见字符。发送逻辑相对简单写入之前检查串口是否处于打开状态if (!m_serial || !m_serial-isOpen()) { QMessageBox::warning(this, 提示, 串口未打开); return; } QByteArray sendData ui-editSend-toPlainText().toUtf8(); m_serial-write(sendData);3.4 界面线程与定时发送的取舍这个项目比较小串口收发直接在GUI线程做问题不大。如果接收频率很高比如每毫秒来一批数据或者你需要在收到数据后做大量解析建议把SerialWorker移到一个独立QThread里。可以用moveToThread()实现也可以直接用Qt的“信号槽跨线程排队”机制避免界面卡顿。定时发送功能我用了一个QTimer最小间隔我限制了10ms防止误操作把CPU占满m_timer new QTimer(this); connect(m_timer, QTimer::timeout, this, MainWindow::onSendButtonClicked); m_timer-setInterval(ui-spinInterval-value()); m_timer-start();4. 真机调试PL2303GS 这类 USB 转串口芯片的识别暗坑串口助手的代码写完不接真实设备跑一遍等于白写。这一章我把PL2303GS这个典型问题完整展开这不仅是驱动问题更代表了一整类USB转串口芯片的调试思路。4.1 插上设备看不到COM口的排查链路当设备插上电脑设备管理器里看不到COM口时你需要的不是代码而是排查链路打开设备管理器展开“端口COM和LPT”看有没有带感叹号的设备。如果“其他设备”里出现一个“USB Serial”或“USB2.0-Serial”说明芯片被枚举到了但驱动不对或者在“属性-详细信息-硬件ID”里能看到USB\VID_067BPID_23C3这类标识。右键更新驱动选择“浏览我的电脑”指向你下载好的驱动目录。如果更新失败检查驱动签名Win10/11用未签名驱动需要高级启动选项禁用驱动签名强制。PL2303GS这种新芯片硬件ID能识别但老驱动会拒绝加载。很多淘宝买的转接线用的就是这类新批次芯片装上几年前的驱动就会出现“设备无法启动代码10”。4.2 老版本驱动的“故意拒绝”Prolific官方对旧款PL2303的驱动是做过“计划废弃”的。部分芯片型号在Windows下会被新版驱动主动拒绝提示“PL2303HXA PHASED OUT since 2012”。到了PL2303GS/GC等新批次官方要求必须装新版本驱动不能再用2008年那种通用驱动。遇到这种情况不要到处找万能驱动直接去芯片官网下载对应型号的驱动包大概率一步到位。装上正确的驱动后设备管理器里显示的就是完整的“PL2303GS USB Serial COM Port”后面会带一个COM号这个信息在Qt里也能读到info.description()。4.3 在Qt程序里区分不同串口设备系统里可能有蓝牙串口、虚拟串口、USB转串口同时存在光靠COM号不可靠。我在程序里把枚举到的串口加上了描述和VID/PID信息下拉框显示格式类似于“COM7 - PL2303GS USB Serial COM Port”。这样用户选择的时候不会被COM号迷惑。foreach (const QSerialPortInfo info, QSerialPortInfo::availablePorts()) { QString item info.portName(); if (!info.description().isEmpty()) item - info.description(); ui-comboPort-addItem(item, info.portName()); // 显示带描述数据存端口名 }实操中还要注意open()失败时用errorString()区分“端口被占用”和“设备不存在”非常关键。比如某串口调试助手上一次非正常退出导致串口没释放新程序再打开就会报Permission Denied这时候不是你的代码错了而是之前的进程没退干净。5. 乱码、编码与缓冲区串口数据处理最常踩的三个坑串口显示乱码是项目上线后最常见的“用户投诉”。解决乱码不能只靠猜要按三条线索排查参数不匹配、编码不匹配、数据帧被截断。5.1 乱码的三种来源第一种波特率、数据位、校验位、停止位与对端设备不一致。这个问题好办先确认设备手册的默认参数再把串口助手的参数设置成一致。第二种编码不匹配。下位机可能发GBK也可能发UTF-8。Qt5里用QTextCodec转换Qt6改用QStringDecoder// Qt5 QTextCodec *codec QTextCodec::codecForName(GBK); QString text codec-toUnicode(data); // Qt6 QStringDecoder decoder(QStringDecoder::Utf8); QString text decoder(data);我在界面上留了一个“编码”下拉框默认UTF-8可以切GBK、Latin1以及二进制Hex模式。这样即使对端设备编码特殊也能现场切换不用重新编译。第三种帧被拆开导致显示乱码。比如一个中文字符在UTF-8里占3个字节如果readyRead触发时只收到2个字节直接解码就会出乱码。这种问题最隐蔽。我的做法是维护一个接收缓冲区先把数据追加进去再按帧结束符解析解析完的字节从缓冲区移除没解析完的留在里面等下一批数据。5.2 Qt/C 中清空 buffer 数组的几种姿势项目里用C时经常需要清空字节数组。网上被问得很多的“C语言对buffer数组清空有哪几种方式”这里直接总结方式适用场景说明memset(buffer, 0, sizeof(buffer));任意字节数组最常用按字节填0bzero(buffer, sizeof(buffer));POSIX/Linux非标准CWindows下慎用for (i0; in; i) buffer[i]0;可读性优先效率低于memset但代码直观strcpy(buffer, );C字符串只把首字节置0不推荐QByteArray::clear()Qt容器释放数据并重置为null在Qt项目里我更推荐用QByteArray或QString管理缓冲不要直接裸数组。比如接收缓冲QByteArray m_recvBuffer; m_recvBuffer.append(data); // 解析完成后 m_recvBuffer.clear();clear()比memset安全得多不会出现数组越界也不用关心长度。5.3 收发缓冲区的联动处理心得真正做串口助手中发送和接收缓冲区往往要联动考虑。比如你要发送一个长指令write()只是把数据交给系统缓冲区不一定全部发出去了要防止UI线程连续调用write()导致数据交错。我的经验是发送区和接收区都维护一个“待处理队列”发送时把要发的内容先压进QQueueQByteArray然后由写入槽函数逐个发送发送完一个再发下一个。这样做的另一个好处是将来如果要加“自动应答”功能直接在队列中间插入响应即可不用改混乱的UI代码。6. Qt Creator 编译输出乱码、高频刷新优化与后续扩展最后一章聊三个很实际的话题IDE编译输出乱码怎么治、接收区高频刷新怎么不卡、项目后续可以怎么扩展。6.1 编译输出窗口全是中文乱码的修复方法很多人在Windows上使用MSVC编译器时Qt Creator的编译输出窗口里中文路径或中文警告变成乱码。原因很简单MSVC输出走系统代码页一般是GBK但Qt Creator默认按UTF-8解释输出。最省事的解法是换用MinGW编译器MinGW输出基本是UTF-8乱码概率极低。如果不方便换编译器可以试着在“工具 - 选项 - 环境 - 系统”里把字符编码改成SystemGBK让Qt Creator按系统代码页解析输出。改完之后重新编译乱码基本消失。注意这个设置只影响编译器输出信息的显示不会改变你的源码文件编码。源码文件统一用UTF-8保存在项目里加一行QMAKE_CC ...也没必要保持默认就行。6.2 高频数据下接收区卡顿的优化如果设备每秒钟上报几百条数据直接用appendPlainText()会在几分钟后卡成PPT。我用的优化方案很简单设置最大显示行数ui-textReceive-setMaximumBlockCount(3000);超过3000行最早的接收内容自动丢弃配合“暂停显示”按钮接收数据仍然在后台线程处理只是不刷新UI可以大幅降低卡顿。更高频的场景还可以改为定时刷新UI比如每100ms统一刷新一次接收区而不是每次readyRead都刷新这样能把UI刷新频率从每秒几百次降到10次。6.3 串口助手还能连大模型一个可行的扩展方向现在很多团队在尝试“串口日志自动分析”思路是把串口助手收到的日志通过QNetworkAccessManager发送到本地的LLM接口让大模型帮忙定位异常问题。这个扩展不用改太多代码在SerialWorker收到数据后加一个信号把日志文本转发给分析模块分析模块再走HTTP接口请求。实现上就是一个标准的Qt网络请求难点不在技术上而在协议设计——发给大模型的数据不能是原始字节流要按帧转成文本或结构化JSON。这个方向我在项目里留了接口但还没完全做完算是后续的另一个坑。6.4 最后分享一点个人经验整个项目从环境搭建到真机跑通花时间最多的不是串口代码而是环境和驱动排查。如果你也准备基于Qt Creator做串口调试助手我给你的顺序建议是先把MinGW编译环境弄干净再写代码代码先跑通最简单的收发再加Hex、定时发送、日志功能本文还有配套的精品资源点击获取
返回列表