ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

飞控二次开发入门:外挂树莓派与自定义模块两条路径详解

飞控二次开发入门:外挂树莓派与自定义模块两条路径详解 一个从大学就开始折腾航模、后来搞了几年无人机开发的人我见过太多同学拿到 Pixhawk 之后做的第一件事就是把 PX4 源码从 GitHub 上 clone 下来然后盯着src/modules目录发呆两小时最后默默删掉仓库关掉电脑。飞控二次开发真不是这么玩的。这个领域确实容易让人迷失方向网上一搜“飞控二次开发”出来的不是源码仓库就是晦涩难懂的论文再点开几个视频发现大家都在玩树莓派、移植视觉、跑自主飞行好像每个人都已经搭好了完整的无人机系统。但实际上飞控二次开发是有清晰路径的而且它比其他工业软件比如 Creo、NX、QGIS 那些二次开发要直观得多——因为你能立刻看到飞机在天上飞。这篇文章我想把飞控二次开发的两条主流路径一次讲透一条是外挂树莓派不碰飞控内部逻辑靠串口通信在外围做文章另一条是自定义模块直接住进飞控固件里改的是控制算法和任务逻辑。适合刚准备入门的航模爱好者、无人机开发者、以及想把 ROS 和飞控结合起来的同学参考。1. 为什么劝你别一上来就啃源码先说说我的观点源码不是不能啃而要等跑通流程之后再啃。很多新手以为二次开发 读源码 改源码。这个想法在飞控领域尤其容易翻车原因是飞控的代码复杂度远高于普通应用软件。以 PX4 为例它包含传感器驱动、状态估计EKF、姿态控制、位置控制、导航任务、通信协议、安全机制等一大堆子系统整个仓库几十万行 C/C 代码实际编译出来的固件还依赖实时操作系统NuttX。别说读懂想找到“改哪一行能起效”都是个技术活。还有一个更现实的问题源码改了你怎么验证飞控不像普通的软件改坏了重新装一个就能继续。它跑在 4~6 个旋翼上面任何逻辑错误都可能导致炸机。如果你连飞控和地面站的通信都还没打通连机载日志都不会看就直接去改 SIM 模式的源码出了问题你根本不知道是自己写错了还是系统本来就那样。所以我一直建议初学者把二次开发分成两个阶段第一阶段不改飞控内部通过对外接口串口、MAVLink 协议、MAVSDK控制它。第二阶段对系统有一定理解之后再进入源码层写自定义模块。这两个阶段不是对立的而是递进的。很多人担心“外挂树莓派”是不是太低级其实完全不是。你看工业界真正落地的无人机方案绝大多数都是“飞控机载电脑”架构树莓派、Jetson 或者香橙派作为上位机飞控作为执行单元。真正做到源码级别的二次开发反而集中在飞控厂商和极少数算法团队。2. 飞控二次开发的完整路径地图在进入具体操作之前先梳理一下飞控二次开发的四个层级这样你就能明白自己现在站在哪里下一步该往哪走。2.1 四个开发层级对应不同能力要求第一层参数级。通过地面站改 PID、改飞行模式、调电调和遥控器曲线。这是最基础的一层几乎每个人都经历过。第二层外设级。给飞控接 GPS、数传、摄像头云台、光流传感器、LED 灯带。这需要懂一些硬件接线和串口配置但不需要动飞控内部逻辑。第三层外挂计算单元机载电脑。通过树莓派、Jetson 等外部计算设备连接飞控。这是目前应用最广、投资回报最高的一种二次开发方式也是本文第一条路径的重点。第四层源码与自定义模块。直接修改或新增飞控固件中的模块比如自定义控制律、传感器融合算法、特殊任务逻辑。这是本文第二条路径的重点。很多人一上来就把目标定在第四层忽略了前三层的积累。这就像新司机非要学漂移连油门刹车都没踩熟结果可想而知。2.2 两条主线外挂树莓派 vs 自定义模块一句话总结这两条路线的区别外挂树莓派是在飞控外面加一个“外脑”自定义模块是给飞控本身“换脑”。外挂树莓派的好处非常明显开发语言用 Python生态极其丰富图像处理、机器学习、ROS 库都能直接用写错了代码不会影响飞控稳定性最坏结果就是树莓派重启一下调试起来也方便连个屏幕看输出、SSH 上去跑脚本都行。它的缺点是高频率、高实时性的任务做不了毕竟串口通信本身有延迟而且飞控和树莓派是两套独立的系统协同工作会有一定的架构复杂度。自定义模块则完全不同。它是把代码编译进飞控固件直接跑在 STM32 等单片机芯片上实时性有硬件级保证。比如你想实现一个新的滤波算法、一条自定义的飞行任务或者想在姿态控制里加一个补偿项这些绕开飞控内部是做不到的。但代价是开发门槛高需要懂 C/C、懂嵌入式编译、懂 uORB 消息机制还要承担刷固件失败导致飞控变砖的风险。我个人给你的建议是如果你过去的工作重心在应用层比如视觉避障、航线规划、物流配送、电力巡检优先走外挂树莓派路线如果你对飞控本身感兴趣想深入系统内部那自定义模块这条路早晚要过。3. 路径一外挂树莓派用 Python 给飞控加外挂这是我认为最容易跑通、也最容易被忽视的一条路。它的本质很简单树莓派通过串口和飞控通信飞控把姿态、位置、电压等状态告诉树莓派树莓派再根据你的逻辑发出控制指令。拿一套常见的穿越机配置举例LQRC APEX 小胡子 5 寸机架 SpeedyBee F405 飞控 55A 电调这是很多玩家跑视觉穿越机的标配。F405 飞控从型号上就能看出来用的是 STM32F405 芯片算力有限跑不了复杂的视觉算法但它可以稳定地执行姿态控制。你在它上面挂一个树莓派树莓派负责把摄像头画面算得出哪里有障碍物再把“该往左躲”的决策通过串口告诉飞控飞控负责真正把飞机飞过去。这就是“外挂”的核心价值各干各的擅长的事。3.1 硬件接线一根杜邦线解决通信连接树莓派和飞控之间的通信最简单可靠的方式是 UART 异步串口。树莓派 4B 或 5 的 GPIO 排针上14 脚是 TXD、15 脚是 RXD把这两个脚接到飞控的 TELEM 串口上再连通地线就完成了物理连接。注意三个坑发射接接收接收接发射。树莓派的 TXD 要接飞控的 RX树莓派的 RXD 要接飞控的 TX。很多第一次接线的人栽在这里。一定要共地。树莓派的 GND 和飞控的 GND 要连在一起否则串口数据会出现乱码。飞控串口电平一般是 3.3V树莓派的 GPIO 也是 3.3V可以直连。如果飞控板子比较老要注意确认电平是否兼容不匹配就需要电平转换模块。接好线之后在树莓派端查一下端口名通常是/dev/ttyAMA0或/dev/serial0。如果用的是树莓派 4B/5默认硬件串口可能被蓝牙占用需要做一步处理后面会细说。3.2 飞控端串口配置别漏掉这两个关键项硬件接好了飞控还得告诉它“老子的这个串口要用来跟树莓派说话”。以 PX4 飞控为例连接 QGroundControl 后进入参数设置界面找到MAV_1_CONFIG把它设置为你接线的那个串口编号比如 TELEM2。然后设置MAV_1_BAUD为 57600。两个参数都要改只改一个飞控不会正常输出 MAVLink 数据。如果是 ArduPilot 固件对应的是SERIAL2_PROTOCOL2、SERIAL2_BAUD57。参数名略有差异但逻辑一模一样启用串口、指定协议、设置波特率。如果你用的是 Betaflight 穿越机飞控情况稍有不同。Betaflight 默认用 MSP 协议和外部设备通信在 CLI 里输入resource查看可用 UART然后用serial 1 0 115200 57600 0 115200这种命令把串口配置成 MSP 模式。树莓派端再配合msp相关的 Python 库来读取姿态。这一块略显折腾但原理上还是串口通信那点事。3.3 树莓派端装好环境pymavlink 是主角树莓派端的工作本质上是把 MAVLink 协议用起来。MAVLink 是 Pixhawk 项目组制定的无人机通信协议它定义了飞控和地面站、机载电脑之间通信的消息格式。好消息是你不需要手写协议解析Python 生态里已经有现成的库pymavlink和dronekit。在树莓派上执行sudo apt update sudo apt install python3-pip sudo usermod -a -G dialout $USER pip3 install pymavlink pyserialusermod那一步是把当前用户加入dialout组否则你很可能遇到串口权限不够的问题。改完组之后需要重新登录或者重启树莓派。如果 pip 安装速度慢可以把 pip 源换成国内镜像比如清华源或阿里云源具体做法是编辑~/.pip/pip.conf。这个属于基础操作就不展开了。3.4 实战读取姿态数据先让数据动起来环境装完之后第一件事不是急着写控制逻辑而是先确认树莓派能收到飞控的数据。我这里给一个最小可用的读取代码from pymavlink import mavutil # 串口设备名和波特率要和飞控端配置一致 master mavutil.mavlink_connection(/dev/ttyAMA0, baud57600) # 等待飞控心跳包 master.wait_heartbeat() print(心跳连接成功飞控已上线) while True: msg master.recv_match(typeATTITUDE, blockingTrue) if msg: print(froll: {msg.roll:.2f} pitch: {msg.pitch:.2f} yaw: {msg.yaw:.2f})运行之后如果串口线和飞控参数都配置正确你会看到终端里不断滚出当前的姿态角数据。手动晃动一下飞控数值会跟着变化。看到这个画面说明“外挂”这条路已经打通了一半。如果你手边有树莓派官方摄像头 OV5647还可以同时把画面推上来自主的视觉任务树莓派计算视野里的目标位置再把偏差换算成控制指令发给飞控。很多视觉避障和循迹飞行的原型就是这么搭出来的。3.5 实战从树莓派发送控制指令读数据只是第一步真正的二次开发是要让树莓派作为“大脑”去指挥飞控。这里以一个常见的解锁并起飞动作为例import time from pymavlink import mavutil master mavutil.mavlink_connection(/dev/ttyAMA0, baud57600) master.wait_heartbeat() # 切换飞控模式到 GUIDED引导模式 mode GUIDED mode_id master.mode_mapping()[mode] master.set_mode(mode_id) master.arm_disarm(True) time.sleep(2) # 发送起飞指令目标高度 10 米 master.mav.command_long_send( master.target_system, master.target_component, mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 0, 0, 0, 0, 0, 10 )如果你想做更细粒度的航点控制比如让飞机飞到指定的经纬度和高度可以发送SET_POSITION_TARGET_GLOBAL_INT消息。这类消息的参数比较多建议去查 pymavlink 的官方文档这里不展开详细字段了。提示在室内测试时一定要绑好机架或者去掉桨叶相信我每个人在第一次跑通控制脚本时都会手忙脚乱。关于代码里没有解释清楚的协议细节我补充一个经验MAVLink 消息分为请求和命令两类很多新手只记得发命令却忘了检查飞控是否真的执行了。调试阶段建议把recv_match打印出来看飞控返没返回COMMAND_ACK只有收到 ACK 才代表命令真正被接受。另外如果你的飞控是 Betaflight 系统的上面这套 MAVLink 代码不适用记得找 MSP 协议的库。4. 路径二自定义模块真正住进飞控内部外挂树莓派玩熟之后你会发现有些事在外部做不了比如你想给飞控加一个“检测到剧烈震动时自动降低油门响应”的功能或者你想实现一种飞控本身没有的飞行模式。这时候就需要进入自定义模块的世界了。这条路径以 PX4 为例来讲因为 PX4 的模块化设计在开源飞控里是最清晰的非常适合学习。4.1 先用仿真跑起来开发环境搭建自定义模块开发的第一步不是买硬件而是把仿真环境跑起来。PX4 官方支持 Gazebo 仿真。在你的开发机不需要是树莓派上执行git clone --recursive https://github.com/PX4/PX4-Autopilot.git cd PX4-Autopilot make px4_sitl gazebo第一次编译会下载工具链和依赖库时间根据网络情况从十几分钟到一个小时不等。如果卡在下载环节优先检查网络并考虑使用国内镜像源加速。仿真环境启动后你会看到一个虚拟的无人机在 Gazebo 里出现这个时候你就可以在不炸机的前提下随便折腾代码重启模拟器就恢复了。我个人强烈建议自定义模块开发的前期所有测试都在仿真中进行等逻辑稳定后再刷到真机上。4.2 摸清模块长什么样uORB 通信模型PX4 内部模块之间不直接调用函数而是通过一个叫 uORB 的消息总线进行通信。每个模块要么订阅subscribe自己关心的话题要么发布publish自己产出的数据。举个例子姿态估计模块计算完飞机姿态后会发布vehicle_attitude话题。姿态控制模块订阅这个话题拿到姿态数据后计算控制量再发布给电机驱动模块。整个过程像一个公开的布告栏任何人可以把信息贴上去任何人也可以来读。理解这个模型是自定义模块开发的关键。你不需要关心别人怎么实现的只需要知道你需要的消息在哪个话题上以及它包含什么字段。常用的话题包括vehicle_attitude姿态四元数vehicle_local_position本地位置和速度sensor_combined传感器原始数据battery_status电池电压和剩余电量4.3 写一个自定义模块需要准备什么一个最基本的 PX4 自定义模块通常包含三个部分模块入口程序、CMakeLists.txt 编译配置文件、启动脚本。模块入口程序的简化示意#include px4_platform_common/px4_config.h #include px4_platform_common/tasks.h #include uORB/uORB.h #include uORB/topics/vehicle_attitude.h extern C __EXPORT int my_hello_main(int argc, char *argv[]); int my_hello_main(int argc, char *argv[]) { int sub orb_subscribe(ORB_ID(vehicle_attitude)); struct vehicle_attitude_s att{}; while (true) { orb_copy(ORB_ID(vehicle_attitude), sub, att); PX4_INFO(roll: %f pitch: %f, (double)att.roll, (double)att.pitch); px4_usleep(100000); } return 0; }CMakeLists.txt 里声明模块px4_add_module( MODULE modules__my_hello MAIN my_hello STACK_MAIN 2000 SRCS my_hello.cpp DEPENDS )这段代码的作用是订阅姿态话题然后每 100 毫秒打印一次滚转和俯仰角。虽然是简化版但它体现了自定义模块的核心结构入口函数、订阅、在循环里处理数据。正式的 PX4 模块会基于ModuleBase类编写支持start/stop/status等命令行指令方便在 PX4 控制台里动态管理。这里不展开写完整类框架你到对应版本的src/examples目录里找一个 hello 模块参考即可。4.4 编译部署流程与版本注意点模块代码写好后把它放到src/modules/my_hello/目录下然后执行make px4_fmu-v5编译完成后会生成固件文件。刷固件可以用 QGroundControl 的地面站界面选择“自定义固件”手动刷入也可以用编译环境直接上传。注意你编出来的固件必须和飞控板型号匹配比如 SpeedyBee F405 就不能刷 px4_fmu-v5 这种针对 Pixhawk 4 的固件。刷错固件轻则飞控不启动重则变砖。启动自定义模块这一步也容易踩坑。不同 PX4 版本里启动脚本的位置不一样有的在ROMFS/px4fmu_common/init.d/有的在ROMFS/px4fmu_common/init.d-posix/还有的可能在板级配置目录里。最靠谱的做法是参照你当前版本中其他模块的注册方式照着抄一遍。注意PX4 的版本更新速度很快跨版本之间的模块 API、编译系统、启动脚本目录都有变动。网上搜到的教程大概率是旧版本的遇到编译报错先检查版本兼容性。5. 两条路径怎么选我给出一个参考决策表很多人在群里问“我有树莓派要不要再买一块 Pixhawk”、“我该学 Python 还是学 C 做飞控二次开发”。这种问题其实没有标准答案但可以从实际需求倒推。判断维度外挂树莓派自定义模块你擅长的语言Python、ROSC/C、嵌入式改动的位置飞控外部逻辑飞控固件内部实时性要求一般串口有延迟高直接进调度器典型应用视觉避障、航线规划、环境感知自定义控制律、传感器融合、故障检测故障代价树莓派重启即可飞控失灵可能需要重新刷固件上手时间顺利的话半天跑通 demo保守估计需要一周以上硬件需求额外一台树莓派/机载电脑一块支持对应固件的飞控板说实话这两条路径并不互斥反而是配合使用的。我有一个很典型的经历最开始我用树莓派给飞控写了一个“电量低于 20% 自动返航”的外挂脚本跑得很欢乐。后来发现串口链路偶尔抽风如果树莓派死机了整个返航逻辑也跟着失效。之后我把同样的逻辑下沉为飞控内部的一个自定义模块虽然开发周期长了不少但可靠性高多了。所以我的建议是先走通外挂路线把任务逻辑、控制指令的来龙去脉搞清楚等你的需求对实时性、可靠性提出更高要求时再考虑把它迁移成自定义模块。6. 常见问题与排查技巧实录做飞控二次开发遇到的问题五花八门但真正卡住绝大多数人的就那几个。我把这些年踩过的坑整理成一张速查表现象可能原因排查思路树莓派收不到飞控心跳接线错误、未配置串口参数、波特率不一致先检查 TX/RX 是否交叉再检查飞控端MAV_1_CONFIG和MAV_1_BAUD最后确认波特率是否匹配串口打开提示 Permission denied当前用户不在 dialout 组执行sudo usermod -a -G dialout $USER重新登录后生效树莓派串口数据乱码未共地、波特率错误确认 GND 是否连通树莓派和飞控波特率设为一致树莓派 4B 串口没有输出硬件串口被蓝牙占用在/boot/config.txt中增加dtoverlaydisable-bt然后重启刷固件后飞控无反应固件和板子型号不匹配确认你使用的固件目标和你飞控的主控芯片一致仿真启动后画面卡顿Gazebo 资源占用高降低仿真分辨率关掉其他大型软件树莓派突然重启供电不足树莓派 4B 对 5V 电流要求高不要只靠飞控的 BEC 供电用独立电源模块这里重点展开几个容易忽略的细节第一个是树莓派 4B 的串口问题。树莓派 4B 的硬件 UART 默认分配给了蓝牙模块如果不在/boot/config.txt里加上dtoverlaydisable-bt你用/dev/ttyAMA0收到的数据可能全是噪音。树莓派 5 的默认状态又不太一样拿到新板子先查一下当前系统的串口映射情况再动手。第二个是供电问题。SpeedyBee F405 这类穿越机飞控的 BEC 主要是给接收机、图传供电的电流余量不会太大。树莓派 4B 满载时可以吃到 3A 的电流直接从飞控取电非常危险轻则树莓派重启重则把飞控的电调供电部分烧掉。我的做法是给树莓派单独配一个 5V/5A 的 BEC或者直接用独立的充电宝供电跟动力系统彻底隔离。第三个是 GPS 场景。很多人把树莓派 3B 接了 GPS 模块做定位但忘了 GPS 模块和飞控之间可能存在频率冲突导致搜星很慢。树莓派端的 GPS 和飞控端的 GPS 最好错开安装位置避免互相干扰。第四个是摄像头权限问题。树莓派接 OV5647 摄像头后如果运行 OpenCV 提示无法打开设备多半是没启用摄像头接口或者当前用户没有 video 组权限。在树莓派终端执行sudo raspistill -o test.jpg如果这个都出不了图就说明摄像头驱动层面有问题别急着调代码。7. 一点个人经验先跑通再深入做飞控二次开发这几年我最大的体会是这个领域不缺少资料缺少的是循序渐进的节奏感。很多人在第一个星期就试图搞清楚 PX4 所有模块的源码结果被 EKF、卡尔曼滤波、控制分配这些名词劝退。而如果你换一个思路先让树莓派和飞控对上话再用 MAVLink 下发指令亲眼看到飞机按照你的命令行进动作这种正反馈会激发你持续深入的动力。等到外挂逻辑写得顺手了你对飞控工作方式的理解已经足够支撑你去读源码、写模块了。还有一个实用技巧想分享无论走哪条路都要养成看日志的习惯。外挂树莓派阶段把你脚本里的关键变量都记录到文件里自定义模块阶段善用 PX4 的日志系统和logger模块。真机飞行前几天在仿真环境里反复测试你写的所有边缘场景。我认识不少开发者就是靠着一份详尽的日志在一次炸机之后定位到了飞控内部一个极其隐蔽的时序问题。飞控二次开发这条路并不难难的是别在一开始就用错方法。从外挂树莓派开始让 Python 成为你探索天空的入口等你想往深了走再一头扎进源码那时你看到的将不是一个陌生的代码海洋而是一套你已经熟悉其行为的系统正在等待你用代码给它注入新的灵魂。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进