1. 环境准备:从零搭建Ubuntu编译环境

我刚开始接触APM飞控编译的时候,被各种依赖和环境配置搞得头大,后来才发现其实只要把基础环境搭好了,后面的编译就会顺利很多。今天我就把自己在Ubuntu系统上搭建APM编译环境的完整过程分享给大家,这些都是我踩过坑之后总结出来的经验。

首先你需要一个Ubuntu系统,我推荐使用Ubuntu 18.04或20.04 LTS版本,这两个版本比较稳定,社区支持也更好。如果你用的是Windows系统,可以考虑安装WSL2(Windows Subsystem for Linux),实测下来编译效果和纯Linux环境差不多。

安装完系统后,第一件事就是更新软件包列表和升级现有软件包。打开终端,输入以下命令:

sudo apt update
sudo apt upgrade -y

这个步骤很重要,因为系统自带的软件源可能不是最新的,更新后才能确保安装的依赖包版本正确。我遇到过因为没更新系统而导致编译失败的情况,所以建议大家一定要先执行这个操作。

接下来安装编译APM所需的依赖包,这是最关键的一步。APM飞控编译需要很多开发工具和库文件支持,缺一不可。执行以下命令一次性安装所有依赖:

sudo apt install git zip python3-pip python3-dev python3-setuptools python3-wheel \
python3-empy python3-numpy python3-serial python3-future \
gcc-arm-none-eabi binutils-arm-none-eabi \
g++ make cmake build-essential device-tree-compiler \
libxml2-dev libxslt1-dev zlib1g-dev -y

这些依赖包包含了Python开发环境、ARM交叉编译工具链、构建工具和各种开发库。其中gcc-arm-none-eabi是最重要的,它是专门用于编译ARM Cortex-M系列处理器的交叉编译器,APM飞控固件就是靠它来编译的。

安装完基础依赖后,还需要通过pip安装一些Python包。这里有个小技巧:使用国内镜像源可以大大加快下载速度。我一般用清华大学的源:

python3 -m pip install pip -U
python3 -m pip install pyserial empy future pexpect -i https://pypi.tuna.tsinghua.edu.cn/simple

这些Python包在编译过程中起着重要作用:pyserial用于串口通信,empy是模板引擎,future提供Python 2/3兼容性,pexpect用于自动化交互式应用程序。我记得有一次编译失败就是因为empy版本不兼容,所以建议大家严格按照指定版本安装。

最后验证一下环境是否配置正确。检查ARM交叉编译器是否安装成功:

arm-none-eabi-gcc --version

如果显示类似arm-none-eabi-gcc (15:9-2019-q4-0ubuntu1) 9.2.1 20191025的版本信息,说明编译器安装成功了。再检查Python版本:

python3 --version

APM编译需要Python 3.6或更高版本,如果版本太低需要先升级Python。

环境准备阶段就这些内容,虽然步骤多了点,但只要按顺序执行一般不会出问题。我在多次环境搭建中发现,最常出现的问题就是网络原因导致的包下载失败,这时候重试几次或者换个软件源通常就能解决。

2. 获取源码与子模块初始化

环境准备好之后,下一步就是获取APM的源代码。APM的源码托管在GitHub上,我们需要使用git命令来克隆仓库。这里我建议大家直接克隆官方的仓库,这样能保证代码是最新的。

打开终端,选择一个合适的目录,执行以下命令克隆代码:

git clone https://github.com/ArduPilot/ardupilot.git
cd ardupilot

这个仓库比较大,有几百MB,所以下载需要一些时间,取决于你的网络速度。我第一次克隆时用了差不多半小时,所以如果网速慢的话可以耐心等待一下。

克隆完成后,你会看到一个ardupilot的目录,这里面包含了所有APM飞控的源代码。进入这个目录,现在要做最重要的一步:初始化子模块。

APM项目使用了很多第三方库和组件,比如NuttX实时操作系统、MAVLink通信协议等,这些都是以子模块的形式管理的。如果你不初始化子模块,编译时就会缺少这些必要的组件。

初始化子模块的命令是:

git submodule update --init --recursive

这个过程也会下载不少内容,需要一定时间。我遇到过子模块初始化失败的情况,一般都是网络问题导致的。如果遇到这种情况,可以多试几次,或者使用--force参数强制更新:

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

有时候子模块的版本可能会和主项目不匹配,这时候需要切换到对应的分支或标签。APM的不同版本对应着不同的子模块版本,所以如果你要编译特定版本的固件,需要先切换分支:

git checkout Copter-4.3.6
git submodule update --init --recursive

这样就切换到了Copter 4.3.6版本,并更新对应的子模块。在实际项目中,我建议使用稳定的发布版本而不是最新的开发版本,因为开发版本可能包含未测试的代码,稳定性没有保证。

初始化完成后,建议检查一下子模块的状态,确保所有子模块都正确初始化:

git submodule status

