简介:面向在Ubuntu下开展ArduPilot飞控开发与固件编译的工程师,这份于2024年7月5日更新的源码包省去了联网克隆仓库时的卡顿与失败风险,解压后即可在Ubuntu系统中配置编译环境,直接生成APM或PX4两类固件。包内目录完整保留原始仓库层级,包含AP_AHRS、AP_InertialSensor、AC_AttitudeControl、AP_RangeFinder等核心飞行控制与传感器模块,也提供AP_Common、AP_Math、AP_SerialManager等基础库,支持Copter、Plane、Rover等多机型适配,并兼容Pixhawk系列硬件及LTM遥测、KDECAN、SmartRTL、Soaring等外围设备,适合二次开发、参数调试、传感器适配和ESC遥测集成。资源文件共3个,以html、inscode、gitignore等类型为主,其中html便于在线预览说明,inscode可作为代码片段配置参考,gitignore用于管理版本控制范围;压缩包大小为3KB,体量极小、便于快速下载核对版本。目前已有16人学习下载,推荐给需要稳定获取最新源码、快速搭建Ubuntu编译环境并进行后续开发的飞控开发者;从源码获取到固件编译全流程覆盖,目录结构清晰,方便按需提取对应模块。 拿到这份2024年7月5日快照的ArduPilot源码包时,我的第一反应是松了口气。过去几年在Ubuntu下编译ArduPilot,最让人上火的从来不是代码本身,而是源码拉下来之后没完没了的子模块补齐、依赖冲突、工具链版本对不上。你照着ArduPilot官网的Building教程一步步来,系统装的是Ubuntu 22.04,git、python、gcc全都齐了,结果./waf configure一跑就报错,这种体验我太熟悉了。
这篇文章不打算重复官方文档,只围绕这份“可直接编译”的源码包,从解压、环境准备、编译配置到固件产出和烧录验证,讲清楚完整流程和那些真正值得注意的坑。适合手头有Ubuntu机器、想把ArduPilot源码跑起来做二次开发或者自己编译固件的朋友,不管你是刚接触飞控源码的初学者,还是被编译问题折磨过的老油条,里面都有能直接抄作业的内容。
1. 为什么需要“可直接编译”的ArduPilot源码包
1.1 ArduPilot的版本机制与快照源码的价值
ArduPilot的开发和绝大多数开源飞控项目一样,采用Git分布式工作流。主分支(master)上的代码是社区实时提交的结果,每天都有大量功能合并、重构和修复。这带来两个问题:第一,master分支的代码时刻在变,今天能编译通过,明天可能因为某人提交了一个未完成的接口重构就编不过;第二,官方文档推荐的依赖版本和编译流程会跟随master更新,你看到的教程可能已经过时了。
正式版本通常以四段式版本号发布,比如Copter-4.5.7,这些稳定版本在Release分支上维护,比如release/4.5分支。稳定分支的特点是有明确的维护策略,只接受bug修复和安全补丁,不引入大规模功能变动,因此编译环境相对稳定,出问题的概率小得多。但即便是在稳定分支上,直接clone的仓库仍然面临一个隐患:ArduPilot的源码里有一个重量级目录modules,里面存放MAVLink协议库、gtest测试框架等子模块,这些子模块通过git submodule机制挂在主仓库下面。如果你只是git clone主仓库而没有同步子模块,编译到一半必然报头文件找不到的错误。
这份2024.07.05的源码包,本质上就是基于当时的稳定维护分支打的一个快照,并且已经完成了子模块的初始化和同步。它把“版本一致性”和“依赖完整性”两个最容易出问题的变量提前处理掉了,所以标题才敢叫“可直接编译”。这一点对新手尤其重要,因为子模块问题报错时往往让人一头雾水,你根本不知道mavlink.h这种头文件为什么就是找不到。
1.2 这份源码包解决了哪些“不能直接编译”的问题
我把这类源码包的价值拆开看,主要解决了三个层面的问题。
第一,子模块完整性。不带--recurse-submodules参数clone下来的ArduPilot仓库,modules/MAVLink和modules/gtest都是空目录。编译时只要代码里引用了MAVLink协议相关的头文件,编译就会立刻中断。这个源码包拿到手,子模块已经就位,省掉了最麻烦的一步。
第二,依赖版本锁定。ArduPilot的编译依赖一套Python工具链和ARM交叉编译器,这些组件都有版本兼容性的讲究。比如Python接口模块empy,某些较新版本会和ArduPilot的waf构建脚本不兼容,导致编译时出现诡异的empy错误。直接编译的源码包通常对应一个已知可用的工具链组合,你在使用说明里看到的推荐依赖版本,就是这个快照实际测试过的版本组合。
第三,源码完整性与可追溯性。快照包不是一个随随便便压缩的文件夹,它有明确的时间节点,对应Git仓库里特定的commit。拿到后可以通过git log和git describe确认版本,这给后续排查问题提供了重要锚点:遇到问题时可以明确说“我编译的是2024年7月5日这个版本”,而不是笼统地说“我编的是最新版”,这对我排查问题的帮助非常大。
1.3 直接clone master与使用快照包的差异实测
我曾经试过直接clone master来编译,那是一个让人印象深刻的经历。第一次编译时一切正常,一周后重新拉取代码再编,编译过程中突然冒出一堆variant相关的错误,查了GitHub issues才发现是有人在重构参数定义框架,中间状态没有保持可编译。这种“半成品代码”在master上非常常见。
快照包就不一样,它选的是稳定分支上经过验证的节点,代码是自洽的,不会出现“模块A已经切换到新接口,模块B还在用旧接口”的错位。所以对于绝大多数场景——包括学习、教学、二次开发,甚至小批量生产——快照包比跟踪master更为可靠。这也是我为什么拿到这份源码包后愿意花时间把完整流程整理出来的原因,它值得推荐。
2. Ubuntu编译环境的完整搭建与避坑
2.1 系统版本选择与硬件底线
编译ArduPilot对Ubuntu版本不算挑剔,但我实测下来,Ubuntu 22.04 LTS是最省心的选择。20.04也能用,只是Python版本偏旧,个别依赖需要手动处理;24.04虽然新,但官方工具链脚本对它的适配在2024年仍然不算完善,可能会出现一些Python包名称或者依赖版本的小问题。如果你不是有特殊原因,就用22.04,这点我踩过坑,不会骗你。
硬件方面,一个容易被人忽视的点是内存。编译ArduPilot的C++代码使用GCC进行交叉编译,虽然单个编译单元的复杂度不算夸张,但并行编译时内存消耗会直线上升。我的经验是8GB内存是底线,16GB是舒适区。如果你用的是虚拟机(比如VMware里跑Ubuntu),分配内存时千万不要只给4GB,否则编译到一半会看到g++: internal compiler error: Killed这种让人绝望的提示,那是系统OOM把编译器进程杀掉的信号。磁盘空间至少预留20GB,源码加编译产物加工具链,空间消耗比想象中快得多。
2.2 依赖安装的正确姿势:别自己一个个装
很多人习惯从网上找一堆apt install命令,然后照着敲一遍。这个做法在ArduPilot上行不通,或者说效率低下。因为ArduPilot的依赖不仅仅包括系统层面的编译工具,还包括特定版本的Python包和ARM工具链,这些需要精确定位。
ArduPilot官方在源码的Tools/environment_install/目录下提供了环境安装脚本install-prereqs-ubuntu.sh,这才是正确的打开方式。我拿到这份源码包后的第一步,先给脚本加上执行权限再运行:
cd ardupilot Tools/environment_install/install-prereqs-ubuntu.sh -y这个脚本会做几件事:安装build-essential、git、libtool、autoconf、automake、cmake等基础编译工具,安装gcc-arm-none-eabi交叉编译器,通过pip安装ArduPilot编译需要的Python模块,包括future、lxml、pexpect、pyserial、empy等。脚本执行完成后,还会把ArduPilot的工具目录写入~/.profile,保证arm-none-eabi-g++等命令可以在后续终端中直接使用。
这里有一个非常常见的坑:脚本执行完后,当前终端环境变量不会自动更新。你需要执行source ~/.profile或者干脆关掉终端重新打开,不然./waf configure还是会报找不到arm-none-eabi-g++。这个问题让很多人在环境配置这一步卡了半天,其实只是忘了刷新环境变量。
2.3 ARM工具链版本:一个隐形的地雷
ArduPilot对ARM工具链的版本有明确要求。官方长期使用ARM官方发布的GCC工具链,版本相对固定。Ubuntu 22.04的软件源里虽然也有gcc-arm-none-eabi包,但版本可能和ArduPilot预期的不完全一致,这会导致一个非常隐蔽的问题:编译时不会直接失败,但生成的固件在飞控板上运行时可能出现莫名其妙的行为异常。
install-prereqs-ubuntu.sh脚本在安装ARM工具链时,会从ARM官方获取指定版本并安装到/usr/bin或者ArduPilot工具目录。脚本设计上已经做了版本适配,所以只要用官方脚本安装,版本就不会出问题。我见过有人为了“更新”擅自升级了gcc-arm-none-eabi,结果编译过程中出现unrecognized command-line option之类的报错,最后不得不降级回来。这个环境的冗余度没有想象中那么大,不要自作聪明去升级组件。
另外提醒一句,如果你在编译时遇到/usr/bin/arm-none-eabi-g++: No such file or directory这类报错,大概率是脚本没有安装成功,或者安装路径不在PATH中。先用which arm-none-eabi-g++确认工具链是否可用,再检查~/.profile里的路径配置,这是最快定位问题的方式。
3. 源码包解压、目录结构与版本校验
3.1 解压后先看这几个关键目录
拿到源码包后,我习惯先看一眼目录结构再动手,而不是急着编译。ArduPilot的源码目录乍一看有点乱,但核心区域其实很清晰:
ArduCopter/:多旋翼飞控的主程序目录,进入ArduCopter后直接./waf build编译产物就是arducopter固件。ArduPlane/:固定翼飞控主程序,编译产物是arduplane。Rover/:地面车辆载具的实现,对应ardurover。Sub/:水下机器人的飞控实现。libraries/:所有公共代码库,包括传感器驱动、姿态解算、导航算法、控制律等,这里是二次开发的主要阵地。Tools/:各种辅助工具和脚本,包括环境安装、日志解析、参数转换等。modules/:子模块目录,MAVLink子模块在这下面,编译时不可缺少。build/:编译输出的根目录,第一次跑完./waf build后,各个板卡的固件会按build/<board>/bin/的路径生成。
如果是初次接触ArduPilot源码,我建议花点时间在libraries/AP_Math和libraries/AP_AHRS目录下翻一翻,这两个是理解飞控姿态解算核心逻辑的入口。源码包的解压路径尽量不要包含中文或空格,因为waf对路径处理时可能会因为特殊字符出问题,这是一个被很多人忽略的小细节。
3.2 确认版本与子模块状态的正确方法
源码包标题写的是2024.07.05,但源码里面到底是什么版本,需要用Git自己确认。在源码根目录执行:
git log -1 --format="%H %ci" git describe --tags git submodule status第一条命令输出当前HEAD对应的完整commit哈希和提交日期,第二条输出距离最近的标签名称,第三条列出所有子模块的当前commit状态。正常情况下,三个输出应该都是正常的,不会出现-dirty标记。
如果git submodule status中某一项以-开头,说明这个子模块没有被正确初始化;如果以+开头,说明子模块的commit和主仓库记录的版本不一致。对于“可直接编译”的源码包来说,正常情况下所有子模块的状态都是开头不带符号的,这种情况下编译环境才算真正就绪。我建议编译前花30秒做一下这个检查,能省掉后面排查问题的大量时间。
3.3 编译前的Python环境自检
ArduPilot的waf构建系统是Python写的,因此Python环境必须正常。系统层面Ubuntu 22.04自带Python 3.10,满足要求。需要注意的是一些Python包的兼容性。
可以先在源码根目录执行一下python3 -c "import empy, future, lxml, pexpect, pyserial",如果某个模块报错,就用pip install补装。关键点是empy的版本,ArduPilot对empy的版本要求比较特殊,某些新版本在生成编译配置时会有兼容性问题。官方脚本安装的是经过测试的版本,如果你之前手动装过别的版本,出现Error: empy is not the correct version之类的提示,不要犹豫,直接用pip uninstall empy卸掉,再重新安装脚本指定的版本即可。
这段自检流程虽然看起来多花几分钟,但能避免后面编译跑到一半才被Python环境问题打断。编译失败不可怕,可怕的是编译到80%才失败,前面的时间全浪费了。
4. 编译全流程:从configure到固件产出
4.1 为什么ArduPilot用waf而不是CMake
第一次接触ArduPilot的人都会问:为什么这个项目不用CMake?这是一个合理的问题。CMake是当前C++项目的主流构建工具,生态成熟,资料也多。但ArduPilot选择waf有它的历史原因和技术考量:waf是一个基于Python的构建工具,构建脚本可以充分利用Python的灵活性,而ArduPilot的编译系统里有大量复杂的参数配置、板卡适配和代码生成逻辑,用Python写这套流程比CMake的DSL更顺手。
另一个现实原因是,ArduPilot的构建系统高度定制化,从硬件抽象层的选择到自动代码生成,再到固件打包脚本,形成了一个完整的工具链。迁移到CMake意味着重写整个构建系统,成本极高,风险极大。所以在可预见的未来,ArduPilot仍然会使用waf。理解了这一点,你就知道为什么编译ArduPilot不能直接用cmake && make,而是必须用./waf命令。
4.2 选择board并执行configure
waf构建流程的第一步是配置编译目标。ArduPilot把不同飞控硬件板称为board,每个板卡对应一个配置文件。执行配置的命令格式是:
./waf configure --board <board名>常见的board名包括:
| 板卡硬件 | board参数 | 适用场景 |
|---|---|---|
| Pixhawk 1 | Pixhawk1 | 经典飞控板,资料最多 |
| Pixhawk 4 | Pixhawk4 | 新一代Pixhawk系列,性能更强 |
| Cube Black | CubeBlack | 工业级飞控,可靠性高 |
| Cube Orange | CubeOrange | 高端飞控,多冗余设计 |
| SITL | sitl | 纯软件仿真,不涉及硬件 |
如果你是第一次编译,又没有特定硬件在手,我强烈建议先编译SITL目标。SITL是全软件仿真环境,不需要任何飞控硬件,编译成本低,而且能跑通完整的飞控逻辑,适合理解整个编译和运行流程。命令是:
./waf configure --board sitl ./waf build如果目标是真实飞控硬件,比如Pixhawk 1,就是:
./waf configure --board Pixhawk1 ./waf buildconfigure阶段waf会检查工具链、Python依赖、编译器版本,并生成一套以板卡名命名的构建配置。看到Configuration is complete之类的输出,说明配置成功;如果中途报错,大多和工具链缺失或者Python依赖有关,先回到第2、3章检查环境。
4.3 编译输出与二进制文件的正确理解
执行./waf build后,waf会根据configure阶段选择的板卡,将源码编译为对应架构的二进制文件。如果configure时选了Pixhawk1,编译产物会出现在build/Pixhawk1/bin/目录下。
第一次编译需要的时间取决于机器性能,我实测在8核16线程的机器上,Copter固件大概需要10到15分钟;如果机器比较老或者内存不足,时间可能翻倍。编译过程中屏幕上会滚动大量编译信息,这是正常的。真正需要关注的是最后是否出现build complete类似的提示,以及bin目录下是否生成了固件文件。
关于固件文件,ArduPilot会生成多种格式:
.apj:带bootloader的固件包,地面站直接加载这个文件烧录即可。.px4:Pixhawk系列原生固件格式,用于地面站烧录。.bin:纯二进制格式,通常用于bootloader配合烧录。- 不带扩展名的文件:例如
arducopter,是ELF格式的原始可执行文件,主要用于调试。
如果编译SITL目标,build/sitl/bin/下生成的arducopter是一个Linux下的可执行文件,直接运行就能启动一个软件在环仿真飞控。
5. 编译高频报错的完整排查链路
5.1 找不到ARM交叉编译器
报错特征:./waf configure时提示找不到arm-none-eabi-g++,或者编译过程中提示/bin/sh: 1: arm-none-eabi-g++: not found。
排查链路:先在终端执行which arm-none-eabi-g++,如果没有任何输出,说明工具链没有安装或安装路径不在PATH中。确认是否执行过Tools/environment_install/install-prereqs-ubuntu.sh,如果执行过但没有生效,检查一下~/.profile里是否添加了ArduPilot工具目录。加入后执行source ~/.profile再重试。如果which命令能找到工具链,但还是报编译错误,可能是工具链权限问题,执行ls -l /usr/bin/arm-none-eabi-g++看是否有可执行权限。这三个步骤能覆盖大多数工具链缺失的情况。
5.2 Python模块empy版本冲突
报错特征:configure过程中提示Python module empy is not installed或者empy version is not supported。
排查链路:ArduPilot对empy的版本有严格要求,在2024年的环境中,官方脚本安装的是empy==3.3.4。如果你之前手动装过其他版本,比如empy==4.x,就会触发这个报错。解决办法是先卸载:pip uninstall empy,再重新安装指定版本:pip install empy==3.3.4。这里有一个细节:如果你使用了虚拟环境,一定要激活虚拟环境后操作,否则卸载的是系统Python的包,而waf用的是虚拟环境的包,问题不会解决。
5.3 编译中段进程被杀死
报错特征:编译跑到一半,屏幕突然出现g++: internal compiler error: Killed,然后编译中断。
排查链路:这个报错几乎可以确定是内存不足导致的操作系统OOM Kill。用free -h查看内存使用情况,如果剩余内存很少,说明并行编译的进程数超出内存承载能力。解决办法有两个方向:一是增加物理内存或虚拟机分配的内存;二是降低并行编译的进程数,waf通过-j参数控制:
./waf build -j2在内存有限的机器上,-j2比默认并行度慢不了多少,但编译稳定得多。这是我在8GB内存笔记本上实测出来的经验,与其反复被Killed,不如一开始就限制并行度。
5.4 子模块缺失导致头文件找不到
报错特征:编译时提示fatal error: modules/MAVLink/mavlink.h: No such file or directory。
排查链路:这个报错在直接clone官方仓库时很常见,但在“可直接编译”的源码包里出现频率较低,如果出现了,说明子模块状态异常。执行git submodule status检查,看到任何子模块状态前有-前缀,说明该子模块没有初始化。运行:
git submodule update --init --recursive同步完成后重新编译。这个错误本身很好解决,但定位时需要一点耐心,因为头文件路径在报错信息中可能只显示一部分,如果没头绪的人可能会错怪代码本身,实际上完全是构建环境的问题。
6. 固件验证、烧录与后续玩法
6.1 编译产物的校验与确认
编译完成后,不要急着烧录,先做几步验证。用ls -lh build/Pixhawk1/bin/查看固件文件大小,正常Copter固件应该在1MB到2MB之间,如果只有几十KB,说明编译过程可能出了问题。再用sha256sum计算固件的哈希值,记录这个值,烧录后可以通过比对校验固件是否完整上传。
如果你有飞控板在手,建议先连上地面站,读取一下当前板上固件的版本和参数,做好备份,再烧录新固件。这样如果新固件有问题,可以随时刷回旧版本。
6.2 USB烧录实操与常用工具
烧录ArduPilot固件最常用的地面站是Mission Planner和QGroundControl。Mission Planner在Windows下支持最好,功能和固件烧录流程最完善;QGroundControl则跨平台做得更好,在Linux下运行稳定。
在Ubuntu下用QGroundControl烧录时,一个容易踩的坑是USB权限问题。飞控板通过USB连接到电脑,如果系统没有给当前用户分配对应串口设备的权限,QGroundControl会显示设备连接失败。解决办法是把用户加入dialout组:
sudo usermod -a -G dialout $USER执行后注销并重新登录,再插上飞控板,这时ls /dev/ttyACM0或者/dev/ttyUSB0能看到设备节点,QGroundControl也能正常识别飞控。
如果不喜欢图形界面,ArduPilot还提供了命令行烧录脚本Tools/scripts/upgrade_firmware.py,在编译出.apj固件之后,可以执行:
python3 Tools/scripts/upgrade_firmware.py --port /dev/ttyACM0 --baudrate 115200 build/Pixhawk1/bin/arducopter.apj这种方式在自动化测试和生产场景下更实用。
6.3 后续扩展:SITL仿真与二次开发方向
编译成功只是起点,真正的玩法在后面。如果你编译的是SITL目标,可以直接在命令行运行./build/sitl/bin/arducopter -I 0启动一个软件在环仿真飞控,然后用MAVProxy连接:
mavproxy.py --master tcp:127.0.0.1:5760 --out 127.0.0.1:14550或者用QGroundControl直接连接UDP端口。SITL环境下,飞控完全模拟真实硬件的行为,你可以在完全不碰实体飞机的情况下,测试自驾逻辑、调参策略、航线规划等,这是学习飞控原理最高效的方式。
再往后,ArduPilot和ROS2生态的融合是当前社区的一个热点方向,通过SITL加Gazebo等仿真环境,可以在虚拟世界里完成无人机全流程的仿真测试,代码验证成熟后再迁移到真实硬件,这能大幅降低试错成本。如果你有心继续深入,这个方向值得好好研究。
从我个人的经验来看,ArduPilot的编译体系虽然初看起来有些另类,但它一旦跑通,整个流程异常稳定和规整。你只要记住几个关键点:环境脚本优先、板卡选择明确、并行度量力而行、子模块常检查,后面就很少会再被编译问题卡住。我还喜欢把常用的编译命令写成一个shell别名,比如alias copter-build='./waf configure --board CubeBlack && ./waf build -j4',一条命令搞定配置和编译,省心很多。希望你也能顺利编出属于自己的固件,跑通这条完整的工具链。
本文还有配套的精品资源,点击获取