☰
Qt无人机地面站编译指南:qextserialport跨平台串口构建
2026/9/30 13:43:48 网站建设 项目流程

简介:本资源是一套基于Qt Creator开发的无人机地面站软件项目源码,面向嵌入式开发初学者、飞控算法学习者及智能机器人方向实践者,聚焦于解决地面站软件的跨平台编译、实时遥测通信与GUI人机交互等核心问题。压缩包共218个文件,含55个C++源文件(如frameserial.cpp、qextserialport_win.cpp等串口通信与航电协议解析模块)、50个头文件、38张UI资源图及23个Qt工程配置文件(.pri/.pro),辅以CSS样式、QRC资源描述及动态链接库等,整体3.17MB,结构完整、模块划分清晰。已有60人学习下载,适合通过真实工业级地面站框架理解QT信号槽机制、多线程串口通信、飞行参数可视化(gaugeplane.cpp等)及ANOPROTOCOL数据解析(package_ano_422.cpp)等关键技术点,代码注释充分、部署门槛低,是深入掌握无人机系统软件层开发的优质实践入口。

1. 这不是普通 Qt 项目:一个真实无人机地面站的编译链路,卡在 qextserialport_unix.cpp 就等于卡死整条遥控通路

你手头这份“无人机地面站项目”,表面看是 QT Creator 打开就能跑的 GUI 工程,实际却是一条精密咬合的实时通信链——从frameserial.cpp的帧解析、到qextserialport_win.cpp/qextserialport_unix.cpp的跨平台串口抽象、再到package_ano_422.cpp对 ANO 协议(常见于国产飞控如 STM32F4 系列)的 422 总线打包,每一行都在和毫秒级时延、字节序、波特率容错率搏斗。它不依赖 Qt Quick 或 QML 渲染动画,而是靠gaugeplane.cpp实时刷新姿态角、progressbarwater.cpp动态渲染电池电量水位——这些控件背后是serialdownload.cpp持续吞吐的遥测流。用 QT Creator 编译它,本质是在构建一个硬实时性要求下的跨平台串口通信中枢,而非普通桌面应用。适合正在调试 PX4/ArduPilot 飞控数据链、需要自定义地面站协议栈、或想深入理解串口驱动层与 Qt 事件循环耦合机制的嵌入式开发者与飞控算法工程师。新手照着.pro文件点 Build 很可能失败,因为qextserialport不是 Qt 官方模块,它的 Unix/Linux 编译路径与 Windows 完全不同,而cmake预编译的写法在此项目中几乎不可用——.pro+ qmake 才是唯一正解。

2. 为什么必须用 qmake 而非 CMake:qextserialport 的平台条件编译逻辑深度绑定 Qt 构建系统

2.1 qextserialport 的跨平台实现机制决定了构建工具选型

qextserialport是一个已停止维护但仍在大量无人机地面站中服役的串口封装库,其核心设计哲学是:同一份头文件qextserialport.h,通过宏开关控制底层实现分支。查看源码可发现:

  • qextserialport_unix.cpp依赖termios.h和fcntl.h,使用open()/ioctl()直接操作/dev/ttyUSB0等设备节点;
  • qextserialport_win.cpp则调用 Windows APICreateFile()/SetCommState();
  • qextserialport.cpp是通用接口层,包含QextSerialPort类声明与虚函数定义。

这种设计意味着:编译时必须让构建系统识别当前目标平台,并仅编译对应.cpp文件。Qt 的 qmake 通过win32/unix等作用域(scope)原生支持该逻辑,而 CMake 需手动编写if(WIN32)/if(UNIX)判断并控制源文件列表——但本项目.pro文件中已固化了该逻辑,强行切换构建系统将导致qextserialport_unix.cpp在 Windows 下被编译(报termios.h: No such file or directory),或qextserialport_win.cpp在 Linux 下被链接(报undefined reference to CreateFileA)。这是qt creator项目无法用cmake预编译的写法替代的根本原因。

提示:不要尝试用cmake -G "Unix Makefiles"导入.pro项目。qmake 生成的Makefile中包含QMAKE_TARGET、QMAKE_HOST等 Qt 特有变量,CMake 无法解析。若需 CMake 支持,必须重写整个构建脚本,且需重新实现qextserialport的平台选择逻辑。

2.2 正确配置 QT Creator 的 Kit 与 qmake 路径:避免 “No valid kits found”

