各位同行,今天想把QT Creator下QSerialPort串口开发这块儿捋一遍。说实话,串口开发本身不复杂,但“模块装不上”“readyRead不触发”“数据莫名其妙丢字节”这些问题,几乎每个做上位机的人都会撞上。尤其是QT Creator跨平台工程,稍有不注意,QSerialPort模块的安装和配置就能让你折腾一下午。这篇文章不绕弯子,直接讲清楚QSerialPort到底怎么正确装、怎么用、以及那些坑到底是怎么踩出来的,适合正在用Qt写串口调试工具、配合单片机调通信协议的开发者,也适合刚接触上位机开发、想少走弯路的朋友。
我会把模块安装、工程配置、最小示例、以及高频故障排查这些都拆开揉碎,结合我自己的实际项目经历来讲,尽量让你看完之后能直接照着操作,别再被这些基础问题拖住进度。
1. 为什么QSerialPort会成为串口开发的拦路虎
1.1 先搞清楚QSerialPort是什么
QSerialPort是Qt官方提供的一个跨平台串口通信类,封装了Windows、Linux、macOS底层的串口API,提供了统一的接口。你在Windows上写完代码,换到Linux上重新编译,基本不用改业务逻辑,这一点对做跨平台工具的人来说非常方便。
不过它不是一个默认就存在的模块,这一点是很多新手踩坑的根源。Qt安装时默认不会把Qt Serial Port模块的所有源文件和库都带上,尤其是在你使用在线安装器选择了自定义组件路径时,很容易漏掉。漏掉之后,你的代码里#include <QSerialPort>会提示找不到头文件,或者链接时报undefined reference to QSerialPort::QSerialPort之类的错误。
还有一种情况是:头文件存在,但运行时提示Cannot load library,这多半是二进制不匹配,比如你用MSVC编译的Qt库,却用MinGW的编译器去链接,或者反过来。这类问题统称为“模块环境没配对”,不是代码逻辑的锅。
1.2 常见的安装失败原因和认知误区
我见过不少人在QSerialPort上出问题,第一反应是“重新安装Qt”。其实绝大多数情况是属于以下三种:
一是安装时没有勾选Qt Serial Port组件。Qt的在线安装器中有很多模块分类,串口模块在“Qt”分类下的“Additional Libraries”里。如果你只是默认安装,通常不会包含。二是编译器套件不匹配。你安装了Qt 5.15.2 MSVC2019 64bit的库,但在Kit里选择了MinGW 32bit,这种情况下模块库根本对不上号。三是环境变量和构建目录的问题。你明明已经装好了,但项目构建时用的是另一个Qt安装路径下的qmake,导致找不到头文件和库。
说实话,很多人第一步就栽在了组件选择上。与其等报错再折腾,不如在安装时一次性把该选的全选上。后面我会具体说怎么验证模块是否可用。
2. 正确的模块准备:从Qt安装到环境验证
2.1 安装Qt时如何选择串口模块
如果你还没安装Qt,建议直接从Qt官方维护的在线安装器走一遍。在安装组件界面的每一步,认真看勾选项:
首先,在Qt版本下选择你需要的编译器套件,比如Qt 5.15.2或Qt 6.x。然后展开“Additional Libraries”,找到Qt Serial Port,把它勾上。注意,如果你同时装了MSVC和MinGW两套编译器,建议对应的库都勾上,免得切换Kit时报错。
这里还有一个容易忽略的点:串口模块的头文件和库文件会随编译器套件分开存放。比如你最终用MSVC64编译,就要确保D:\Qt\5.15.2\msvc2019_64\include\QtSerialPort这个目录存在。如果使用了6.4.0版本,则类似路径为D:\Qt\6.4.0\msvc2019_64\include\QtSerialPort。如果这个路径不存在,基本就可以确定你没装这个组件。
如果你用的是Qt Maintenance Tool来增删组件,打开维护工具,选择“添加或移除组件”,然后找到与你已经安装的Qt版本对应的“Qt Serial Port”模块,勾上之后“下一步”会开始下载更新。维护工具能自动匹配当前Qt版本,比手动拷贝靠谱得多。
提示:千万不要手动把网上某个版本的QtSerialPort源码拖进现有Qt目录,除非你非常清楚自己在做什么,否则原型不匹配、qmake配置链表错乱会浪费你更多时间。
2.2 手动安装和维护Qt Serial Port模块
如果你的Qt安装包比较特殊,或者公司内网没有在线升级权限,实在需要手动处理,那么也有一套相对稳妥的办法。
从Qt官方仓库拉取对应版本的qtserialport模块源码。需要注意:源码版本必须与你当前的Qt主版本一致,比如你用的是Qt 5.15.2,那么尽量找5.15分支的源码。接下来打开QT Creator,用你已经配置好的Kit打开源码中的qtserialport.pro工程文件,然后构建。构建成功之后,在源码目录的lib和include里会生成相应的库文件和头文件,你需要手动把这些文件移动到Qt安装目录下的lib和include中。
这里要特别留意的是,手动方式极易出错,尤其是不同编译器的二进制库不能混用。MSVC编译出来的.lib是COFF格式,MinGW的.a和.dll是另一种ABI,混用的结果就是编译通过但运行时报The procedure entry point could not be located。所以手动安装只建议在特殊场景下用,正常环境还是老老实实装官方组件。
另外,不管你是用维护工具还是手动装,装完最好重启一下QT Creator。不要问我为什么,很多模块加载问题就是IDE缓存导致的。关闭所有工程,退出IDE,再重新打开,往往莫名其妙就好了。
2.3 验证模块是否可用的最快方法
模块装没装好,不用等跑完整个项目。最快的办法是用qsizetest之类的小测试工程,或者干脆在QT Creator里新建一个空项目,只写几行代码:
#include <QCoreApplication> #include <QSerialPort> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QSerialPort port; qDebug() << "QSerialPort available" << port.portName(); return 0; }如果头文件拼写正确,且能编译通过链接成功,执行后输出了端口空字符串,说明模块可用的。如果编译阶段报找不到QSerialPort头文件,那就回头查模块是否安装;如果编译通过但链接失败,多半是编译器套件不匹配,或者pro文件里缺少QT += serialport。
一个隐藏小技巧:在QT Creator的“帮助”菜单里搜索“QSerialPort Class”,如果帮助文档存在,说明模块的docs目录也在,一般模块安装就是完整的。如果帮助都搜不到,那基本判定模块没装全,你需要回去补包了。
3. 项目配置和第一行串口代码
3.1 .pro文件配置与头文件引入
很多人拿到QSerialPort的代码就直接copy,结果自己的pro文件里什么都没加。QSerialPort不是QtCore自带的,它属于独立模块,所以你必须在工程文件里显式声明:
QT += core gui serialport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = SerialDemo TEMPLATE = app SOURCES += main.cpp \ serialwidget.cpp HEADERS += serialwidget.h如果你的项目是纯控制台程序,不加widgets也没事,但serialport必须加。加了之后,qmake会重新生成Makefile,然后自动在头文件搜索路径里加入QtSerialPort目录,链接期间也会带上Qt5SerialPort或Qt6SerialPort库。
有时候你从网上拷了一段代码,pro文件也写了,但编译就是不通过。这时候先检查一下是不是工作目录或构建目录里还残留着旧生成的Makefile,执行一下清理项目/qmake/重新构建项目,比盲目改代码有效率得多。
对于Qt 6,模块名依然是serialport,头文件路径一般会自动包含,不需要你再手动INCLUDEPATH。但如果你用CMake,则需要:
find_package(Qt6 REQUIRED COMPONENTS SerialPort) target_link_libraries(your_target PRIVATE Qt6::SerialPort)CMake环境下最常见的错误是find_package(Qt6 SerialPort)找不到包,说明CMAKE_PREFIX_PATH没有指向你Qt安装路径,或者安装时没选SerialPort。
3.2 一个最小可用的QSerialPort示例
理论讲再多,不如直接跑一个例子。以下是我在Windows下调试串口工具时常用的一段基础代码,可以直接放到一个QWidget工程里用:
#include <QSerialPort> #include <QSerialPortInfo> #include <QComboBox> void populatePorts(QComboBox *combo) { combo->clear(); const auto infos = QSerialPortInfo::availablePorts(); for (const QSerialPortInfo &info : infos) { combo->addItem(info.portName() + " " + info.description()); } } void openSerial(QSerialPort *serial, const QString &portName, qint32 baudRate) { 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)) { qWarning() << "open failed:" << serial->errorString(); } }这里注意几个点:QSerialPortInfo::availablePorts()是枚举系统当前可用串口的最佳方式。不要在open()之前设置太复杂的参数,很多USB转串口芯片在默认参数下其实也能打开,但如果你设置了错误的流控模式,打开之后可能收发都不正常。
实际操作中,我见过有人用setPortName("COM3")之后直接open(),结果成功了但立即收到一堆乱码。这种大多是因为没设置波特率或设错了,又或者串口线缆虚接。串口通信不是USB即插即用,双方波特率、数据位、停止位、校验位必须完全一致。
3.3 关键参数:波特率、数据位、停止位、流控的坑
这部分是串口开发中的核心细节,看似简单,但差之毫厘谬以千里:
- 波特率:常见值1200、2400、4800、9600、19200、38400、57600、115200。很多STM32例程里默认115200,但有些老设备却用4800。如果你对接的是单片机,务必先确认固件里初始化的波特率,而不是猜。同一个设备在高速和低速波特率下,线缆质量和长度的影响完全不同,偶尔会有低速正常、高速丢包的情况。
- 数据位:通常用8位,极少用7位。如果通信协议里需要传输ASCII码且不包含扩展字符,7位也是可以的,但现代串口设备基本都是8位,老式工业设备可能存在7位E偶校验,这种就需要按设备手册来。
- 停止位:一般是1位,也有1.5位和2位。使用2位停止位更多见于老式电传打字机或低速设备。QSerialPort里设置停止位用
QSerialPort::OneStop、OneAndHalfStop、TwoStop。 - 流控:这是最大的坑。绝大多数USB转TTL模块不需要硬件流控,也就是RTS/CTS、DSR/DTR这些不用管。但如果你遇到设备能发不能收,或者能收不能发,不妨检查一下是不是把流控设成了Hardware。之前调一个工控屏协议,就是被上一位开发者设置的硬件流控坑了半天,最后改成NoFlowControl立刻通了。
一个小经验:凡是接USB转串口芯片,比如CH340、CP2102、FT232,第一优先使用NoFlowControl。只有当你明确知道设备端使用了RTS/CTS引脚时才开启硬件流控。
还有一个很多人忽略的:打开串口后,建议设置一下QSerialPort::setReadBufferSize。默认缓冲区对一般数据量完全够用,但如果单帧数据很大或者连续高速接收,缓冲区太小会造成数据截断。你可以在大流量环境下把它调到64 * 1024或更大。
4. 实际使用中的高频问题与排查技巧
4.1 readyRead不触发是怎么回事
这是一个超高频问题,在CSDN、Stack Overflow、Qt Forum上被翻来覆去地提问。readyRead信号不触发,或者偶尔触发一次就再也不触发,通常有以下几个原因:
原因一:打开串口失败。你没有检查返回值,以为打开了,结果端口被其他软件占用。这时候信号当然不会触发了。解决办法是if (!serial->open(...)) { qWarning() << serial->error(); }把错误打出来看一眼。
原因二:信号槽连接方式错误。比如你用QSerialPort对象在非主线程,但信号连接到了主线程的槽,如果没走队列连接,可能就丢了。建议使用connect(serial, &QSerialPort::readyRead, this, &MyClass::onReadyRead);这种新语法,让Qt自动选择连接类型。
原因三:没有给事件循环机会。如果你在控制台程序里用了阻塞循环,比如while(1);或者Sleep(),界面的消息循环转不动了,readyRead当然永远不执行。正确做法是把串口操作放进Qt事件循环里,或者用定时器去检测。在GUI线程中,千万不要用while(waitForReadyRead(10));这种阻塞自旋,那会让整个界面无响应。
还有一种隐蔽的原因:设备没发数据。很多人在测试时手里没有设备,用串口调试助手往COM口发数据,但实际上两个程序同时打开同一个COM口是不行的,设备会被占用。如果你想在没有真实设备的情况下测试,可以用虚拟串口工具,后面我会专门讲。
4.2 数据接收不完整/粘包的处理思路
串口是字节流协议,不是数据包协议,也就是说你发送{0xAA, 0x01, 0x02},对端可能一次收到,也可能分两次收到;反过来也一样。很多新手在readyRead里直接做一个readAll(),然后把读到的内容当作一帧处理,就会出现“明明发了8个字节,结果只收到5个”的现象。
正确的处理思路是维护一个接收缓冲区,或者用QByteArray累积数据,然后按协议解析:
void onReadyRead() { QByteArray data = serial->readAll(); rxBuffer.append(data); while (rxBuffer.size() >= FRAME_SIZE) { // 检查帧头、校验,取出一个完整帧 QByteArray frame = rxBuffer.left(FRAME_SIZE); rxBuffer.remove(0, FRAME_SIZE); handleFrame(frame); } }如果协议是长度可变的,则先解析帧头中的长度字段,再判断缓冲区内是否已经攒够一整帧。这个思路比在readyRead里等数据凑齐要稳得多。
还有一点:不要频繁调用readAll()而没有清空,虽然是移动语义,但大流量下如果不清缓冲区,内存会持续增长。也不要以为bytesAvailable()返回了多少就一定是多少,那只是当前缓冲区的可用字节数。
4.3 常见错误码和日志排查
QSerialPort的错误枚举常见有:
| 错误码 | 含义 | 建议排查方向 |
|---|---|---|
| NoError | 没有错误 | 无需处理 |
| DeviceNotFoundError | 找不到设备 | 检查端口名,是否被拔出,驱动是否正常 |
| PermissionError | 没有权限访问 | Linux下常见,加入dialout用户组;Windows下可能端口被占用 |
| OpenError | 打开失败 | 检查端口是否被占用,是否存在物理设备 |
| NotOpenError | 端口未打开 | 在读写前调用open |
| ParityError | 校验错误 | 检查校验位设置或线路质量 |
| FramingError | 帧错误 | 波特率、停止位不匹配,或线缆接触不良 |
| BreakConditionError | 线路break | 设备端异常或接地问题 |
调试时,最有效的手段是用errorOccurred信号捕获错误,然后输出到日志或界面上:
connect(serial, QOverload<QSerialPort::SerialPortError>::of(&QSerialPort::errorOccurred), this, &SerialTest::handleError); void handleError(QSerialPort::SerialPortError error) { if (error == QSerialPort::NoError) return; qWarning() << "serial error:" << error << serial->errorString(); }依据我个人经验,Windows下最常见的是DeviceNotFoundError和PermissionError。前者往往是端口号变了,后者往往是另一个调试工具还占着COM口。建议在打开串口前先执行一次QSerialPortInfo::availablePorts()刷新列表,不要信任用户手动输入的固定端口号。
4.4 串口关闭/重连的稳定性建议
在实际项目中,设备可能会掉线、拔掉USB又插回、或者烧录固件时复位。如果这些过程处理不当,串口对象就会陷入无法恢复的状态。
首先,不要在设备拔出后直接destruct串口对象,那样很危险。正确姿势是在端口消失时close(),等端口重新出现后再重新open()。你可以用QTimer定期调用availablePorts(),或者监听QEvent中的设备变化事件。Windows下能收到WM_DEVICECHANGE消息,Qt里可以通过nativeEvent实现,但这部分比较平台相关,最通用的还是定时刷新。
其次,串口对象最好做成可重入的,也就是在open()之前先close(),避免状态残留:
if (serial->isOpen()) { serial->clear(); serial->close(); } serial->setPortName(newPort); serial->open(QIODevice::ReadWrite);另外要特别注意:在关闭串口时,如果当前正在处理接收缓冲区里的数据,先停掉相关定时器,再close()。否则可能出现关闭后readyRead仍然触发,但readAll()返回空的情况,虽然无害,却容易让人误判。
5. 设备连接层面:驱动、虚拟串口和CH340的坑
5.1 插上设备没反应?先查驱动再查代码
很多时候,代码明明没问题,可串口列表里就是看不到设备。这时候首先怀疑的是USB转串口驱动。CH340、CP2102、FT232、PL2303这些常见芯片,Windows 10/11有时候会自动装驱动,但也经常出现因为系统更新导致驱动异常的情况。
碰到这种情况,我通常按顺序做这几步:
- 打开设备管理器,展开“端口(COM和LPT)”,看有没有带黄色感叹号的设备。
- 如果有,右键更新驱动,或者去芯片厂商官网下载对应驱动。
- 如果没有端口项,检查是不是USB线的问题。有些USB线只能充电不能传数据,这也是很常见的坑。
- 拔插一次USB转串口模块,观察设备管理器里是否有新设备出现。
不要一上来就改代码里的端口号,先确认系统层面设备是否存在。很多所谓“串口打不开”的报错,根源是驱动不对,设备根本没被系统识别成COM口。
5.2 虚拟串口工具在调试中的妙用
虚拟串口是我调试上位机时离不开的工具。它可以在系统里创建一对虚拟串口COM3和COM4,从一个端口写入的数据,会从另一个端口读出来。这样一来,你不需要任何真实硬件,就能验证你的Qt程序收发逻辑是否正常。
最常见的流程是:
- 安装类似VSPD或com0com这样的虚拟串口工具。
- 添加一对端口,比如COM10和COM11。
- 你的Qt程序打开COM10,然后用另一个串口调试工具(例如SSCOM、XCOM)打开COM11。
- 在调试工具里发数据,你的Qt程序就能在
readyRead里收到,反之亦然。
虚拟串口对处理粘包问题特别有帮助。你可以控制一次只发若干个字节,模拟拆分帧,看看你的接收缓冲逻辑能不能正确处理。
需要注意的是:虚拟串口的驱动与真实串口驱动不同,其波特率设置虽然会校验,但实际不会影响数据吞吐。在虚拟串口上测试通过并不代表真实硬件也一定通过,最终还是要接板子联调。
5.3 USB转TTL与电平注意点
除了驱动,硬件电平也是一个容易被忽略的问题。单片机系统常用TTL电平(0V~3.3V,或者0V~5V),而老式电脑的DB9串口是RS232电平(-12V~+12V)。如果你直接用USB转TTL模块去接一个RS232接口设备,轻则收不到数据,重则烧坏芯片。
通常情况下,USB转TTL模块(常见的有CH340G、CP2102、FT232RL)输出的是TTL电平,适合直接连接单片机的UART引脚。但如果你的设备是工控机上的DB9接口,那么你需要一个RS232转TTL模块,或者直接用USB转RS232的线缆。
还有一点,有些模块上面有跳线或短路帽,用来选择3.3V还是5V供电。如果你的单片机是3.3V系统,却给模块选择了5V供电,那么模块输出的逻辑高电平可能是5V,直接灌进单片机的IO口,可能会烧毁引脚。这个问题在连线前一定要确认清楚。
注意:QSerialPort本身不关心你用的是USB转TTL还是原生串口,它只是通过系统API读写COM口。但硬件接线不正确,代码再对也没用。所以当上位机收不到数据时,先拿万用表量一下TXD/RXD的电压,至少能确认线路通不通。
6. 工具链和编译环境的隐藏坑
6.1 编译器套件选错会导致串口模块无法链接
前面提到过编译器套件不匹配的问题,这里再展开说说。QT Creator中Kit是“编译器+Qt版本+调试器”的打包配置。常见的有:
- Desktop Qt 5.15.2 MSVC2019 64bit
- Desktop Qt 5.15.2 MinGW 8.1.0 32bit
- Desktop Qt 6.4.0 MinGW 64bit
如果你在一个项目里先用了MSVC的Qt库,后来又把编译器换成了MinGW,即使你修改了pro文件,老旧的构建缓存也可能导致链接失败。这时候最安全的操作是:
- 删除构建目录(build-xxx)。
- 选择你确认的目标Kit。
- 执行qmake。
- 重新构建。
这个操作治好了我90%的模块链接问题。同样的,如果你在Windows下用了MSVC编译器,还必须在系统里安装对应版本的Microsoft Visual C++ Redistributable,否则运行程序时会提示缺少VCRUNTIME140.dll。
6.2 代码对齐快捷键“不好用”的处理
题目下面还有一条热搜是“qt creator 代码对齐快捷方式不好用”,虽然它是附属热词,但实际上和长期写QT Creator代码的开发者息息相关。很多人在写代码时习惯用自动对齐快捷键,却发现在QT Creator里按了没用,或者对齐结果怪怪的。
QT Creator中代码自动对齐默认快捷键是Ctrl + I(缩进选中区域),而格式化整个文件更常用的是Tools -> Options -> Environment -> Keyboard里搜索Format,把“格式化选中代码”或“格式化当前文件”绑定到你习惯的快捷键上。我之前一直按Ctrl + A全选再Ctrl + I,后来发现其实直接在“选项”里搜索“Auto-Format”就能自定义。
对于串口开发这种大量重复的结构体初始化、参数赋值,代码对齐确实能提高不少可读性。如果你觉得自带快捷键不顺手,可以绑定成Ctrl + Alt + L,类似Windows其他IDE的习惯。绑定完记得重启IDE,否则可能不生效。
6.3 日志输出和调试器的配合使用
写串口程序时,我非常建议利用Qt的日志框架把收发数据打印出来。不仅仅是qDebug(),还可以用qInfo()、qWarning()区分日志级别。在QT Creator的“应用程序输出”和“调试器控制台”两个视图里,qDebug()输出很容易隐形,尤其是输出大量数据时,默认会截断。
你可以加一个环境变量QT_LOGGING_RULES或者使用自定义MessageHandler,把日志写入文件。对于串口数据,最好用十六进制打印,别用字符串,否则遇到非ASCII字符直接乱码,你根本定位不了问题:
QString hex = data.toHex(' ').toUpper(); qDebug() << "RX:" << hex;另外,如果你在readyRead槽里断点调试,很可能会触发串口缓冲区溢出,因为调试暂停期间设备端还在发数据。这种调试方式容易产生误导,建议用日志或文件记录代替断点,尤其处理高频数据时。必要时可以在关键位置加上QElapsedTimer测一下槽函数的执行时间,看看是不是因为处理太慢导致缓冲区溢出丢包。
7. 从测试到联调:一个完整的排查思路
7.1 先用Loopback测试验证你的程序
在接单片机之前,先用一根杜邦线把USB转TTL模块的TXD和RXD短接,也就是自发自收。此时你在你Qt程序里发送什么,自己就能收到什么。这是验证串口硬件通路和程序收发最简单的方式。
具体操作:
- 关闭串口,在硬件上短接TXD、RXD。
- 打开串口,发送一段特征数据,比如
0xAA 0x55 0x01 0x02 0x03。 - 检查
readyRead是否触发,显示的数据是否与发送一致。
如果自发自收发都乱码,那大概率是波特率设置不对,线材有问题,或者使用了错误的流控模式。如果自发自收完全正常,再接单片机联调,那就是设备端协议或接线的问题。
7.2 与单片机联调时的分步验证法
当你的程序与单片机通信异常时,不要慌,先用串口调试助手排除嫌疑:
- 用SSCOM或XCOM打开同一个COM口,选择与单片机一致的波特率。看能不能收到单片机主动发送的数据。如果收不到,说明单片机没发数据或接线有问题。
- 如果串口助手能收到,但你的Qt程序收不到,那问题在代码的串口初始化或信号连接上。
- 再用串口助手发送一帧测试数据,看单片机能否响应。若单片机无反应,可能是你发的格式不对,或者波特率不同。
这种“串口助手+Qt程序”互相验证的方式,能快速定位问题在硬件、单片机端,还是上位机代码。千万不要一直盯着自己的Qt代码死磕,换一个标准工具试一下,往往立刻能发现问题所在。
7.3 长期运行的稳定性:发送队列和线程处理
如果你的串口程序需要长期运行,比如实时监控设备状态,那么你需要处理发送和接收的并发问题。虽然QSerialPort是异步IO,但如果在多个线程中同时操作同一个QSerialPort对象,会产生未定义行为。
推荐的做法是:把QSerialPort对象放在一个单独的线程中,或者使用信号槽跨线程发送数据,而不要直接调用write()。比如UI线程需要发送数据时,发送一个信号,让串口线程执行实际的写入操作。这样能避免UI卡顿,也能避免串口数据竞争。
对于接收到的数据,不要直接在readyRead里做耗时的解析或写库。可以先放进一个线程安全的队列,再由工作线程取出来处理。这样才能保证串口接收的实时性,不至于因为处理慢而丢失数据。
另外,发送超时和接收超时也需要考虑。QSerialPort的waitForBytesWritten()可以用于同步写等待,但如果在GUI线程中调用,可能阻塞界面。异步方式可以用bytesWritten()信号跟踪发送进度,或者自己用QTimer实现超时重发逻辑。
8. 实用经验小结:我的串口调试法
文章写到这里,核心的内容都讲完了。说几点我个人在实际操作中的体会:
首先是模块安装,真没必要自己造轮子。官方维护工具把组件勾上,专治各种头文件找不到。如果公司内网实在没权限,那就用集成环境时注意拷贝源码编译,但务必保持编译器套件一致,不然库都白编。
其次是调试手段,始终把“串口调试助手”当作你最好的伙伴。我每次写串口程序,都会先把单片机那端用串口助手调通,然后才用Qt程序去对接。这样出了问题能快速二分定位,不至于在一个大黑盒里瞎猜。
再者,代码层面一定要盯着“异步”两个字。QSerialPort是异步的,你的UI千万别用阻塞循环等数据,否则readyRead被活活饿死。遇到信号不触发,首先确认串口打开成功、事件循环正常、端口元宝都没有写错。
最后,硬件上一定要养成先量电压再写代码的习惯。USB转TTL模块的TXD、RXD很好认,但接错一次就可能烧毁芯片。宁可多花一分钟用万用表确认,也不要贸然通电,血泪教训。
希望通过这篇文章,你不管是装QSerialPort模块,还是调试实际串口设备,都能少走些弯路。串口开发不难,但基础打不牢,后面全是坑。