尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
QT四轴上位机实战:串口通信、姿态绘图与指令控制
简介面向QT与无人机开发初学者这份资源提供了四轴飞行器上位机软件的初级版本。内容涵盖基于Qt的GUI控制面板、串口通信模块、下位机协议解析以及简单的实时数据显示逻辑适合希望上手无人机地面站基础开发、理解上位机与飞控交互流程的读者。压缩包共34个文件大小约10.28MB包含可运行的exe程序、Qt核心dll动态库、下位机C语言工程源码以及hex固件和编译中间文件如obj、lst、m51等便于对照学习程序结构与编译产物。已有208人学习该资源说明其具备一定的参考价值。通过这个初级版读者可以熟悉QT控件布局与信号槽机制掌握QSerialPort串口通信和基础协议解析方法并了解陀螺仪等传感器数据的处理与状态显示思路为进一步开发功能完善的无人机地面站打下基础。1. 用 QT 做四轴上位机先想清楚这版做到什么程度拿到一个叫 QT四轴上位机初级版.zip.zip 的压缩包常见的第一反应是双击打开找 exe。但这类初级版本质上是一个工程模板它给你的是串口通信、数据解析、界面绘制三条线的最小骨架。四轴飞行器没有地面站也能飞但调 PID、看姿态、切飞行模式时没有上位机就只能靠串口打印效率低得让人头疼。这篇博文按“串口链路—状态可视化—指令下发—打包发布”的顺序讲一套能跑通的 QT Widgets 方案新手跟着敲完熟手也能拿去对照自己的代码。2. 搭建 QT 串口通信四轴上位机与飞控的底层数据链路2.1 环境准备选 QT 版本和开启 serialport 模块做四轴上位机不需要上 QML用 Widgets 足够。QT 5.15 或 6.x 都行编译器建议 MSVC 或 MinGW 统一位数最常见的组合是 5.15.2 MSVC2019_64。网上大部分 qt 安装教程都停在“装完能写 HelloWorld”忽略了 Kit 和模块勾选导致后面#include QtSerialPort/QSerialPort都报错。在 Qt Creator 里创建 Qt Widgets Application 后先改工程文件QT core gui widgets serialport如果你用 CMake 管理工程则写成find_package(Qt6 REQUIRED COMPONENTS Widgets SerialPort) target_link_libraries(my_gcs PRIVATE Qt6::Widgets Qt6::SerialPort)如果 qmake 里不加serialport编译器只报头文件找不到不会提示你去改 pro 文件新手很容易卡在这。下表是几组常见组合和对应坑选型时直接对照组合适用场景典型坑5.15.2 MSVC2019_64老项目兼容、教程资料最多目标机器缺 VC 运行库6.5 MinGW 64新项目、不依赖旧插件主控板和界面库位数必须一致5.12 ARM 交叉编译跑在板载屏幕上的 HMI交叉工具链里要自带 serialport 模块串口模块是 Qt SerialPort它底层封装了 Windows 的 COM 和 Linux 的 tty不需要自己写 WinAPI这也是选 QT 做初级版上位机最省事的地方。装完环境后先用下面代码验证模块可用#include QtSerialPort/QSerialPortInfo qDebug() QSerialPortInfo::availablePorts().size();正常运行会打印当前系统可用的串口数量不是 0 就说明模块加载成功。2.2 枚举串口让下位机一插就能被找到打开串口之前先做枚举。我一般会在主窗口构造函数里调用refreshPorts()把可用串口填进 QComboBox用户插拔 USB 转 TTL 后点刷新按钮重新扫描。代码如下void UiMainWindow::refreshPorts() { ui-comboPort-clear(); const auto infos QSerialPortInfo::availablePorts(); for (const QSerialPortInfo info : infos) { QString display info.portName(); if (!info.description().isEmpty()) { display info.description(); } ui-comboPort-addItem(display, info.portName()); } }addItem的第二个参数是 UserData后面打开串口时直接用currentData().toString()取纯端口名不会把描述文本带进来。注意这里description()在 Linux 下经常为空所以要做判空拼接否则下拉框会出现多余空格。接着设置串口参数m_serial.setPortName(ui-comboPort-currentData().toString()); m_serial.setBaudRate(115200); m_serial.setDataBits(QSerialPort::Data8); m_serial.setParity(QSerialPort::NoParity); m_serial.setStopBits(QSerialPort::OneStop); m_serial.setFlowControl(QSerialPort::NoFlowControl);串口参数必须与飞控下位机保持一致。四轴飞控默认常用 115200 或 57600数据位 8、无校验、1 停止位也就是 8N1。如果下位机是 10Hz 发一帧波特率高一点没关系如果下位机每帧不到 20 字节57600 足够。CH340 芯片在 Windows 下枚举名可能是COM3或USB-SERIAL CH340过滤时不要只按COM前缀判断原因是有驱动会带描述后缀。2.3 接收数据粘包和半包怎么处理下位机以 20~50Hz 发传感器数据一次readyRead()读到的数据可能包含半帧、一帧或好几帧。初级上位机最常见的错误是在槽函数里“读一次解析一次”遇到半帧直接丢结果姿态值一顿一顿。我一般用 QByteArray 做接收缓冲先找帧头再按长度截整帧void UiMainWindow::onReadyRead() { m_buffer.append(m_serial.readAll()); while (m_buffer.size() 4) { // 找帧头 0xAA如果缓冲区开头不是帧头就丢弃 int headIdx m_buffer.indexOf(char(0xAA)); if (headIdx 0) { m_buffer.clear(); break; } if (headIdx 0) { m_buffer.remove(0, headIdx); } // 第二字节是负载长度最小一帧 4 字节帧头长度校验数据 quint8 len static_castquint8(m_buffer.at(1)); if (len 128) { // 长度异常丢弃当前字节避免死循环 m_buffer.remove(0, 1); continue; } if (m_buffer.size() 2 len 1) { break; // 整帧没到齐等下次 readyRead } QByteArray frame m_buffer.left(2 len 1); if (verifyFrame(frame)) { handleFrame(frame); } m_buffer.remove(0, 2 len 1); } }m_buffer是成员变量跨槽函数保存。verifyFrame用累加和校验初级版比 CRC16 简单而且能挡住大部分串口干扰。参数len一定要做上限检查否则一帧坏数据里长度被写成 255m_buffer.size() 2 len 1会一直为真缓冲区越积越大拖慢 UI。需要注意这种方式要求帧内不能出现 0xAA 转义协议设计时让数据区里的 0xAA 做转义或者干脆在协议里固定用 0xAA 0x55 作为双字节帧头。初级版用单字节帧头加长度就能满足但下位机在发送浮点数据时某个字节恰好等于 0xAA 的概率是 1/256累积起来很容易误判。所以我建议协议里用双字节帧头后续章节会展开讲。3. 用 QT 绘图把四轴姿态画出来从串口字节到飞行仪表盘3.1 建立状态结构体把字节解码成可读字段解析不能每个界面控件都写一遍memcpy那样改一个字段要动七八处。我习惯先建一个四轴状态结构体把 handleFrame 里的载荷按字段填充进去界面部分只读结构体。struct QuadState { float roll 0.0f; float pitch 0.0f; float yaw 0.0f; float height 0.0f; float batteryVoltage 0.0f; quint8 flightMode 0; };假设下位机发来的帧头之后是 12 字节姿态、4 字节高度、2 字节电压、1 字节模式载荷排列固定且按小端序。解析时这样写QuadState state; const char *p payload.constData(); memcpy(state.roll, p, 4); memcpy(state.pitch, p 4, 4); memcpy(state.yaw, p 8, 4); memcpy(state.height, p 12, 4); quint16 voltageRaw 0; memcpy(voltageRaw, p 16, 2); state.batteryVoltage voltageRaw / 100.0f; state.flightMode static_castquint8(payload.at(18));这里用memcpy而不是强制转换指针是因为const char*转float*在 ARM 平台可能遇到对齐异常。电压用原始值除以 100 是飞控端常用的定点缩放可以在不传浮点的情况下保留两位小数。飞行模式建议用数字枚举而不是字符串界面层负责把枚举映射成中文。3.2 自定义仪表盘用 QPainter 做高度表和电池进度条四轴上位机初级版不需要做三维姿态球一个带刻度的圆表盘加一个电池进度条就够用了。这里要用到 qt 绘图核心是继承 QWidget 重写paintEvent。下面是一个简化高度表的核心绘制void AltimeterWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QRectF rectF rect().adjusted(10, 10, -10, -10); painter.drawEllipse(rectF); // 值范围 0~100 映射到 135°~405°刻度间隔 10 for (int value 0; value 100; value 10) { double angle 135 (270.0 * value / 100); double rad qDegreesToRadians(angle 90); // 计算内外圈刻度点 QPointF inner( rectF.center().x() qCos(rad) * rectF.width() * 0.35, rectF.center().y() qSin(rad) * rectF.height() * 0.35); QPointF outer( rectF.center().x() qCos(rad) * rectF.width() * 0.45, rectF.center().y() qSin(rad) * rectF.height() * 0.45); painter.drawLine(inner, outer); } }角度偏移90是因为qCos(0)指向水平右侧而仪表盘 0 应该在垂直上方。用rectF.width()做系数能保证窗口拉伸后刻度跟着变不会因为固定像素而变形。更新数值时不要直接调用repaint()用update()让 QT 在下一帧统一刷新否则高频串口数据会让事件循环卡死。电池显示直接用 QProgressBar 改样式就能满足初级版不需要特意自定义。如果想把低电量标红、正常电量标绿可以用样式表ui-batteryBar-setRange(0, 100); ui-batteryBar-setValue(static_castint(state.batteryVoltage * 10)); if (state.batteryVoltage 3.5f) { ui-batteryBar-setStyleSheet(QProgressBar::chunk { background: red; }); }进度条范围设成 0~100电压 3.0V 对应 30%4.2V 对应 42%。这里乘以 10 而不是 100是因为信号数值切换太快会导致进度条闪烁初级版只要求“看得见趋势”。3.3 波形曲线QCustomPlot 比 Qt Charts 更适合高频数据姿态角、高度和电压如果想看实时曲线常选 QCustomPlot。qt 绘图效率比较下来QCustomPlot 在动态追加几百个点后依然能维持 30fps而 Qt Charts 的动态刷新在 PC 上也能跑但封装偏重改样式更麻烦。四轴初级版用 QCustomPlot 就够了。ui-plot-addGraph(); ui-plot-graph(0)-setPen(QPen(Qt::green)); ui-plot-yAxis-setRange(-180, 180); ui-plot-xAxis-setRange(0, 100);在定时器槽里更新ui-plot-graph(0)-addData(m_pointIndex, state.roll); ui-plot-xAxis-setRange(m_pointIndex - 100, m_pointIndex); ui-plot-replot();定时器用 50ms 刷新一次也就是 20Hz串口 50Hz 的数据并不需要每一笔都画进曲线界面刷新率高于 30Hz 就会浪费 CPU。replot()在 QCustomPlot 里比update()更直接因为它绕过 QT 的合成器直接触发绘制但也不能密集到每次串口读包都调否则 UI 线程会被拖死。4. 控制指令下发与协议设计让四轴上位机从监视器变成遥控台4.1 指令帧协议双字节帧头加序号上位机不只是读数据还要下发解锁、加锁、切模式、调油门。如果再沿用上一章的“长度累加和”格式会缺少“命令是否发到”“下位机是否执行”的确认依据。因此指令帧我一般单独设计一组格式与遥测帧分开字段长度说明帧头2 字节0xAA 0x55指令类型1 字节0x01 解锁0x02 加锁0x03 飞行模式0x04 遥控量序号1 字节0~255 回绕数据长度1 字节数据区字节数数据N 字节具体参数遥控量时是 4 个字节的油门/滚转/俯仰/偏航校验1 字节整个帧的累加和低 8 位发送代码封装成一个函数void UiMainWindow::sendCommand(quint8 type, const QByteArray data) { QByteArray frame; frame.append(char(0xAA)); frame.append(char(0x55)); frame.append(char(type)); frame.append(char(m_sendSeq)); frame.append(char(data.size())); frame.append(data); quint8 crc 0; for (int i 0; i frame.size(); i) { crc static_castquint8(frame.at(i)); } frame.append(char(crc)); m_serial.write(frame); }m_sendSeq是成员变量每发一帧自增可以让下位机检测丢帧。累加和不是最强的校验但对付四轴这种距离短、干扰小的现场足够下位机解析成本也低。数据长度用 1 字节说明单条指令最多 255 字节四轴遥控量远到不了这个数。4.2 界面槽函数和控制定时器按钮触发很简单点“解锁”时调用void UiMainWindow::on_unlockButton_clicked() { sendCommand(0x01, QByteArray()); }但遥控量不能靠点击得用 QTimer 周期性发送。常见做法是界面里四个 slider 或者 Joystick 控件每次值改变时把最新控制量写进成员变量定时器按 20Hz 发送QByteArray data; union { float f; char bytes[4]; } convert; convert.f ui-throttleSlider-value(); data.append(convert.bytes, 4); // 同理 append pitch/roll/yaw sendCommand(0x04, data);这里用union转浮点和接收端memcpy的思路一致。滑块默认范围如果设成 0~1000需要在下位机做对应缩放。注意 float 的位模式不能直接当作字节送到不同单片机上两端都必须按小端序解释。如果下位机用 STM32 也按小端解析这样就通。4.3 ACK 确认和超时重发下位机收到指令后回复确认帧比如 0x81 表示解锁成功、0x82 表示加锁成功。初级版不要求完整重传机制但至少要区分“命令没发出去”和“命令被拒绝”。我在接收解析里增加一个命令回执表QHashquint8, QByteArray m_pendingCmd;发送时先存储再启动单次定时器m_pendingCmd.insert(type, data); QTimer::singleShot(500, this, [this, type]() { if (m_pendingCmd.contains(type)) { m_pendingCmd.remove(type); ui-statusLabel-setText(QStringLiteral(指令 %1 超时无应答).arg(type)); } });收到 ACK 时移除对应项。油门遥控这类周期性指令不需要 ACK只有解锁、加锁、切模式这类离散命令才做确认。这里要注意闭包捕获type不要捕获this之外的悬空引用。如果用户下位机不支持 ACK就删掉这段逻辑不要让 UI 弹错误提示误导操作员。5. 四轴上位机发布时的几个坑windeployqt 和插件路径开发机上能跑不等于拷到别的电脑能跑。QT 程序发布最常遇到的是打开 exe 报错“缺少 DLL 文件”或控制台提示qt_qpa_platform_plugin_path找不到。用 windeployqt 处理时注意顺序和参数cd build\my_gcs windeployqt my_gcs.exe --release --no-translations执行后会自动复制 Qt 运行库、平台插件和 styles 插件到 exe 所在目录。如果程序用了 serialportwindeployqt 在多数情况下会自动带出 QtSerialPort.dll但有些历史版本不会需要从编译器目录手动复制。复制完看目录里有没有platforms/qwindows.dll没有它程序会直接闪退报错信息就是qt_qpa_platform_plugin_path相关。发布前还有两件事值得做。第一在 exe 同目录放一个qt.conf内容固定为[Paths] Plugins plugins Prefix .作用是让程序从自身目录找插件而不是跑到注册表或源码目录找这也是解决“开发机能跑、别的机器不能跑”高频办法。第二检查串口模块的依赖库用dumpbin /dependents my_gcs.exe可以列出直接依赖的 DLL。如果看到Qt5SerialPort.dll就把 Qt 安装目录下对应编译器版本的 DLL 一起复制。四轴上位机的界面里串口下拉框、曲线绘制、电池进度条这三样东西在发布后最容易出现风格错乱最常见原因是编译器版本与 windeployqt 版本不一致。发布包必须与编译器一致比如用 5.15.2 MSVC2019_64 构建就不能用 5.15.2 MinGW 的 windeployqt否则运行到replot()或中文渲染时会崩溃。发布完成后建议找一台没装 QT 的干净虚拟机跑一遍“打开串口—接收数据—曲线刷新—发送指令”四个步骤再交付给用户。本文还有配套的精品资源点击获取
RELATED

