第二章 源码获取与编译:Git仓库管理、分支选择策略、waf编译系统详解、常见编译错误及解决

好,咱们直接进入正题。源码获取与编译,是每个ArduPilot开发者都要过的第一关。我见过太多新手在这上面卡好几天,甚至有人因为分支选错,飞控刷上去直接炸机。嗯,这节课我就把这几年的经验掰开揉碎了讲给你听。

2.1 Git仓库管理:别把仓库搞乱了

ArduPilot的官方仓库在GitHub上,地址是 https://github.com/ArduPilot/ardupilot。但直接 git clone 完事?没那么简单。

警告: 千万不要在Windows上用默认的Git Bash直接clone!ArduPilot仓库包含大量子模块,Windows的路径长度限制和换行符转换会让你痛不欲生。我建议用WSL2或者直接上Linux。

我个人习惯的克隆方式是这样的:

git clone --recursive https://github.com/ArduPilot/ardupilot.git
cd ardupilot
git submodule update --init --recursive --force

为什么要加 --recursive?因为ArduPilot依赖很多第三方库,比如 mavlink、uavcan,它们都是以子模块形式存在的。如果你忘了这一步,编译时会报一堆找不到头文件的错误。

小技巧: 如果你网络不好,可以设置Git代理,或者用 git clone --depth=1 只拉取最新版本。但注意,深度克隆后切换分支会比较麻烦。

2.2 分支选择策略:选错了真会炸机

ArduPilot的分支策略,说白了就是「稳定优先,尝鲜谨慎」。我见过有人直接拿 master 分支去飞,结果遇到一个未修复的bug,飞机直接翻跟头。

分支名称 用途 风险等级 我的建议
master 开发主线,每天都有新提交 高 仅用于开发测试,别上真机
Copter-4.5.x 稳定版本分支,经过充分测试 低 量产或正式飞行首选
Plane-4.5.x 固定翼稳定分支 低 固定翼项目用这个
Sub-4.5.x 水下机器人稳定分支 低 水下项目专用
beta 候选发布版,接近正式版 中 可以测试新功能,但要做好回退准备

我的选择策略很简单:

  • 正式项目: 用最新的稳定分支,比如 Copter-4.5.x。别追新,稳定第一。
  • 功能验证: 用 beta 分支,但一定要在仿真环境里跑几天。
  • 二次开发: 基于稳定分支创建自己的特性分支,比如 my-feature-from-4.5。
核心原则: 永远不要在 master 上做二次开发!你想想看,master每天都有变动,你今天改的代码,明天可能就跟上游冲突了。我曾经有个同事,直接在master上改了一个月,结果合并时发现冲突多到无法解决,最后只能重来。

2.3 waf编译系统详解:别怕,它没那么神秘

ArduPilot用waf作为编译系统,而不是常见的CMake或Makefile。我第一次接触waf时也觉得奇怪,但用久了发现,它其实更适合嵌入式交叉编译的场景。

waf的核心命令就几个,我列出来:

# 配置编译环境,选择板卡
./waf configure --board Pixhawk1

# 编译指定固件
./waf copter

# 编译并上传
./waf --upload copter

# 清理编译产物
./waf clean

# 查看所有可用板卡
./waf list_boards

这里有个坑:./waf configure 只需要执行一次,除非你换了板卡或者改了工具链。每次编译前不需要重新configure,直接 ./waf copter 就行。

提速技巧: 多核编译可以加 -j 参数,比如 ./waf -j8 copter。我一般设为核心数+1,8核CPU就用 -j9。

waf的编译流程其实很清晰:

  1. configure阶段: 检查工具链、依赖库,生成编译配置。
  2. build阶段: 根据配置编译源码,生成 .elf 和 .bin 文件。
  3. upload阶段: 通过串口或SD卡将固件刷写到飞控板。

下面这张图能帮你理解waf的整个工作流程:

waf编译系统工作流程 ArduPilot源码 ./waf configure ./waf copter arducopter.elf / .bin ./waf --upload copter 注意点 • configure只需执行一次 • 换板卡需重新configure • 多核编译用 -j 参数 • 编译前先 git pull

2.4 常见编译错误及解决:这些坑我都踩过

编译错误是家常便饭。我把最常见的几种列出来,你遇到了直接对照解决就行。

错误1:找不到Python模块

ModuleNotFoundError: No module named 'future'

原因: waf依赖一些Python库,比如 future、lxml。

解决:

pip install future lxml pyserial

如果你用的是Python3,记得用 pip3。

错误2:工具链找不到

arm-none-eabi-g++: command not found

原因: 没装ARM交叉编译工具链。

解决:

# Ubuntu/Debian
sudo apt-get install gcc-arm-none-eabi

# macOS
brew install arm-none-eabi-gcc

我个人习惯用 gcc-arm-none-eabi-9-2020-q2-update 这个版本,太新或太旧都可能出问题。

错误3:子模块未更新

fatal: 'mavlink' does not exist

原因: 克隆时忘了 --recursive,或者子模块版本不匹配。

解决:

git submodule update --init --recursive --force

如果还不行,试试:

git submodule foreach --recursive git checkout --force

错误4:编译时内存不足

virtual memory exhausted: Cannot allocate memory

原因: 多核编译时,每个编译线程都会占用内存。如果你用 -j16 但只有8GB内存,很容易爆。

解决: 减少并行数,比如 ./waf -j4 copter。或者加swap空间。

注意: 如果你在虚拟机里编译,建议给虚拟机分配至少4GB内存和2个CPU核心。我曾经在2GB内存的虚拟机上编译,等了两个小时还没编译完,最后直接卡死了。

错误5:链接时符号未定义

undefined reference to 'AP_Param::setup_object_defaults'

原因: 通常是代码版本与库版本不匹配。比如你从master分支拉了新代码,但子模块还是旧的。

解决: 先更新子模块,然后 ./waf clean 再重新编译。

我的习惯: 每次编译前,我都会先执行 git pull 和 git submodule update --recursive,确保代码是最新的。然后 ./waf clean 清理旧产物,再开始编译。虽然多花几十秒,但能避免很多莫名其妙的问题。

2.5 避坑指南:我踩过的那些坑

嗯,最后分享几个我亲身经历过的坑,希望能帮你省点时间。

  • 坑1: 我曾经在 master 分支上直接改代码,结果第二天上游合并了一个大改动,我的代码全冲突了。从那以后,我每次开发都从稳定分支拉一个特性分支。
  • 坑2: 有一次编译通过了,但飞控刷上去后GPS一直搜不到星。查了两天,发现是编译时选错了板卡配置,导致GPS的UART引脚没初始化。所以,./waf configure --board 一定要选对。
  • 坑3: 用Windows的Git Bash克隆仓库,结果子模块下载到一半报错,因为路径太长了。后来我改用WSL2,再也没出过这个问题。

好了,这一章的内容就这些。源码获取和编译是基础中的基础,但基础打牢了,后面才能走得稳。记住:分支选稳定,编译前更新,遇到错误先看子模块。做到这三点,你就能避开80%的坑。