news 2026/3/10 9:54:54

Qt中qserialport串口通信:新手教程(从零实现)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt中qserialport串口通信:新手教程(从零实现)

从零开始掌握 Qt 串口通信:用QSerialPort打通软硬件的桥梁

你有没有遇到过这样的场景?手里的开发板连上电脑,却不知道怎么把数据读出来;调试传感器时,串口助手弹出乱码;写了个漂亮的界面,却卡在“如何让按钮一点击就发指令给单片机”这一步。

别急——Qt 的QSerialPort模块,就是为你解决这些问题而生的。

它不像 Win32 API 那样晦涩难懂,也不像原始 termios 设置那样繁琐复杂。它是 Qt 给我们的一把“万能钥匙”,让我们可以用简洁、优雅的方式,在 Windows、Linux 和 macOS 上统一实现串口通信。

今天,我们就从一个最基础的空白项目出发,手把手带你用QSerialPort实现一个完整的串口调试工具。不需要任何前置经验,只要你熟悉一点 C++ 和 Qt 的基本操作,就能跟着走完全程。


为什么是QSerialPort?它到底强在哪?

在嵌入式和工业控制领域,串口(UART)依然是最常用的通信方式之一。STM32 输出调试信息用串口,Arduino 控制舵机靠串口,PLC 与 HMI 交互也离不开串口。

但问题来了:怎么在 PC 端写个程序去收发这些数据?

传统做法是调用操作系统底层接口:

  • Windows 上要用CreateFile,SetCommState…… 一堆 Win32 API
  • Linux 上得折腾termios,open(),tcsetattr()……

光是让两个平台跑同一套代码,就得写两套逻辑,维护成本高到让人崩溃。

QSerialPort的出现,彻底改变了这一点。

它封装了所有平台差异,提供一套简单清晰的 C++ 接口,并且天然支持 Qt 最强大的机制——信号与槽。这意味着你可以轻松做到:

“当有新数据来时,自动触发函数处理”
“发送失败时,弹出提示框”
“设备拔掉后,程序不会崩溃”

这一切都不需要你自己开线程或轮询。

先看一眼最终效果

想象一下这个小工具:
- 启动后自动列出当前可用的 COM 口 / tty 设备
- 点击下拉框选择端口,设置波特率(比如 115200)
- 点“打开”就能连接
- 收到的数据以十六进制显示在文本区
- 输入AA BB CC并点击“发送”,立刻下发字节流

是不是很实用?接下来我们就一步步把它做出来。


第一步:配置项目环境

要使用QSerialPort,必须先告诉 Qt 编译器你要引入这个模块。

打开你的.pro文件,加入这一行:

QT += serialport

就这么一句话,Qt 就会自动链接对应的库文件。

然后在头文件中包含必要的类:

#include <QSerialPort> #include <QSerialPortInfo>

💡 提示:QSerialPort不属于核心模块,默认不包含,所以这一步不能省!


第二步:发现设备——让用户知道有哪些串口可用

用户不可能记住自己的 USB 转串口模块到底是 COM3 还是/dev/ttyUSB0。我们需要帮他们“看见”设备。

Qt 提供了一个静态方法:QSerialPortInfo::availablePorts(),它可以返回系统当前所有可用的串行端口信息。

我们通常把这些信息填充到一个下拉菜单里:

void MainWindow::refreshPortList() { ui->comboPort->clear(); const auto ports = QSerialPortInfo::availablePorts(); for (const QSerialPortInfo &info : ports) { QString item = info.portName() + " (" + info.description() + ")"; ui->comboPort->addItem(item, info.portName()); } if (ports.isEmpty()) { ui->comboPort->addItem("未检测到设备"); } }

这里有个小技巧:我们用addItem(text, userData)的形式,把真实端口号(如COM3ttyUSB0)作为用户数据存起来,避免后续解析字符串出错。