相关推荐

开发者注意力管理:从科学原理到工程实践

开发者注意力管理:从科学原理到工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/14 7:40:45
基于深度学习的3D MRI分类:从数据集预处理到模型训练与评估

基于深度学习的3D MRI分类:从数据集预处理到模型训练与评估

简介:本项目是一套面向医学影像分析入门者与深度学习初学者的3D MRI分类演示代码包,聚焦将对比学习中的InfoNCE损失扩展至弱监督场景,利用受试者年龄、性别等辅助信息改进数据表示。压缩包共12个文件,涵盖Python源码、图解图片与说…

📅 2026/9/14 7:35:45
降AI率工具实测:10款AI写作改写助手效果对比与避坑指南

降AI率工具实测:10款AI写作改写助手效果对比与避坑指南

去年有个学弟来找我,说他毕业论文第二部分被导师圈了一整页,旁边批了四个字:“很像AI写的”。他觉得很冤,因为他确实没有拿AI整段生成,只是让AI帮忙扩了提纲,又自己改了两遍。我拿过来一看,问题…

📅 2026/9/14 7:35:45
MORE NEWS

更多资讯

📰

用Python手写BP神经网络实现鸢尾花分类:从原理到调参

简介:面向Python初学者的人工智能实践项目,使用BP神经网络对经典鸢尾花数据集进行分类,配套完整源码、数据集和文档说明,可满足期末大作业、课程设计等场景。除BP神经网络两个版本(V1/V2)外,还提…

📰

基于GPT-6 Astra的跨平台GitHub查询机器人:QQ与飞书双端实现

上个月我把公司内部使用的 GitHub 辅助查询机器人从单一聊天工具迁移到了 QQ 和飞书双端,同时接入了 GPT-6 Astra 的智能体能力。现在同事在 QQ 群里发一句“帮我看下 fastapi 这个仓库最近的 issue 情况”,机器人会自动调用 GitHub API、拉取数据、再交…

📰

Python毕业设计:恶意代码检测分类平台搭建与实现

简介:面向计算机相关专业毕业设计的高分项目源码包,以Python实现恶意代码检测与分类平台,适用于正在准备毕设、课程设计或期末大作业的学生。项目经导师指导认可,评审分97分,覆盖数据预处理、模型训练、分类识别等完整…

📰

二分查找算法原理、实现与优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Doris+Lance实现毫秒级跨模态联合检索

1. 这不是又一个“多模态数据库”概念秀,而是智能驾驶与具身智能落地的硬性瓶颈被捅破了我第一次在某头部自动驾驶公司数据平台组看到他们用 Apache Doris Lance 搭建的实时感知日志分析链路时,第一反应是:这玩意儿居然真能跑通?…

📰

移动应用安全测试全流程指南与最佳实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