QT Creator 不是编译器,它只是调用 qmake 的前端。编译失败的第一道关卡往往是 Kit 配置错误。以 Ubuntu 22.04 + Qt 5.15.2 为例:

# 1. 确认系统已安装 Qt5 开发包(关键!) sudo apt install qt5-default qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools # 2. 查找 qmake 路径(注意:不是 /usr/bin/qmake,而是 Qt 安装目录下的 qmake) ls /usr/lib/x86_64-linux-gnu/qt5/bin/qmake # 或若使用在线安装版 Qt:/opt/Qt/5.15.2/gcc_64/bin/qmake # 3. 在 QT Creator 中配置 Kit: # Tools → Options → Kits → Add Kit # - Name: "Qt 5.15.2 GCC 64-bit" # - Device type: Desktop # - Compiler: System GCC (x86_64-linux-gnu-g++-11) # - Qt version: Browse → 选择上述 qmake 路径 # - Debugger: system gdb

若 Kit 显示为灰色(No valid kits found),说明qmake返回的QT_INSTALL_PREFIX与系统 Qt 库路径不匹配。此时需检查:

# 运行 qmake -query 查看 Qt 安装路径 /usr/lib/x86_64-linux-gnu/qt5/bin/qmake -query # 输出应包含: # QT_INSTALL_PREFIX:/usr/lib/x86_64-linux-gnu/qt5 # QT_INSTALL_LIBS:/usr/lib/x86_64-linux-gnu/qt5/lib # 若 LIBS 路径为 /usr/lib/x86_64-linux-gnu,而 qmake 报告为 /usr/lib,则需软链接修复: sudo ln -sf /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu/qt5/lib

2.3 修改 .pro 文件以适配现代 Qt 版本:解决QextSerialPort类未声明错误

原始.pro文件通常基于 Qt 4 编写,而 Qt 5+ 移除了QextSerialPort的内置支持,需显式包含头文件路径并链接-lqextserialport。在项目根目录.pro文件末尾添加:

# --- BEGIN qextserialport 适配段 --- # 声明 qextserialport 源码位置(假设与 main.cpp 同级) HEADERS += \ qextserialport.h \ frameserial.h \ frameplanesetting.h \ serialdownload.h \ gaugeplane.h \ package_ano_422.h \ progressbarwater.h SOURCES += \ qextserialport.cpp \ qextserialport_unix.cpp \ qextserialport_win.cpp \ frameserial.cpp \ frameplanesetting.cpp \ serialdownload.cpp \ gaugeplane.cpp \ package_ano_422.cpp \ progressbarwater.cpp # 关键:为 Unix 平台启用 termios 支持 unix { DEFINES += QEXTSERIALPORT_UNIX LIBS += -lqextserialport # 若提示找不到 libqextserialport.so,需先编译该库(见 3.2 节) } win32 { DEFINES += QEXTSERIALPORT_WIN LIBS += -lqextserialport } # --- END qextserialport 适配段 ---

注意:LIBS += -lqextserialport表示链接名为libqextserialport.so(Linux)或qextserialport.lib(Windows)的静态/动态库。若项目未提供预编译库,则必须先编译qextserialport源码(见下一章)。

3. 编译 qextserialport 库:从源码构建 libqextserialport.so 的完整流程与平台差异

3.1 Linux 下编译 libqextserialport.so:解决termios.h与QMutex冲突

qextserialport源码包中通常包含qextserialport.pro文件。在 Ubuntu 下编译需分两步:

# 进入 qextserialport 源码目录(假设路径为 ./3rdparty/qextserialport/) cd ./3rdparty/qextserialport/ # 1. 生成 Makefile(指定 Qt5 qmake 路径) /opt/Qt/5.15.2/gcc_64/bin/qmake qextserialport.pro # 2. 编译(关键:添加 -fPIC 生成位置无关代码) make -j$(nproc) # 3. 检查生成物 ls -l libqextserialport.so* # 应输出:libqextserialport.so.1.2.0 libqextserialport.so.1 libqextserialport.so

若编译报错error: 'QMutex' does not name a type,说明qextserialport.h中缺少 Qt 头文件包含。需手动修改qextserialport.h:

// 在 #include <QThread> 下方添加 #include <QMutex> #include <QWaitCondition> #include <QTimer>

注意:qextserialport的QextSerialPort类继承自QIODevice,而QIODevice在 Qt 5 中已移至QtCore模块,因此必须确保qextserialport.pro中包含QT += core。若缺失,编辑该文件并添加:

QT += core TARGET = qextserialport TEMPLATE = lib