启动程序时调用一次refreshPortList(),就能看到类似这样的选项:

[ USB-SERIAL CH340 (COM5) ] [ Arduino Uno (ttyACM0) ]

干净又直观。


第三步:打开并配置串口——建立连接的关键一步

现在用户选好了端口,点击“打开”按钮,我们要做的就是创建QSerialPort对象并完成初始化。

假设你在类中已经声明了一个成员变量:

QSerialPort *m_serial;

构造时记得传入this作为父对象,由 Qt 自动管理内存:

m_serial = new QSerialPort(this);

然后开始配置参数。常见的设置如下:

void MainWindow::openSerial() { m_serial->setPortName(ui->comboPort->currentData().toString()); m_serial->setBaudRate(QSerialPort::Baud115200); m_serial->setDataBits(QSerialPort::Data8); m_serial->setParity(QSerialPort::NoParity); m_serial->setStopBits(QSerialPort::OneStop); m_serial->setFlowControl(QSerialPort::NoFlowControl); }

这些参数你可能已经在串口助手中见过无数次了:

参数常见值说明
波特率9600, 115200决定传输速度
数据位8每帧有效数据位数
校验位无 / 奇 / 偶错误检测机制
停止位1 或 2标记一帧结束
流控无 / 硬件 / 软件防止缓冲区溢出

⚠️重点提醒:PC 端和设备端的这些参数必须完全一致!否则要么收不到数据,要么收到一堆乱码。

设置完成后尝试打开:

if (m_serial->open(QIODevice::ReadWrite)) { // 成功打开 connect(m_serial, &QSerialPort::readyRead, this, &MainWindow::readData); connect(m_serial, &QSerialPort::errorOccurred, this, &MainWindow::handleError); ui->statusBar->showMessage("✅ 已连接", 3000); } else { QMessageBox::critical(this, "错误", "无法打开串口:" + m_serial->errorString()); }

注意这里注册了两个关键信号:

  • readyRead:只要有数据到达,立即通知你去读
  • errorOccurred:一旦发生异常(比如设备被拔掉),第一时间捕获

这就是 Qt 异步 I/O 的精髓所在——不用阻塞主线程,也不会让界面卡住。


第四步:异步接收数据——别再用 while 循环了!

很多人初学时喜欢这样写:

while (true) { if (serial.bytesAvailable()) readAll(); }

这是典型的反模式!这样做会占用 CPU 资源,还会导致 GUI 界面无响应。

正确做法是利用readyRead信号:

void MainWindow::readData() { QByteArray data = m_serial->readAll(); // 转成十六进制显示,便于观察原始数据 QString hex = data.toHex(' ').toUpper(); ui->textRecv->append("← " + hex); // 🔍 补充建议:循环读取直到缓冲为空,防止丢包 while (m_serial->bytesAvailable() > 0) { data += m_serial->readAll(); } }

为什么强调“循环读取”?

因为操作系统底层缓存和 Qt 的事件调度之间存在延迟。一次readyRead可能只触发部分数据到来,剩下的还在路上。如果不持续读完,下次触发前可能会堆积,最终导致溢出丢失。


第五步:发送数据——支持十六进制输入更专业

很多设备通信协议都是基于字节流的,比如你要发0x01 0x02 0xFF,直接打字符串肯定不行。

所以我们设计输入框支持十六进制格式:

void MainWindow::sendData() { QString input = ui->editSend->text().trimmed(); // 去除空格,转为字节数组 QByteArray sendData = QByteArray::fromHex(input.replace(" ", "").toLatin1()); qint64 result = m_serial->write(sendData); if (result == -1) { QMessageBox::warning(this, "警告", "发送失败:" + m_serial->errorString()); } else { ui->statusBar->showMessage(QString("📤 发送 %1 字节").arg(result), 2000); } }

这样用户就可以输入:

AA 55 01 02 FF

程序会自动解析成五个字节并发出。

如果你要做 Modbus 或自定义协议,这种输入方式非常实用。


第六步:健壮性提升——处理错误和意外断开

设备不是永远在线的。USB 线松了、CH340 驱动崩了、树莓派重启了……这些情况都会导致串口失效。

如果我们不做处理,程序很可能崩溃或卡死。

好在QSerialPort提供了errorOccurred信号,我们可以监听关键错误类型:

void MainWindow::handleError(QSerialPort::SerialPortError error) { if (error == QSerialPort::ResourceError || error == QSerialPort::PermissionError) { QMessageBox::critical(this, "连接中断", "设备已断开或访问被拒绝"); closeSerialPort(); // 安全关闭 } }

什么叫“安全关闭”?不只是调close(),还要:

  • 断开信号连接
  • 清理资源
  • 更新 UI 状态
void MainWindow::closeSerialPort() { if (m_serial->isOpen()) { m_serial->close(); } disconnect(m_serial, &QSerialPort::readyRead, this, &MainWindow::readData); ui->btnOpen->setText("打开串口"); ui->statusBar->showMessage("❌ 未连接"); }

这样才能保证用户重新插上线后还能顺利重连。


常见坑点与实战秘籍

🐞 坑一:明明发了数据,设备没反应?

检查以下几点:
- 波特率是否匹配?尤其是某些 GPS 模块默认是 9600
- TX/RX 是否接反?PC 的 TX 要接设备的 RX
- 是否忘了调open()?或者权限不足(Linux 下常见)

🐞 坑二:收到的数据总是粘在一起?

这是因为串口是流式传输,没有“包”的概念。短时间内连续发送的数据可能合并成一次readyRead到达。

解决方案:
- 在协议层面加帧头帧尾(如0xAA 0x55 ... 0x77
- 使用长度字段提前预判一帧多长
- 添加超时机制:若 10ms 内无新数据,则认为一帧结束

🐞 坑三:Linux 下打不开/dev/ttyUSB0

权限问题!普通用户默认没有访问权限。

两种解法:

  1. 临时提权(每次都要输密码):
    bash sudo chmod 666 /dev/ttyUSB0

  2. 永久加入用户组(推荐):
    bash sudo usermod -aG dialout $USER
    注销重登即可生效。

还可以写 udev 规则实现自动赋权,适合批量部署。


更进一步:打造工业级串口模块

学会了基础操作之后,你可以逐步升级功能:

✅ 加入自动扫描定时器

QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &MainWindow::refreshPortList); timer->start(2000); // 每2秒扫描一次

实现热插拔检测。

✅ 添加发送历史记录

把之前发过的命令保存下来,做成下拉补全,提升用户体验。

✅ 支持文件发送

.bin固件通过串口分包烧录,常用于 Bootloader 场景。

✅ 日志导出功能

把通信过程保存为.log文件,方便后期分析问题。

✅ 封装为独立组件

把串口模块抽成一个SerialManager类,带缓冲队列、重试机制、超时控制,供多个项目复用。


它不只是教学玩具,更是工程利器

也许你会觉得:“这不就是一个串口助手吗?网上随便下一个都比我自己写的强。”

但真正有价值的不是工具本身,而是你掌握了构建它的能力

当你需要做一个工控软件,要求:

  • 启动时自动识别 PLC 的串口地址
  • 每 100ms 发送一次心跳查询
  • 收到温湿度数据后更新仪表盘
  • 出现通信中断时报警灯闪烁

这时候你会发现,QSerialPort+QTimer+ 信号槽的组合拳,刚好够用又不失灵活。

它已经被广泛应用于:

  • 医疗设备数据采集系统
  • 科研仪器远程控制系统
  • 自动化测试平台(ATE)
  • 国产化替代项目中的 HMI 开发

特别是在国产操作系统(如统信 UOS、麒麟 OS)上运行 Qt 应用的趋势下,掌握这套技能尤为重要。


写在最后:通往硬件世界的入口

QSerialPort看似只是一个小小的串口类,但它其实是你进入“软硬协同”开发的第一扇门。

从此以后,你写的不再是孤立的图形界面,而是真正能操控现实世界的程序。

下次当你按下按钮,看到机械臂动起来、LED 亮起、传感器数值跳动的时候,你会明白:

那条看不见的串口线,正承载着代码与物理世界之间的对话。

而现在,你已经掌握了发起这场对话的语言。

如果你正在做毕业设计、准备面试题,或是想给自己做个调试神器,不妨动手试试。一个完整的QSerialPort示例项目,往往就是简历上那个亮眼的技术亮点。

有问题欢迎留言讨论,我可以分享完整源码结构或帮你排查通信故障。一起把软硬件的边界,推得更远一点。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/6 22:15:25

AI健身教练开发实战:人体关键点检测,2小时搞定原型开发

AI健身教练开发实战&#xff1a;人体关键点检测&#xff0c;2小时搞定原型开发 引言 想开发一个AI健身教练应用&#xff0c;但苦于没有编程基础&#xff1f;本文将带你从零开始&#xff0c;用最简单的方式实现人体关键点检测功能。通过现成的GPU环境和预训练模型&#xff0c;…

作者头像 李华
网站建设 2026/3/10 12:40:27

OpenPose实战教程:云端GPU 10分钟出结果,2块钱玩一下午

OpenPose实战教程&#xff1a;云端GPU 10分钟出结果&#xff0c;2块钱玩一下午 1. 什么是OpenPose&#xff1f;舞蹈博主的AI助手 OpenPose是一个开源的人体姿态估计算法&#xff0c;它能像X光机一样"看穿"人体动作。简单来说&#xff0c;这个技术可以&#xff1a; …

作者头像 李华
网站建设 2026/3/4 16:43:09

ComfyUI ControlNet Aux完全使用手册:解锁AI绘画的无限潜力

ComfyUI ControlNet Aux完全使用手册&#xff1a;解锁AI绘画的无限潜力 【免费下载链接】comfyui_controlnet_aux 项目地址: https://gitcode.com/gh_mirrors/co/comfyui_controlnet_aux ComfyUI ControlNet Aux插件是AI绘画领域的全能工具箱&#xff0c;它集成了数十种…

作者头像 李华
网站建设 2026/3/5 12:17:44

AI手势识别可扩展性分析:支持多手及复杂动作吗

AI手势识别可扩展性分析&#xff1a;支持多手及复杂动作吗 1. 技术背景与核心挑战 随着人机交互技术的演进&#xff0c;AI手势识别正从实验室走向消费级应用&#xff0c;广泛应用于虚拟现实、智能驾驶、智能家居和无障碍交互等场景。传统触摸或语音交互在特定情境下存在局限&…

作者头像 李华
网站建设 2026/3/10 12:58:21

COCO数据集姿态估计全流程:从标注到训练,云端GPU省心方案

COCO数据集姿态估计全流程&#xff1a;从标注到训练&#xff0c;云端GPU省心方案 引言 作为一名研究生&#xff0c;当你需要复现COCO关键点检测论文时&#xff0c;是否遇到过这样的困境&#xff1a;在自己的笔记本上训练模型需要两周时间&#xff0c;实验室服务器又总是排长队…

作者头像 李华
网站建设 2026/3/6 12:01:25

OFD转PDF完整教程:简单三步实现高效文档转换

OFD转PDF完整教程&#xff1a;简单三步实现高效文档转换 【免费下载链接】Ofd2Pdf Convert OFD files to PDF files. 项目地址: https://gitcode.com/gh_mirrors/ofd/Ofd2Pdf 在数字化办公日益普及的今天&#xff0c;OFD格式作为国产电子文档标准被广泛应用。然而&#…

作者头像 李华