如果所有行开头都是空格而不是减号,说明子模块都初始化成功了。如果有减号,表示某些子模块没有正确初始化,需要重新执行更新命令。

还有一个常见问题是子模块更新后出现冲突或错误,这时候可以尝试先清理再重新初始化:

git submodule deinit -f --all
git submodule update --init --recursive

这样会先清理所有子模块,然后重新初始化,相当于从头开始。虽然耗时一些,但能解决大部分子模块相关的问题。

源码获取和子模块初始化是编译的基础,这一步做不好后面编译肯定会出问题。我记得有一次因为子模块没有完全更新,编译时一直报找不到头文件的错误,折腾了好久才发现是子模块的问题。所以大家一定要确保这一步正确完成后再进行后续操作。

3. waf配置与硬件平台选择

源码准备就绪后,就要开始配置编译环境了。APM使用waf作为构建系统,这是一个用Python编写的构建工具,比传统的make更加灵活和强大。刚开始我也有点不习惯waf,但用了几次后发现确实比make方便很多。

首先我们来了解一下waf的基本用法。在ardupilot目录下,执行以下命令可以查看waf的所有选项:

./waf --help

这会显示waf支持的所有命令和参数,对于新手来说很有帮助。我最常用的是configure、copter、plane这些命令。

在编译之前,我们需要先配置目标硬件平台。APM支持很多种飞控硬件,从Pixhawk系列到各种国产飞控都有支持。查看所有支持的硬件列表:

./waf list_boards

这个命令会列出所有可用的硬件平台,比如fmuv3、fmuv4、Pixhawk4、CubeOrange等等。选择正确的硬件平台很重要,因为不同硬件的处理器、外设和内存配置都不同,编译出的固件不能混用。

根据你的飞控硬件选择合适的平台。常见的配置如下:

  • Pixhawk 1/2 系列使用 fmuv2 或 fmuv3
  • Pixhawk 4 使用 Pixhawk4
  • Cube系列使用 CubeOrange 或 CubeBlack
  • 国产飞控需要查看具体支持的平台

配置硬件平台的命令格式是:

./waf configure --board <硬件平台名称>

比如我要为Pixhawk 4编译固件,就执行:

./waf configure --board Pixhawk4

第一次配置时会下载一些工具链和依赖项,需要等待一段时间。配置成功后,会显示类似"Configuration completed successfully"的消息。

有时候配置过程中会出现错误,最常见的是缺少依赖包。比如可能会提示缺少empy或future等Python包,这时候只需要按照提示安装相应的包即可:

python3 -m pip install empy future pexpect

另一个常见错误是编译器找不到,通常会提示"could not configure a C++ compiler"。这可能是没有安装ARM交叉编译工具链,或者安装的版本不对。解决方法是重新安装工具链:

sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi

配置完成后,会在项目目录下生成一个build文件夹,里面包含了针对该硬件平台的配置文件和编译缓存。如果你之后要更换硬件平台,建议先清理这个文件夹:

./waf clean

或者更彻底的清理:

./waf distclean

我建议在每次更换硬件平台或者遇到编译问题时,都先执行清理命令,这样可以避免很多奇怪的编译错误。

配置步骤只需要执行一次,除非你更换了硬件平台或者修改了核心配置。配置成功后,就可以开始编译具体的固件了。记得每次编译前确认当前配置的硬件平台是否正确,这是我踩过多次坑后养成的习惯。

4. 编译不同机型固件

配置好硬件平台后,就可以开始编译具体的飞控固件了。APM支持多种机型,包括多旋翼、固定翼、直升机和无人车等,每种机型都有对应的编译命令。这里我把自己经常编译的几种机型固件经验分享给大家。

首先是最常见的多旋翼固件,编译命令是:

./waf copter

这个命令会编译适用于当前配置硬件平台的多旋翼固件。编译过程需要一些时间,取决于你的电脑性能。我的i7处理器大概需要5-10分钟,第一次编译可能会更久一些,因为需要编译所有依赖库。

编译成功后,会在build目录下生成固件文件。比如对于Pixhawk 4硬件,固件路径通常是build/Pixhawk4/bin/arducopter.apj。这个.apj文件就是可以刷写到飞控中的固件。

固定翼固件的编译命令类似:

./waf plane

直升机和无人车的编译命令分别是:

./waf heli
./waf rover

如果你需要编译水下机器人的固件,可以使用:

./waf sub

除了这些主要的机型固件,APM还支持一些特殊用途的固件。比如外设固件,用于CAN总线设备:

./waf AP_Periph

这个我用的比较多,因为经常需要调试GPS和ESC等外设。AP_Periph固件可以让这些外设设备独立工作,通过CAN总线与主飞控通信。

编译过程中可能会遇到各种错误,我把自己遇到过的常见问题和解决方法分享一下。最常见的是内存不足错误,特别是在虚拟机上编译时。解决方法是要么给虚拟机分配更多内存,要么使用交换文件:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

