1. 项目概述与编译目标拆解
1.1 标题背后的核心需求
提到Ardupilot,玩过飞控、做过无人机或者折腾过地面机器人的朋友应该都不陌生。这是一套开源的自驾仪软件栈,支持多旋翼、固定翼、直升机、无人车甚至是无人船。项目名叫“Ardupilot part.1 项目编译和构建”,摆明了是系列文章的第一篇——从零开始把Ardupilot的固件编译出来,把构建环境跑通。
先说说标题这个“part.1”的定位。Ardupilot这个项目编译说难不难,说简单也不简单。很多新手第一次碰都会卡在环境配置上:依赖装了一堆,版本对不上,编译到一半报错,或者固件编出来了但烧进去飞控不工作。整套流程涉及子模块拉取、工具链切换、GCC交叉编译、参数配置、Bootloader处理等一堆东西。如果没有人带着走一遍,确实容易浪费大量时间在环境折腾上。
我个人的建议是,如果你之前在单片机或者Linux环境下做过交叉编译,那这套流程的上手成本会低很多。Ardupilot的构建系统本质上是基于Python脚本加Makefile的一套封装,底层调用的是arm-none-eabi系列的交叉编译工具链。目标板子不同,编译开关和参数差异也很大。
这篇文章适合三类人:
- 第一次接触Ardupilot源码,想自己编译出一份飞控固件的新手;
- 需要定制固件功能、修改默认参数、增加自定义驱动,必须修改源码并重新构建的老手;
- 以及那些只是想把Ardupilot构建流程彻底搞清楚、知其然也想知其所以然的爱好者。
整篇内容我都会以实际命令和真实操作作为基础,尽量把每一步为什么要这么做讲清楚,而不是只给一串配置就完事。读完你应该能做到:在自己电脑上独立完成一次Ardupilot固件编译,并且理解整个构建链路里每个环节发生了什么。
1.2 开发环境与工具链总览
Ardupilot的构建环境,常见的是Ubuntu或者Debian系的Linux发行版。官方脚本也是针对这类系统写的。如果你用的是macOS,理论上也能跑,但在编译串口工具和某些依赖时容易遇到小坑。Windows平台不是官方推荐环境,虽然能通过WSL或者虚拟机折腾,但对新手来说门槛更高。
从工具链层面来看,Ardupilot的编译分两大方向:
- 针对飞控板上MCU的固件编译,也就是Pixhawk系列或者其他基于STM32的硬件,使用arm-none-eabi交叉编译器,输出的是运行在单片机上的.bin或.hex固件;
- 针对PC环境的模拟器编译,比如SITL(Software In The Loop),直接使用本机的GCC编译成Linux可执行程序。
这篇文章我主要讲第一个方向,也就是实际飞控固件的编译,因为这才是绝大多数人玩无人机最关心的东西。SITL模拟器编译会在后续的part中单独介绍,混在一起讲容易让新手搞混。
整个编译流程涉及的组件大致有这么几块:
- Ardupilot主仓库源码;
- 子模块(Submodules),包含大量第三方库和驱动代码;
- GCC交叉编译工具链(arm-none-eabi);
- Python构建脚本和若干依赖包;
- 对应硬件的Bootloader(有时需要单独烧写)。
梳理清楚这个结构,后面操作起来就有数了。很多人在编译阶段报错,根源不是命令不对,而是子模块没有拉全,或者工具链版本与固件要求不匹配。
2. 编译环境的完整准备
2.1 系统依赖与自动化安装脚本
先强调一点:不要手动去apt安装一堆包,除非你明确知道自己在做什么。Ardupilot官方提供了一个自动化配置脚本,位置在源码目录下的Tools/environment_install/install-prereqs-ubuntu.sh。这个脚本会把你需要的绝大多数系统依赖装好。
脚本执行方式如下,建议在Ubuntu 22.04或24.04上操作:
cd ~ git clone --recurse-submodules https://github.com/ArduPilot/ardupilot.git cd ardupilot git submodule update --init --recursive第一行命令里的--recurse-submodules参数是关键。Ardupilot仓库和其他很多开源项目一样,把第三方依赖拆成了子模块。如果你只用了普通的git clone,后面编译时会发现一堆头文件和库找不到,报错信息五花八门。
说句实话,我第一次编译时就是栽在这个坑上。只记得执行了一个git clone,结果编译到自带库的环节疯狂报错,后来检查才发现是子模块没有拉取完整。所以这里我强烈建议:
克隆完仓库后,先手动执行一次
git submodule update --init --recursive,确保所有子模块都处于已拉取状态。哪怕你已经用了--recurse-submodules,多执行一次也无害,能避免很多后续麻烦。
接下来运行环境依赖脚本:
cd Tools/environment_install/ sudo ./install-prereqs-ubuntu.sh -y这个脚本会安装的东西包括:
- Python3以及相关pip包;
- 编译工具链的自动下载器(用于获取arm-none-eabi-gcc);
- cmake、ninja、gcc等构建基础工具;
- 一些USB串口相关的驱动包,比如udev规则。
安装过程视网络状况可能需要一段时间,最好选择网络环境稳定的时段操作。如果中途出现某个包下载失败,重跑一次脚本即可,脚本设计上是可以重复执行的。
2.2 交叉编译工具链的选择与安装
Ardupilot本身并不会直接使用Ubuntu软件源里的arm-none-eabi-gcc版本。官方推荐的是从Arm官方或者工具链管理器中获取特定版本的GCC。因为固件编译对编译器版本有隐性要求,版本太新或太旧,某些内核代码的编译结果就可能出现不可预期的问题。
最常见的方法是使用Ardupilot提供的工具链安装脚本。在安装依赖的步骤中,它会自动下载一个特定版本的arm-none-eabi-gcc到本地目录,具体位置一般是~/bin或者源码目录的某个工具目录下。安装完成后,用下面的命令验证:
arm-none-eabi-gcc --version如果系统提示找不到命令,多半是安装过程中PATH没有配置好。可以参考官方文档,把工具链路径手动加入~/.bashrc或~/.profile。
这就是为什么安装之后要打开一个新终端窗口,或者执行一遍source ~/.bashrc来刷新环境变量。实践中有很多人在这一步栽跟头:工具链明明装了,却在编译时提示找不到gcc。检查一下PATH配置,多半能解决。
另外一个容易出现问题的地方:不同飞控硬件对编译器版本要求不完全一样。某些老旧的Pixhawk硬件需要老版本GCC才能正常编译,而新出的硬件则对GCC版本没有太多挑剔。编译报错时,先别急着怀疑代码问题,先确认一下你用的工具链版本是否在项目要求的范围内。
2.3 Python构建环境与依赖库
Ardupilot的编译系统强依赖Python3。仓库里很多构建逻辑都是Python脚本驱动的。官方环境安装脚本其实已经帮你装好了绝大多数Python包,但如果之后你需要在本地进行更多自动化构建或配置,建议再看看有没有缺失的依赖。
执行下面命令可以检查基础依赖是否正常:
python3 -c "import pexpect; print('pexpect ok')" pip3 list | grep pymavlink整个构建过程中,比较关键的一个Python组件是pymavlink。它是Ardupilot与地面站、调试工具交互的底层库,部分构建任务会用到。缺失时,某些模块的编译步骤会中途失败,提示信息中一般会直接出现pymavlink字样。
个人经验:在环境准备阶段多花几分钟检查这些基础组件,比编译到一半再处理缺失要省心得多。
3. 固件编译的原理与构建配置
3.1 构建系统架构——waf和CMake共存
Ardupilot的构建系统经历了多次演化。早期版本主要使用waf作为构建系统,近些年官方逐渐迁移到CMake。但当前主流的稳定版本中,waf仍然占有一席之地,特别是自定义固件编译方面,很多老玩家习惯用waf命令。
在我常用的稳定版本中,Ardupilot的构建入口依然保留了waf模式的便捷性。典型的固件编译命令是这样的:
cd ardupilot ./waf configure --board Pixhawk1 ./waf copter如果你倾向使用最新的CMake方式,也可以:
cd ardupilot cmake -B build/Pixhawk1 -DCMAKE_BUILD_TYPE=Release -DBOARD=Pixhawk1 cmake --build build/Pixhawk1 -j4两种方式底层产物是一致的,区别在于构建缓存、增量编译效率以及后续扩展的灵活程度。对新手来说,我更推荐先用waf模式跑通流程,因为相关文档、报错解决方案更成熟,网络上能搜到的经验也更多。
3.2 Board选项与飞控硬件对应关系
Ardupilot中,每一种飞控硬件都对应一个Board名称。比如Pixhawk1对应的是Pixhawk1,Pixhawk4对应的是Pixhawk4,等等。编译前必须确认好你的飞控板子具体是哪一个型号。
这里有个常见误区:很多人手里拿着一个蓝色外壳的Pixhawk,就随便选一个Board去编译。实际上Pixhawk1、Pixhawk2(Cube)、Pixhawk4的引脚定义、传感器型号、Flash大小都不完全相同,固件不能通用。选错Board即便编译成功,烧录后也可能出现外设不工作、传感器读取异常的问题。
如何确认板型?最稳妥的方式是查看购买时的产品说明,或者看电路板上的丝印标注。如果是二手板子,也可以通过连接地面站读取固件版本信息,反推出板型范围。最不建议的做法是蒙一个。
下面列举几个常见板型的配置命令:
./waf configure --board Pixhawk1 ./waf configure --board CubeBlack ./waf configure --board Pixhawk4构建配置结果会生成在build目录下,后续如果切换板型编译另一个固件,需要先clean或者分开配置不同的构建目录,否则容易出现配置残留冲突。
3.3 固件类型选择——ArduCopter还是其他
飞控能跑的固件不止一种。ArduCopter对应多旋翼、ArduPlane对应固定翼、ArduRover对应无人车、AntennaTracker对应天线跟踪器。同一个Board可以编译出不同机型固件,刷入后飞控相当于换了个“角色”。
对我们这次的主题而言,如果只是为了熟悉编译流程,建议先从ArduCopter开始,因为多旋翼在Ardupilot社区中拥有最庞大的用户群体和文档积累,后续遇到问题也更容易找到解决方案。
编译命令:
./waf copter编译输出文件一般在build/<板型>/bin/目录下,常见文件名格式为:
- arducopter_with_bl.bin:包含Bootloader的完整固件;
- arducopter.apj:用于Mission Planner地面站刷写的固件包;
- arducopter.bin:纯固件镜像,不含Bootloader。
其中.apj文件是使用Mission Planner刷写时的常用格式,而.bin文件则多用于命令行刷写或者U盘拖拽刷写。如果你使用QGroundControl地面站,通常刷.apj即可。
4. 实际编译操作全流程
4.1 获取源码与子模块初始化(新手重点)
嫌弃官方仓库下载太慢的朋友,通常会在国内镜像或代理加速之间犹豫。我的建议是:第一遍尽量从官方地址拉取,因为镜像源的同步时效性和完整性不一定有保证。某些镜像漏掉子模块,会让后续编译非常痛苦。网络环境实在不好的同学,可以用代理加速git操作,而不是更换仓库源。
整个源码获取过程如下:
mkdir -p ~/work cd ~/work git clone --recurse-submodules https://github.com/ArduPilot/ardupilot.git cd ardupilot git submodule update --init --recursive仓库体积不小,包含大量历史记录和子模块内容,第一次拉取可能耗时较久。完成后可以快速检查子模块状态:
git submodule status正常情况下不会有异常输出。如果看到类似-开头的行,说明对应子模块没有正确初始化,需要重新执行git submodule update --init --recursive。
实际操作中,我见过太多人跳过这步检查,直接去编译,结果编译到中间环节报一堆找不到文件的错误。那些错误往往回看子模块状态就能发现端倪。
4.2 运行环境依赖安装(此处易出坑)
依赖脚本的运行时间取决于网络速度和系统状态。我这里给出一个相对稳妥的执行顺序:
cd ~/work/ardupilot git submodule update --init --recursive cd Tools/environment_install sudo ./install-prereqs-ubuntu.sh -y cd ../../脚本执行过程中可能会弹出图形化安装界面或者要求确认的交互式提示,比如安装某些驱动时询问是否保留修改过的配置文件。推荐全部选择默认或保留当前配置。
等脚本跑完后,重启一次终端,或者执行:
source ~/.bashrc然后验证关键工具:
which arm-none-eabi-gcc python3 --version gcc --version如果which arm-none-eabi-gcc没有任何输出,说明工具链没有正确安装到PATH中。此时需要检查~/bin或者~/.local/bin目录下是否存在arm-none-eabi-gcc,并手动加入PATH:
export PATH=~/bin:$PATH echo 'export PATH=~/bin:$PATH' >> ~/.bashrc顺带提一句,有些用户在编译过程中遇到/bin/sh: 1: arm-none-eabi-gcc: not found的错误,就是PATH配置没弄好。这个错误信息非常明确,处理方式上面已经给了。
4.3 板型配置与首次编译
环境一切就绪后,正式进入编译环节。这里以Pixhawk1作为示例:
cd ~/work/ardupilot ./waf configure --board Pixhawk1 ./waf copter首次编译需要的时间比较长,通常在十几分钟到半小时不等,取决于机器性能。编译过程中屏幕上会滚动输出大量编译信息,看到warning不用慌,只要不是error导致退出,基本不影响最终固件生成。
如果编译过程中由于某种原因中断,直接重新执行./waf copter即可。waf支持增量编译,只有修改过的部分会重新编译,比起第一次编译要快得多。
编译成功后,到输出目录检查产物:
ls -la build/Pixhawk1/bin/正常情况下会看到以下文件:
- arducopter_with_bl.bin
- arducopter.apj
- arducopter.bin
这三个文件用途存在差异,实际刷写飞控时根据地面站软件选择对应的格式。其中arducopter.apj和arducopter_with_bl.bin用的最多。
4.4 编译参数调整与常见自定义
除了默认编译固件,Ardupilot还支持在编译时加入自定义编译选项。比较常用的是在waf configure阶段传入额外的宏定义。例如取消某些组件的编译、引入自定义参数等。
./waf configure --board Pixhawk1 --disable-uart这个例子虽然不一定适合所有场景,但它展示了一个思路——构建系统的参数化能力。实际上,大多数人的自定义需求集中在代码层面修改,比如调整默认PID参数、修改最大飞行速度限制、增加新的传感器驱动支持等。这些改动只要在源码对应位置修改后重新编译即可,不需要额外配置参数。
另一个很实用的自定义是设置自己的固件标识,方便在地面站中识别固件来源:
./waf configure --board Pixhawk1 --custom-id My_Own_Firmware编译后在Mission Planner里看到的固件版本字符串就会带上这个标识。这个方法适合团队内部维护多套定制固件时的区分管理。
5. 常见编译错误与排查经验
5.1 git子模块相关错误
错误现象:
编译时提示找不到某个头文件,panic之后退出,或者提示某个库文件不存在。常见错误信息中带有missing或No such file字样。
排查方法:
先不要急着删除重装,而是回到仓库根目录执行:
git submodule status如果有子模块状态异常,输出行开头会是-或+而不是空格。这表示子模块没有正确同步。执行:
git submodule update --init --recursive然后重新编译。这个问题绝大多数情况下都能通过重新拉取子模块解决。
5.2 编译工具链缺失或版本异常
错误现象:
执行./waf copter时提示找不到编译器,或者编译器版本太低/太高。
排查方法:
首先检查工具链:
arm-none-eabi-gcc --version如果没有输出,说明工具链没有安装到PATH。按照2.2小节的方法重新配置。如果工具链存在但版本异常,建议检查是否安装过多个版本的ARM GCC导致优先级混乱。可以用which -a arm-none-eabi-gcc查看所有路径,确保使用的是Ardupilot环境脚本配置的那个版本。
5.3 Python依赖缺失
错误现象:
编译到某个模块时提示ModuleNotFoundError: No module named 'pymavlink'或者其他Python库缺失。
排查方法:
安装对应依赖即可:
pip3 install pymavlink如果是权限问题,可以加上--user参数。多数情况下,环境安装脚本已经处理过这一块,出现缺失多半是因为用户中途跳过了某些步骤。
5.4 磁盘空间不足
错误现象:
编译过程中提示No space left on device或者编译产物写入失败。
排查方法:
Ardupilot的构建目录占用空间不小,通常会有几GB。第一次编译时尤其明显。建议提前检查磁盘剩余空间:
df -h如果空间不足,可以清理build目录下的旧板型配置,或者将整个仓库迁移到大容量分区。有时候还会遇到inode耗尽的情况,可以用df -i查看inode使用率,这个问题相对隐蔽,但排查思路一样。
5.5 编译报错但信息不明确
编译过程中偶尔会出现一些非标准错误信息,或者编译中途直接卡死。这种时候我的处理思路一般是:
- 清除构建缓存,重新执行configure。
- 检查系统内存是否充足,编译过程较吃内存,内存不足时表现就是莫名卡死或段错误。
- 检查源码是否有未提交的本地改动污染了构建。
清除缓存的操作:
./waf clean ./waf configure --board Pixhawk1 ./waf copter这一步能解决相当一部分“玄学”问题。
6. 固件的烧写与验证
6.1 通过地面站刷写固件
固件编译完成后,最常用的是通过Mission Planner刷写。打开Mission Planner,选择对应的串口或USB设备,连接飞控,然后进入初始设置中的“安装固件”页面,选择“加载自定义固件”,找到刚才编译得到的.apj文件即可。
刷写过程中需要注意:
- 确保飞控供电稳定,不要使用可能掉电的USB Hub;
- 刷写过程中不要断开连接;
- 如果飞控已经有旧固件且Bootloader版本过老,可能需要先刷Bootloader再刷固件。
实际操作中,我遇到过因为USB口供电不足导致刷写一半失败的情况,换了机箱后置USB口后问题解决。如果飞控支持SD卡刷写,也可以通过复制.bin文件到SD卡根目录,随后上电自动刷写的思路来操作,这种方式的稳定性通常高于串口刷写。
6.2 编译产物的正确选择
不同刷写方式对应不同文件:
| 刷写方式 | 推荐文件格式 | 说明 |
|---|---|---|
| Mission Planner | .apj | 包含元数据,地面站自动识别 |
| QGroundControl | .apj | 同上 |
| SD卡刷写 | .bin | 不包含Bootloader,适合已有引导固件的板子 |
| 命令行烧写工具 | .bin | 灵活度高,但需要自行处理地址偏移 |
有一个易混淆的点:arducopter_with_bl.bin是Bootloader加固件的合并文件,SD卡方式用带Bootloader或不带都可以,但串口工具直接写入时,如果写入的是带Bootloader的版本,而板子本身已经存在Bootloader,可能会造成引导异常。稳妥起见,有Bootloader的板子优先使用不带Bootloader的.bin,即arducopter.bin。
6.3 刷写后的基本验证
固件刷写完成并重新上电后,先在Mission Planner的初始设置界面查看固件版本和机型信息。和编译时选择的板型、固件类型对比,确认一致。
接下来看传感器页面,确认IMU、罗盘、GPS等是否正常识别。如果传感器读数明显异常,先不要急着调试参数,大概率是固件板型选错,导致传感器硬件地址和驱动不匹配。这种情况重新刷写正确板型的固件即可。
还有一个容易被忽略的细节:刷写新固件后,飞控的遥控器校准、加速度计校准和罗盘校准都需要重新做一遍。因为新固件的默认参数可能覆盖了旧的校准数据。跳过校准直接解锁试飞是非常危险的。
7. 实用经验总结与操作建议
7.1 编译环境维护心得
编译环境这种东西,最怕“用的时候没有,装的时候乱装”。如果只是偶尔编译一次Ardupilot固件,我建议做成一个固定的“编译工作区”,不要和日常开发环境混在一起。
比如专门建一个~/work/ardupilot目录,所有相关源码、工具链、构建脚本都放在这里。这样可以最大程度避免多个项目之间的环境变量和依赖包冲突。
另外,升级Ardupilot源码前,建议先看更新日志。某些大版本更新会调整子模块结构或工具链要求,如果只是盲目git pull,很可能在下次编译时踩到新的坑。
7.2 增量编译与日常迭代
如果你和我一样,经常需要修改代码加个功能、调个参数然后重新编译,那增量编译就是最依赖的操作。每次修改后,直接:
./waf copter会自动只编译受影响的部分。大部分情况下,从修改到生成新固件只需要一两分钟,比首次编译快得多。
但有一点要注意:如果修改涉及工程配置文件、Board定义、或者编译选项,增量编译偶尔会不够彻底,出现改动不生效的假象。遇到这种情况,优先执行一次./waf clean,再做完整编译。虽然慢一点,但结果可靠。
7.3 常见误区和避坑清单
最后整理一些个人经验中比较常见的误区,这些坑我都踩过,写出来帮大家避一避:
- 不要用系统自带的gcc去编译飞控固件。系统gcc是面向本机程序的,编出来的东西根本不能跑在MCU上。必须要ARM交叉编译工具链。
- 不要随意更换子模块版本。某些第三方库和Ardupilot主仓库之间存在严格的版本匹配关系。手动切换到其他分支或者旧版本,很可能编译出一堆错误。
- 不要忽略Bootloader。第一次刷写或者换板子时,Bootloader缺失会导致固件无法引导。飞控看起来上电了,但地面站连接不上,多半就是这个原因。
- 不要把编译任务跑在内存较小的虚拟机上。Ardupilot编译峰值内存占用不低,1GB内存的虚拟机编译到一半会比较吃力,至少要2GB以上。
- 不要跳过传感器校准步骤。新固件刷上去默认参数不一定适配你的机型,校准则是最低保障。
7.4 后续延伸方向
编译固件只是整个Ardupilot生态的敲门砖。part.1跑通后,可以继续往几个方向深入:
- SITL仿真环境的搭建,不需要真实硬件就能跑通飞控逻辑和地面站联调;
- MAVProxy和MAVLink协议的调试技巧,学会通过命令行与飞控交互;
- 从源码层面理解ArduCopter的姿态控制链路,从代码角度改进飞行性能;
- 自定义外设驱动的编写,让飞控支持非官方的传感器与执行机构。
每一块展开来都值得单独写一篇。这篇文章先把编译构建这条主线捋清楚,后续我们再一步步深入。
按照我个人这几年接触Ardupilot的经验,编译环境一次配好,后面再折腾的成本就低很多。很多人在项目中期换电脑、换系统,结果重新配环境又是半天起步,关键就是当时没有把整个环境安装过程记录下来。如果你看完这篇文章打算动手实践,建议把用到的命令和安装的依赖版本存成一个笔记,等下次重装环境时照着执行,能省下大把时间。