3.2 Windows 下编译 qextserialport.lib:MinGW 与 MSVC 工具链的选择陷阱

Windows 用户常因工具链不匹配导致qextserialport_win.cpp编译失败。关键原则:QT Creator 的 Kit 中选择的 Compiler 必须与 qmake 生成的 Makefile 一致。

  • 若 Kit 使用MinGW 11.2(推荐,兼容性好):

    # 在 Qt MinGW 终端中执行(非 cmd) cd ./3rdparty/qextserialport/ qmake -spec win32-g++ qextserialport.pro mingw32-make -j4 # 生成 libqextserialport.a 和 qextserialport.dll
  • 若 Kit 使用MSVC 2019:

    :: 在 x64 Native Tools Command Prompt for VS 2019 中执行 cd .\3rdparty\qextserialport\ qmake -spec win32-msvc qextserialport.pro nmake :: 生成 qextserialport.lib 和 qextserialport.dll

提示:qextserialport_win.cpp中的#include <windows.h>会与某些 Qt 版本的qglobal.h冲突。若报错error C2011: 'OVERLAPPED' : 'struct' type redefinition,需在qextserialport_win.cpp开头添加:

#define WIN32_LEAN_AND_MEAN #include <windows.h>

3.3 将 libqextserialport.so 部署到项目可链接路径

编译成功后,需让主项目.pro文件能定位到该库。两种可靠方式:

方式操作适用场景
绝对路径链接在.pro中写LIBS += -L/home/user/project/3rdparty/qextserialport/ -lqextserialport快速验证,但路径硬编码,不便于协作
相对路径 + INSTALLS在qextserialport.pro中添加:
target.path = $$OUT_PWD/../lib
INSTALLS += target
然后运行make install
推荐。$$OUT_PWD指向构建目录,../lib即主项目根目录下的lib/文件夹

验证链接是否生效:

# 编译主项目后,检查可执行文件依赖 ldd ./build-groundstation/GroundStation | grep qext # 应输出:libqextserialport.so.1 => /path/to/libqextserialport.so.1 (0x...)

4. 解决package_ano_422.cpp编译期协议校验失败:ANOPacket 结构体对齐与字节序陷阱

4.1 ANO 协议帧结构解析:为什么#pragma pack(1)是刚需

package_ano_422.cpp封装的是 APM/PIXHAWK 飞控常用的 ANO(Anonymous)协议,其典型帧格式为:

字段长度(字节)说明
Header1固定值 0xAA
Len1数据长度(不含 Header/Len/Checksum)
Cmd1命令 ID(如 0x01=姿态,0x02=GPS)
DataLen变长负载,含 float/int16 等类型
Checksum1Header + Len + Cmd + Data[0] + ... + Data[Len-1]的低 8 位

问题在于:C++ 结构体默认按 4 字节对齐,而Data字段中的float pitch; int16_t roll;若未强制 1 字节对齐,会导致sizeof(ANOPacket)> 实际协议长度,memcpy复制时溢出。必须在package_ano_422.h中声明:

#pragma pack(push, 1) // 关键:强制 1 字节对齐 typedef struct { uint8_t header; // 0xAA uint8_t len; // Data 长度 uint8_t cmd; // 命令 uint8_t data[255]; // 最大负载 uint8_t checksum; // 校验和 } ANOPacket; #pragma pack(pop)

注意:#pragma pack(1)是 GCC/Clang/MSVC 通用指令,但需确保所有包含该头文件的.cpp都受其影响。若frameserial.cpp也使用ANOPacket,则必须在frameserial.h中#include "package_ano_422.h",而非重复定义。

4.2 字节序转换:Linux x86_64 与 STM32 ARM 的 endianness 差异

ANO 协议规定Data中的float以little-endian存储。但 x86_64 主机与 ARM Cortex-M4 飞控均是小端,看似无需转换。然而qextserialport在 Linux 下读取串口数据时,QByteArray::data()返回的指针直接映射到内存,若ANOPacket结构体未正确对齐,reinterpret_cast<float*>(pkt.data + 4)会读取错误地址。安全做法是显式转换:

// 在 package_ano_422.cpp 中解析 float float parseFloat(const uint8_t* src) { union { uint32_t i; float f; } u; u.i = (src[0]) | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); return u.f; } // 使用 float pitch = parseFloat(pkt.data); // pkt.data[0..3] 是 pitch 的 4 字节

