尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ArduPilot源码深度解析:从架构到自定义飞控模式实战

ArduPilot源码深度解析:从架构到自定义飞控模式实战 简介APM飞控源码是一份面向无人机开发者与爱好者的开源项目资料基于ArduCopter 3.2.1版本进行精简保留了多旋翼飞行器最核心的姿态控制、高度保持、位置锁定、航点导航等功能模块。源码在Pixhawk等常用飞控硬件上具备良好适配性且专门加入中文注释降低了阅读门槛适合初学者在Visual Studio中结合调试工具逐步理解算法逻辑。资源包以zip格式打包大小约24.8MB涵盖ArduCopter目录下PID控制器、传感器处理、飞行模式切换、地面站通信等关键源码文件便于按需研读。目前已有803人学习下载通过这份代码可以深入掌握姿态估计、位置控制及无线数据交互的实现细节并在此基础之上进行二次开发或算法优化是一份兼顾学习与实践引导价值的飞控源码资料。 先放一句话开头如果你问我做开源飞控这些年哪一套代码最值得反复啃我的答案一直是 ArduPilot也就是大家常说的 APM 飞控源码。它不只是某一个飞控板的固件而是一整套涵盖多旋翼、固定翼、车辆、船舶、水下潜航器的完整自动驾驶系统。这篇文章不会跟你从头念一遍源码目录而是想聊聊我实际研究这套源码时的整体思路、核心机制、控制链路怎么读以及如何基于源码去新增一个自定义飞控模式。想入坑开源飞控的朋友可以对照这份路线少走一些弯路。很多人第一次接触 APM 源码时第一反应是“太大了”“从哪看起”。确实ArduPilot 的代码量在开源飞控里算非常庞大的但它有一个好处模块划分极其清晰尤其是libraries下的核心算法库几乎每一个都独立可读。这篇文章我按自己实际下钻的顺序来组织从目录结构、调度机制、姿态控制到动手扩展一个模式最后再聊聊编译调试和常见坑尽量让这份记录同时适合新手入门和老手查漏补缺。1. APM 飞控源码的整体架构与目录拆解1.1 源码仓库与版本选择先明确一个前提现在所说的 APM 飞控源码基本指 ArduPilot 官方仓库。很多人会把 APM 和 PX4 搞混虽然都源于早期 APM 项目但两者架构差异很大。APM 在代码风格上更偏向经典嵌入式 C对象化封装、模块耦合低对硬件抽象做得特别彻底。如果你是从单片机转过来读 APM 会比读 PX4 更容易建立完整的飞控知识体系。版本选择上我个人建议从Copter-4.5左右的稳定分支开始。这个版本的调度器结构、模式管理方式与最新主线一致但代码量和重构复杂度比最新版少适合做源码学习基线。用 git 拉代码时最好固定到某个 release tag不要直接跟着 master 跑否则前后两天的代码可能差异很大网上查资料也容易对不上。在实际开发中我会建两个分支一个study分支只读不改用来做代码注释和笔记另一个dev分支专门放自己的二次开发。这样既能随时对比官方原版又不会把学习笔记混进可编译的工程里。1.2 顶层目录arbitrary 之外的四个关键区域ArduPilot 仓库拉下来后顶层目录乍看很多但真正核心的其实就四块ArduCopter/ArduPlane/Rover/Sub四种载具的主程序目录包含各自的主循环、飞行模式枚举、参数注册。如果你想做自定义飞控模式主要就是在这里动手脚。libraries真正的技术核心包括姿态估计、惯性导航、控制律、传感器驱动、EKF、PID 库、日志系统等。这个目录直接决定飞控的“内力”。Tools各种辅助脚本包括自动测试、参数文档生成、地面站配套工具。调试时经常用到Tools/autotest下的模拟器脚本。modules第三方库比如 MAVLink 协议库、实时操作系统等。非特殊情况不需要深入。还有个容易被忽略的是docs目录里面有开发者文档的 markdown 源文件。我经常边读源码边对照docs/dev/source/docs下的说明比自己瞎猜要快得多。1.3 从 main() 到调度器理解 APM 的“心跳”很多嵌入式开发者在读 APM 时卡住的第一关是找不到“主循环在哪”。其实 ArduCopter 的入口在ArduCopter/ArduCopter.cpp核心可以简化为三个动作APM_State等全局对象初始化在类构造函数里完成setup()里完成硬件初始化、传感器校准、参数加载loop()里以固定频率循环调用scheduler.run()。loop()默认跑在 400Hz调度器通过时间片轮转的方式把不同任务按优先级和周期塞进这个循环里跑。比如姿态控制在 400Hz导航控制在 50Hz地面站通信有的任务 10Hz有的 50Hz都由scheduler_tasks表来决定。这个设计非常关键APM 并非像裸机那样一个 while 循环从头跑到尾而是每个任务自己声明“我多久跑一次”“我最长能跑多久”调度器统一管理。实际新增一个自定义飞控模式时如果你的逻辑有高频运算也要考虑把它放进调度器而不是直接塞在run()里否则很容易超时导致看门狗复位。2. 核心控制链路从传感器到电机指令2.1 数据源惯性测量与姿态估计飞控的“感知层”主要由陀螺仪、加速度计、磁力计、气压计、GPS 等组成。在 APM 里传感器驱动都在libraries/AP_InertialSensor、AP_Compass、AP_GPS、AP_Baro等目录中。每个传感器都做成了独立库有统一的读接口这样上层算法不关心中间用的是 SPI 还是 I2C也不关心是哪款芯片。真正复杂的是姿态估计。目前 APM 主推的是 EKF3也就是扩展卡尔曼滤波器代码在libraries/AP_NavEKF3。EKF3 接收 IMU、GPS、气压计、空速、磁力计等数据输出位置、速度、姿态四元数等状态量。读 EKF 源码时不用一开始就去推公式先理解它的输入输出、状态向量长度以及几个核心的协方差更新流程对后续调参和排查问题很有帮助。如果只想快速上手可以先读AP_AHRS层。这一层对 EKF 做了二次封装给上层控制算法提供get_attitude()、get_velocity()这类统一接口。控制代码通常只跟 AHRS 打交道不会直接调 EKF。2.2 姿态控制角度环与角速度环的级联结构ArduPilot 的控制律走的是经典级联 PID 结构。外环是姿态角控制器输入是期望的姿态角roll/pitch/yaw输出的是期望角速度内环是角速度控制器输入期望角速度输出机体坐标系下的期望力矩再经混控器映射到各个电机。在源码中的具体位置姿态角控制AC_AttitudeControl主要在libraries/AC_AttitudeControl/AC_AttitudeControl.cpp角速度控制rate controller也在这个库里核心函数类似rate_controller_run()混控器多旋翼用的是AP_MotorsMatrix代码在libraries/AP_Motors/AP_MotorsMatrix.cpp负责把期望力矩换算成 4/6/8 个电机的油门百分比。理解这个链路非常重要。你在地面站上看到的所有 PID 参数——RateRollP、RatePitchP、StabRollP等——最后都是作用在这条链路上的。调参时如果只盯着地面站的波形而不知道每个参数对应源码里的哪个变量常常会出现“调了没反应”或“调了就炸机”的情况。我自己的经验是先学会在源码里定位 PID 库的参数读取点。比如AC_AttitudeControl的构造函数里_rate_bf_pid对象就绑定了RATE_RLL_P、RATE_RLL_I这些参数名。搞清楚参数和代码的绑定关系再去看地面站的调参界面思路会清晰很多。2.3 位置控制与导航L1 引导与 TECS多旋翼要悬停、要跑航点只靠姿态控制是不够的还得有外层的位置控制。APM 的位置控制采用“串级”思路期望位置经过位置环 PID 得到期望速度期望速度经过速度环 PID 得到期望加速度然后转换成期望姿态角交给姿态控制器执行。相关代码在AC_PosControl。而在航线跟踪上固定翼的横航迹引导用的是 L1 导航算法多旋翼在 Auto 模式下也有类似的前瞻点逻辑。L1 的核心思想是参考点不是目标航点本身而是当前航段上离飞机一定距离的前瞻点这样能天然产生平滑的横向加速度指令避免“追点”造成的振荡。固定翼的纵向控制则依赖 TECS总能量控制系统代码在libraries/AP_TECS。TECS 不直接控制油门和升降舵而是控制“比能量”和“能量分配”简单说就是同时兼顾速度和高度。初次读 TECS 时不用钻公式先把calculate_total_energy()和control_energy_balance()这类核心函数的流程走通再结合无人机实际飞行数据去理解会容易很多。3. 新增自定义飞控模式的完整实现路径3.1 模式管理机制枚举、派生与调度ArduPilot 把飞行模式做成了“类对象”而不是一堆 if-else。在ArduCopter里每个模式都对应一个继承自Mode的类比如ModeLoiter、ModeAltHold、ModeAuto。模式对象的创建集中在ArduCopter/mode.cpp的Copter::flight_modes数组里。切换模式的入口是Copter::set_mode()它会做退出当前模式、初始化新模式、更新控制模式标志等一系列操作。实际接一个自定义模式时一般情况下要做的事情就四件在FlightMode枚举里增加一个新的模式编号创建一个新的模式类文件比如mode_circle.cpp实现init()和run()把新类实例挂到flight_modes数组对应枚举位置在mode.h类的公有接口里面加一个入口并补上模式切换允许条件。这样做的好处非常明显模式之间天然隔离新逻辑不会影响旧模式而且能直接复用现有辅助函数如pos_control、att_control、gps状态判断、failsafe处理等。3.2 实操写一个“环绕兴趣点”自定义模式下面我以“自定义一个 Circle 环绕模式”为例演示核心流程。这个模式的需求飞控起飞后以某个 GPS 点为圆心以固定半径和角速度绕圈飞行同时保持高度不变。第一步在枚举中加编号。在ArduCopter/defines.h的FlightMode枚举中找一个空位比如Circle 24具体数值要和现有表不冲突同时更新ArduCopter/mode.h中的mode_reason相关定义。加完后把number_of_modes检查一下这些数字在 MAVLink 协议里是公开的随意改会导致地面站识别异常。第二步新建模式类。基础框架大致如下// mode_circle.h #pragma once #include mode.h class ModeCircle : public Mode { public: ModeCircle(void); bool init(void) override; void run(void) override; protected: float _radius; // 环绕半径 float _angular_vel; // 期望角速度 }; // mode_circle.cpp #include mode_circle.h ModeCircle::ModeCircle(void) : _radius(30.0f), _angular_vel(0.15f) {} bool ModeCircle::init(void) { if (!copter.position_ok()) { return false; } // 记录兴趣点为中心 return true; } void ModeCircle::run(void) { // 1. 计算当前位置相对圆心的方位角 // 2. 根据方位角 90度得到期望切向速度方向 // 3. 通过位置环计算期望加速度并转换为期望姿态角 // 4. 调用 attitude_control-input_euler_angle_roll_pitch_yaw() }这里面的核心是第三步。每次run()被调度时飞控都根据当前位置和圆心位置计算出一个“期望切向速度”然后丢给pos_control完成闭环。具体坐标转换可以直接复用AP::location()和Location::offset_bearing()这些现成方法不用自己重新写经纬度转米制的工具。第三步注册模式。在ArduCopter/mode.cpp的flight_modes数组里加上copter.mode_circle,同时在Copter::set_mode()的switch分支中把新模式的初始化调用补上。如果漏了这一步虽然编译能过但切换模式时会直接走进break表现就是“切了模式没反应”。第四步编译与验证。ArduPilot 用的是 WAF 构建系统。命令行通常是./waf configure --board Pixhawk1 ./waf copter编译通过后我一般不会立刻烧真机而是先跑 SITL 软件在环仿真。启动方式在Tools/autotest下sim_vehicle.py -v ArduCopter --console --mapSITL 起来后用地面站连接把模式切到自定义模式观察能否正常进入、日志输出有无异常。这一套验证完再考虑烧到硬件上。自己写的模式第一次上真机时一定要先在空旷、无人的场地接好电流计和电压监测并保留一键返航切换通道。3.3 模式切换的细节初始化和安全约束很多刚接触源码开发的人会忽略init()的作用。实际上run()是周期执行的但init()只在模式切入时执行一次。像环绕模式的“记录圆心”就应该放在init()里而不是每次run()都重新取一次。再看安全约束。比如你的自定义模式要求必须要有 GPS那么init()里就要检查copter.position_ok()。如果 GPS 定位质量不够直接返回false调度器会拒绝切入该模式或自动切回悬停。这种防护逻辑对于自定义模式尤其重要——官方模式已经处理了各种故障场景但你自己的模式大概率没考虑那么全所以一定要自己补上。还有一个常被忽略的是“退出清理”。如果模式里用了非默认的坐标系或控制方式退出时最好恢复默认状态。否则下一次切入别的模式时可能会沿用你上次遗留的中间变量造成很难排查的偶发问题。4. 源码编译、仿真验证与问题排查实录4.1 编译环境与常见报错处理ArduPilot 官方推荐在 Linux 或 macOS 下开发。./waf configure时需要指定板卡比如--board Pixhawk1。这里有个小坑如果你在 64 位系统上交叉编译 32 位 ARM 固件部分旧版本需要先安装 32 位兼容库。我当时第一次编译就卡在这个问题上报错信息很抽象指向某个工具链文件后来才发现是系统缺gcc-multilib。还有一类常见报错是工具链版本不匹配。ArduPilot 对 ARM 编译器版本比较敏感太新或太旧的 GCC 都可能触发一些奇怪的编译错误。建议直接按官方文档装g-arm-linux-gnueabihf或使用Tools/environment_install/install-prereqs-ubuntu.sh脚本统一安装依赖。如果编译时发现某个源文件报错但代码本身又没问题优先考虑工具链版本。烧录时ArduPilot 生成的是.px4或.apj固件文件。我的常用烧录方式是通过 Mission Planner 地面站的“加载自定义固件”功能选本地文件后地面站会擦除旧固件、写入新固件。备份参数这个动作千万不能省每次刷固件前后都做一次完整的参数导出遇到问题能快速回滚。4.2 SITL 仿真不上真机也能试飞新模式SITL 是 ArduPilot 提供的软件在环仿真工具它在 PC 上编译运行飞控代码再用模拟的传感器数据喂给飞控。这样自定义模式从编译到验证的迭代周期大大缩短基本几分钟就能跑完一轮修改和测试。sim_vehicle.py是标配入口。启动时可以加--map打开 2D 地图加--console打开 MAVProxy 调试台。MAVProxy 支持mode circle、param set等指令比地面站更适合快速验证。我的习惯是先在 SITL 里跑一个定点任务确认新模式的init()和run()都被正常调用然后再慢慢调 PID 参数。SITL 的另一个价值是方便调试崩溃问题。真机上固件崩溃基本只能看日志猜原因而 SITL 里可以直接用 gdb attach 到仿真进程设置断点。我在自定义模式早期开发时很多“电机突然没输出”的问题都是靠 gdb 在AP_Motors输出函数处打断点定位到的。4.3 真机调试中的经典问题与速查表真机调试是完全不同的体验我在这里踩过的坑比写代码多得多。下面这张表是我做自定义模式开发时整理的高频问题对照表现象可能原因排查方向模式切不进去init()返回 false检查position_ok()、GPS 状态、起飞检测标志进入模式后飞机猛偏转期望姿态角初始化异常检查run()中是否用了未初始化的变量或 YAW 控制是否环回电机输出不平滑混控器饱和或输出限幅未处理检查AP_Motors输出限幅、是否触发了 failsafe日志里出现EKF3 IMU0 STOPPEDIMU 数据异常检查机体震动、传感器安装、陀螺仪噪声跑一会儿自动切回悬停RC 失联或 GPS 定位丢失查看failsafe触发日志逐条确认故障源调参后性能不升反降PID 参数方向搞反用正负小步长梯度法验证响应方向别梭哈式调参表格里列的问题几乎每一个都对应着源码里的一个具体函数或标志位。碰到问题时我第一个动作不是改参数而是在源码里加上关键标志位的日志输出确认飞控当前到底处于什么状态。这个习惯能帮你过滤掉大量“伪故障”。4.4 源码调试的个人习惯日志大于一切ArduPilot 本身有非常强大的日志系统默认会记录IMU、PID、RCIN、RCOUT、MODE、EKF等数据。调自定义模式时我强烈建议开启LOG_DISARMED这样即使飞机没有解锁也能在地面站上观察传感器数据和控制器状态。还有一个参数LOG_REPLAY可以配合Tools/replay工具把飞行日志重新灌入 EKF 跑一遍用来定向分析某个控制环节。日志文件一般是.bin格式Mission Planner 自带LogAnalyzer能快速给出一堆飞行健康度提示。但要注意LogAnalyzer更多是通用性检查自定义模式的特殊问题还是要自己画曲线。我经常把RCOUT和ATTITUDE放在同一张时序图里对比看电机输出是否与姿态期望同步变化。数据对齐后大多数失控原因都能肉眼看出来。5. 写在最后APM 源码的进阶之路我做 APM 二次开发这几年最大的体会是这是一套“读代码比看文档更靠谱”的项目。官方文档能覆盖基础使用但真正要自定义模式、改控制律、修 bug最后全靠源码本身。每一次深入读源码都会发现很多地面站和文档里根本没写透的细节比如set_mode()里的模式切换条件、调度器的插队机制、参数对象的注册方式这些才是项目的灵魂。如果你现在准备开始读这套源码我的建议是别试图一次搞懂所有模块。先把ArduCopter/ArduCopter.cpp的setup()和loop()看完再跟着Mode类的几个成熟模式比如AltHold和Loiter走一遍控制链路之后再逐步扩展。刚开始两个月觉得进度慢很正常一旦跨过“入口和调度”这个坎后面的路会越来越顺。最后分享一个小经验在 clone 仓库后先把git log翻一遍看看每个重要模块的 commit 历史。很多设计决策的来龙去脉源码注释里不会写但提交说明里往往会说得很清楚。理解“为什么要这么写”有时比理解“这行代码在干什么”更重要。祝大家都少走弯路早日写出属于自己的第一个自定义飞控模式。本文还有配套的精品资源点击获取
返回列表