另一个常见错误是Python包版本不兼容。APM对Python包的版本要求比较严格,如果版本不匹配就会编译失败。解决方法是安装指定版本的包:

python3 -m pip install empy==3.3.4 future==0.18.3

有时候编译会卡住或者报一些莫名其妙的错误,这时候可以尝试清理后重新编译:

./waf clean
./waf copter

如果还是不行,就用更彻底的清理:

./waf distclean
./waf configure --board <你的硬件平台>
./waf copter

编译完成后,建议检查一下生成的固件大小和版本信息。正常的固件大小应该在1-2MB之间,具体取决于硬件平台和功能配置。如果固件大小异常,可能是编译过程中出了问题。

我一般还会用arm-none-eabi-size工具查看固件的内存占用情况:

arm-none-eabi-size build/Pixhawk4/bin/arducopter

这样可以了解代码段、数据段和bss段的大小,确保没有超出硬件的内存限制。特别是对于内存较小的飞控硬件,这个检查很有必要。

编译不同机型固件的过程大同小异,主要区别在于最后的链接阶段。多旋翼、固定翼等不同机型会链接不同的代码模块,但前面的编译过程都是共享的。所以如果你已经编译过一种机型,再编译另一种机型时会快很多,因为很多目标文件已经编译好了。

5. 高级技巧与疑难解答

在使用APM编译系统的过程中,我积累了一些高级技巧和疑难解答经验,这些都是在实际项目中踩坑后总结出来的。掌握这些技巧可以大大提高编译效率,避免很多常见问题。

首先是并行编译技巧。waf支持并行编译,可以显著加快编译速度。使用-j参数指定并行任务数,一般是CPU核心数的1.5到2倍:

./waf copter -j8

我的电脑是8核的,所以用-j8。如果你不确定该用多少,可以直接用-j让waf自动决定最优并行数:

./waf copter -j

并行编译能大幅缩短编译时间,特别是清理后重新编译时效果更明显。我记得第一次编译用了将近半小时,后来用并行编译只需要5分钟左右。

其次是ccache的使用。ccache是个编译器缓存工具,可以缓存之前的编译结果,重复编译时直接使用缓存,速度更快。安装配置方法:

sudo apt install ccache
export CC="ccache gcc"
export CXX="ccache g++"

配置好后,第二次及以后的编译速度会有明显提升。特别是经常切换分支开发时,ccache的效果非常明显。

关于硬件平台选择,有个实用技巧是使用--static参数进行静态构建:

./waf configure --board bebop --static

静态构建会将所有库都链接到固件中,生成独立的二进制文件,适用于一些特殊的硬件平台。

在开发过程中,经常需要只编译特定的目标而不是整个固件。waf支持指定编译目标:

./waf --targets bin/arducopter

这样只编译多旋翼固件,不会编译其他机型。对于调试和测试很有用。

接下来分享一些常见的编译错误和解决方法。最让人头疼的是Python环境问题,经常提示各种模块找不到。这时候需要检查Python路径和版本:

python3 --version
python3 -m pip list

确保Python是3.6以上版本,并且安装了所有必需的包。如果问题依旧,可以尝试重新创建Python虚拟环境。

另一个常见错误是工具链问题,提示找不到编译器或者编译器版本不对。解决方法是检查ARM工具链:

arm-none-eabi-gcc --version
which arm-none-eabi-gcc

确保工具链已正确安装并且在PATH中。有时候需要手动添加路径:

export PATH=$PATH:/usr/bin/arm-none-eabi-gcc

内存不足错误也很常见,特别是在虚拟机上编译大型固件时。除了增加交换空间,还可以尝试优化编译选项:

./waf configure --board <平台> --optimize=size

这样会启用尺寸优化,减少内存占用。

如果遇到奇怪的链接错误,可能是缓存问题。可以尝试清理pch(预编译头文件):

./waf clean --pch

有时候子模块更新后会出问题,这时候需要强制重新初始化:

git submodule deinit -f --all
git submodule update --init --recursive --force

对于网络问题导致的下载失败,可以尝试使用代理或者换源。比如Python包可以使用国内镜像源:

python3 -m pip install <包名> -i https://pypi.tuna.tsinghua.edu.cn/simple

最后,如果所有方法都试过了还是编译失败,可以考虑使用Docker环境。APM官方提供了Docker镜像,包含了完整的编译环境:

docker run -it --rm -v $(pwd):/ardupilot ardupilot/ardupilot-dev-env

这样就在容器中有了一个配置好的编译环境,可以避免很多环境问题。

这些技巧都是我在实际项目中总结出来的,希望能帮助大家少走弯路。编译APM固件虽然一开始可能觉得复杂,但熟悉之后就会发现其实很 straightforward。最重要的是保持耐心,遇到问题时不要慌,按照错误信息一步步排查,总能找到解决方法。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