此写法绕过结构体对齐风险,且明确表达字节序意图,避免qscintilla下载与编译等第三方库引入的隐式内存布局干扰。

4.3 编译期校验:用 static_assert 确保协议结构体尺寸

在package_ano_422.h末尾添加编译期断言,防止未来修改破坏协议:

// ANO 协议最大帧长 = 1(header) + 1(len) + 1(cmd) + 255(data) + 1(checksum) = 259 static_assert(sizeof(ANOPacket) == 259, "ANOPacket size mismatch! Check #pragma pack and field order."); // 验证 header 偏移量为 0 static_assert(offsetof(ANOPacket, header) == 0, "ANOPacket header offset must be 0");

若sizeof(ANOPacket)计算结果不为 259,qmake 编译时将直接报错static assertion failed,而非运行时崩溃。这是比qml编译错误更底层、更致命的协议一致性保障。

5. 验证串口通信链路:用stty与hexdump定位serialdownload.cpp数据流中断点

5.1 在 Linux 下绕过 QT Creator,用命令行复现串口初始化失败场景

当serialdownload.cpp在 QT Creator 中点击 Run 时卡在port->open(QIODevice::ReadWrite),优先排除硬件与权限问题:

# 1. 确认设备存在且权限正确 ls -l /dev/ttyUSB0 # 应输出:crw-rw---- 1 root dialout 188, 0 May 20 10:00 /dev/ttyUSB0 # 若用户不在 dialout 组,执行: sudo usermod -a -G dialout $USER # 注销重登录生效 # 2. 用 stty 手动配置串口(模拟 qextserialport 初始化) stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb -crtscts # 参数含义:115200 波特率,8 数据位,1 停止位,无校验,无硬件流控 # 3. 发送测试帧(模拟 groundstation 发送心跳) echo -ne '\xaa\x00\x01\x00\xab' | dd of=/dev/ttyUSB0 bs=1 conv=notrunc # \xaa=header, \x00=len, \x01=cmd(heartbeat), \x00=checksum, \xab=校验和(示例) # 4. 用 hexdump 监听飞控返回(验证物理链路) hexdump -C /dev/ttyUSB0 | head -20 # 正常应看到连续的 ANO 帧,如:aa 10 01 00 00 00 00 00 ...

若hexdump无输出,说明飞控未响应或接线错误;若输出乱码,检查stty参数是否与飞控固件配置一致(常见错误:飞控设为 57600,地面站设为 115200)。

5.2 在 QT Creator 中启用 qextserialport 调试日志:捕获QextSerialPort::open()的 errno

qextserialport的open()方法内部调用open()系统调用,失败时设置errno。在serialdownload.cpp的connectToPort()函数中插入调试:

bool SerialDownload::connectToPort(const QString &portName) { port = new QextSerialPort(portName, QextSerialPort::EventDriven); port->setBaudRate(BAUD115200); port->setFlowControl(FLOW_OFF); port->setParity(PAR_NONE); port->setDataBits(DATA_8); port->setStopBits(STOP_1); if (!port->open(QIODevice::ReadWrite)) { // 关键:打印 errno qDebug() << "QextSerialPort open failed:" << port->errorString() << "errno:" << errno << "strerror:" << strerror(errno); // 常见 errno:13=Permission denied, 2=No such file, 6=Device busy return false; } return true; }

注意:errno是全局变量,必须在port->open()紧接着读取,否则后续 Qt 调用可能覆盖其值。qextserialport的errorString()仅返回 Qt 封装的字符串,而strerror(errno)给出 POSIX 标准错误描述,对定位debian如何源码编译git等系统级问题更精准。

5.3 使用udevadm固定 USB 串口设备名:避免/dev/ttyUSB0变成/dev/ttyUSB1

当同时接入多个 USB 设备(如 GPS 模块、数传电台),Linux 可能将飞控分配为/dev/ttyUSB1,导致地面站连接失败。解决方案是创建 udev 规则:

# 创建规则文件 sudo nano /etc/udev/rules.d/99-drone-serial.rules # 添加内容(根据 lsusb 获取飞控的 idVendor/idProduct): SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="drone_fc" # 保存后重载规则 sudo udevadm control --reload-rules sudo udevadm trigger # 检查是否生效 ls -l /dev/drone_fc # 应输出:lrwxrwxrwx 1 root root 7 May 20 10:00 /dev/drone_fc -> ttyUSB0

在serialdownload.cpp中将端口名改为/dev/drone_fc,即可彻底规避设备名漂移问题。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询