夜雨聆风学习资料网

ARTICLE · 1262030

开源ROV | ArduSub详解-源码与构建

开源ROV | ArduSub详解-源码与构建

本文记录 ArduSub 源码的获取、编译、运行,以及修改代码后重新编译看到效果的整个过程,全部在 Windows 自带的 WSL 里完成,不需要任何硬件。

1 仓库简介

源码在 https://github.com/ArduPilot/ardupilot。这个源码库包含了六种载具的代码:Plane(固定翼)、Copter(多旋翼)、Rover(地面车)、Sub(水下)、Blimp(飞艇)、AntennaTracker(天线跟踪器)。ArduSub 就是其中的 Sub 部分。仓库有约 1.6 万 star,许可证 GPL-3.0,即源码永远公开,谁都可以下载和修改,做二次开发没问题,但从它改出来的成果也必须同样开源(图 1)。

图 1:GitHub 仓库首页

仓库是2010 年创建,而ArduSub 的代码是 2015 年 12 月 30 日进来的,提交人是 Blue Robotics 的联合创始人 Rustom Jehangir。项目现在很活跃,用 git 统计 master 分支:最近 30 天有 631 个提交、51 个人提交过代码,最近一年提交数是 5013。

版本方面,master 分支是开发主线,代码随时在合入。稳定版用标签(tag)标记,水下载具的标签以 Sub- 开头,最新的稳定版是 Sub-4.7.2。六种载具统一发版,所以同一个提交上同时挂着 Sub-4.7.2、Plane-4.7.2、Copter-4.7.2 一串标签。Releases 页面把这些稳定版列成发布清单,附更新说明。

主开发语言是 C++,一部分第三方库是 C,构建工具是 Python。大致的目录如下:

ardupilot/├─ ArduSub/          入口 + 11 个模式文件(约 1 万行)├─ ArduPlane/ ArduCopter/ Rover/ Blimp/ AntennaTracker/  其余五种载具├─ libraries/        全部载具共用,固件的大头│   ├─ AP_InertialSensor/    传感器   ≈ 2.2 万行│   ├─ AP_NavEKF3/          状态估计  ≈ 1.7 万行│   ├─ GCS_MAVLink/         通信      ≈ 1.6 万行│   ├─ AP_Motors/            电机      ≈ 1.2 万行│   └─ AP_HAL + AP_HAL_*/    硬件抽象层│        ChibiOS · Linux · SITL · ESP32  四个硬件后端└─ modules/          子模块 15 项,克隆默认不带    waf · mavlink · ChibiOS · littlefs · ...

仓库的目录树,右侧是各部分的职责和实测行数

2 获取源码

用 git clone 把仓库拉下来,然后用 ls-remote 查最新的稳定版标签,切过去:

$ git clone https://github.com/ArduPilot/ardupilot.git$ git ls-remote --tags origin 'Sub-*'dbe79216...  refs/tags/Sub-4.7.1fb773567...  refs/tags/Sub-4.7.2  ← 最新稳定版$ git checkout Sub-4.7.2HEAD is now at fb77356  Plane: version to 4.7.2

注意标签名是 Sub-4.7.2,不是 ArduSub-4.7.2。如果有新的稳定版本,建议用新的版本。

完整仓库拉下来有 2 GB 上下,包含 7 万多个提交的全部历史。如果只想要当前版本代码,clone 时加 --depth 1 参数,只拉最新一次提交的文件,体积小很多,但历史就只剩一层,git log 翻不出任何记录。本文的实验在完整仓库上做。

主仓库之外还有子模块要拉。子模块(submodule)是 git 引用别的仓库的办法,主仓库只记一个地址加一个提交号,clone 的时候这些内容不会下来。ArduPilot 用它管理全部第三方依赖,一共 15 项,个别还有嵌套,全部拉齐是 26 个仓库,一条命令:

git submodule update --init --recursive

15 项的用途见表 1。

子模块
作用
waf
构建系统,编译全靠它
mavlink
MAVLink 协议库和消息生成器
ChibiOS
MCU 板上的实时操作系统
littlefs / lwip
文件系统、网络协议栈
DroneCAN 系列(4 项)
CAN 总线协议和工具
gtest / gbenchmark
测试框架
gsoap 等 5 项
各有专门用途

>表 1:15 个子模块。编译 SITL 时缺任何一项都会中断,实测只拉 waf 和 mavlink 会在 littlefs 处报错。

3 编译

官方支持的编译环境有四种:Ubuntu 等 Debian 系 Linux、其他 Linux 发行版、macOS、Windows。Windows 这条路官方给的方案是 WSL。Docker 也有,但官方文档写了不推荐,只在需要隔离环境时才用,而且 SITL 的图形功能会受限。本文用 WSL,因为 Windows 机器上这是官方认可的路,装起来也直接。

编译用的系统是 WSL 里的 Ubuntu 22.04。装依赖、拉子模块、跑编译这些操作都是照着固定命令重复执行,交给编码 agent 去跑正合适。本文的实测就是这么来的:命令交给 agent,人不用守在终端前。这类不碰控制逻辑的工作,也在 ArduPilot 给 AI 定的规范允许的范围内。

需要的编译依赖分两部分。系统包两个:build-essential(gcc、g++、make)和 python3-pip。Python 包五个:empy、future、pymavlink、lxml、pexpect,其中 empy 必须是 3.3.4 版,4.x 接口不兼容,编译会在第一步报错。官方有一键脚本 install-prereqs-ubuntu.sh,连仿真器带测试工具装一大套,编译 SITL 用不着那么多,上面这些就够。

依赖
用途
装法
build-essential
gcc / g++ / make
apt install
python3-pip
装 Python 包的入口
apt install
empy==3.3.4
waf 的模板引擎
pip3 install
future / lxml / pexpect
构建和脚本的辅助库
pip3 install
pymavlink
MAVLink 的 Python 库
pip3 install

>表 2:编译 SITL 的全部依赖

依赖装齐后开始编译,一共两步。第一步选目标板,SITL 在这里被当成一块虚拟的板子:

$ ./modules/waf/waf-light configure --board=sitl'configure' finished successfully (1.549s)$ ./modules/waf/waf-light -j8 sub[1357/1357] Linking build/sitl/bin/ardusub'sub' finished successfully (1m7.414s)$ ls -lh build/sitl/bin/-rwxr-xr-x 1 sk sk 4.9M  ardusub

8 路并行全量编译用了 1 分 07 秒,产物 4.9MB。官方 4.7.1 预编译固件是 5.9MB,自己编的比它小 17%,差别主要在编译选项。

编译之前失败过两次,第一次缺 empy(configure 能过,编译第一步停,提示装 empy==3.3.4),第二次缺 littlefs 子模块(报 source not found,就是第 2 节说的子模块没拉齐)。补齐后一次通过。

build/sitl 目录下有四样主要产物:hwdef.h 是这块虚拟板的硬件定义,configure 生成的。ap_config.h 是编译配置。lib/ 放编译出的库。bin/ardusub 是固件本体。整个目录 104MB,全是编译产物,删掉重编就会重新生成。

4 启动 SITL

上一节编出的 ardusub 是一个 Linux 程序,直接运行就是 SITL(Software In The Loop,软件在环仿真):跑的就是固件代码本身,传感器和物理世界由模拟器提供,不需要飞控硬件。水的浮力、推进器的推力这些物理量由仓库里 SITL 后端的模型计算,模拟环境可以用 SIM_ 开头的参数调,比如水流速度、传感器噪声。

$ sleep infinity | build/sitl/bin/ardusub --model vectored \    --serial0=tcp:5760 --serial1=tcp:5762Starting sketch 'ArduSub'bind port 5760 for SERIAL0bind port 5762 for SERIAL1bind port 5763 for SERIAL2Home: -35.363262 149.165237 alt=584.000000m

几个参数的意思:--model 选构型,Sub 只有两个选项:vectored 是 BlueROV2 标准版的六推进器构型,vectored_6dof 是 Heavy 版的八推进器构型,两个都定义在源码 SITL_cmdline.cpp 里。三个 --serial 各开一个 TCP 端口,5760 给地面站,5762 给脚本,5763 备用。日志末尾的 Home 是起始位置,默认值在澳大利亚堪培拉,换成自己想要的坐标加 --home 参数就行,实测换成上海临港:

启动命令加:--home=31.1434,121.8083,0,0日志输出:Home: 31.143400 121.808300 alt=0.000000m hdg=0.000000

日志里还有一句 Waiting for internal clock bits to be set,看着像卡住,其实是在等一颗模拟 LED 芯片的时钟位,正常的启动提示,等几秒自己就过去了。

启动时三个注意的点。第一,启动命令前面要接一段 sleep infinity 的管道,因为程序会检测标准输入,输入没了进程就退出。第二,启动命令得挂在一个活着的会话下面,WSL 里会话一关,挂在它下面的进程跟着被杀。第三,重新启动前先把旧进程杀干净,旧进程占着端口,新的起不来,报 Address already in use。

图 2:SITL 的三个端口和两端的连接方式(动图,依次强调)

5 连接地面站

地面站用 QGroundControl。它默认监听 UDP 14550 端口,而 SITL 的 5760 是 TCP 的,两边对不上,中间写个三十行的转发脚本(bridge)接起来,脚本一边连 SITL,一边把收到的消息转发给 QGC,同时持续替 QGC 发心跳。脚本一启动,QGC 那边就自动连上了(图 3)。

图 3:QGC 连上 SITL,顶部出现载具图标

连上后看两条消息。心跳里 type=12,水下载具的意思。心跳还有个机制:地面站得一直发,只发一次的话,固件会报 heartbeat lost,之后这位地面站发来的请求会被降权处理,连参数都读不了,实测复现过。所以转发脚本里要带着发心跳。

另一条是标识信息。地面站一连上、发过一次请求后,固件发来三行消息:

STATUSTEXT: ArduSub V4.7.2 (fb773567)STATUSTEXT: 0ca936a3c2d647bcb338ee0b61cbaee5STATUSTEXT: Frame: VECTORED

版本号、提交号、机架型号。提交号是编译的时候从 git 里取出来写进固件的,所以固件跑起来的第一句话就能证明它是从哪份源码编出来的。下一节改代码,就看这句话变不变。

6 改一行源码

上面第三行 Frame: VECTORED 出自 GCS_MAVLink_Sub.cpp 的 send_banner 函数,把它改成带自己标记的版本:

$ sed -i 's|"Frame: %s"|"Frame: %s (my build)"|' ArduSub/GCS_MAVLink_Sub.cpp$ ./modules/waf/waf-light -j8 sub'sub' finished successfully (2.094s)

重新编译只用了 2 秒。waf 记录了文件之间的依赖关系,改一个文件只重编受影响的部分,第一次全量要 1 分 07 秒,之后都是这个 2 秒的节奏。重启 SITL,地面站收到:

STATUSTEXT: Frame: VECTORED (my build)

改的字样出现在固件消息里,源码、编译、运行这条链走通了。

7 两条启动路线

前面的启动方式是直接跑编译产物。官方还有个启动脚本 sim_vehicle.py,日常开发用它更多。实际跑一次看输出:

$ python3 Tools/autotest/sim_vehicle.py -v ArduSub -f vectored --no-mavproxy -l 31.1434,121.8083,0,0'build' finished successfully (2.288s)RiTW: Starting ArduSub : build/sitl/bin/ardusub --model vectored --speedup 1 --slave 0 \  --defaults Tools/autotest/default_params/sub.parm --sim-address=127.0.0.1 -I0 \  --home 31.143399999999993,121.80830000000003,0.0,0.0RiTW: Window access not found, logging to /tmp/ArduSub.log

从输出能看到它做了四件事:启动前自动检查编译,刚才那次 2.3 秒是因为没改动。启动参数里自动带上默认参数文件 sub.parm,直接跑二进制是没有这个的,这也是两条路线最主要的差别。位置参数 -l 直接写经纬度。日志自动记到 /tmp/ArduSub.log。另外它还支持 -I0、-I1 这样开多个实例。

参考

$ArduPilot 开发者文档——构建、SITL 说明ardupilot.org/dev

$ArduPilot 源码仓库,Sub-4.7.2 标签github.com/ArduPilot/ardupilot

$QGroundControl 地面站qgroundcontrol.com

$pymavlink,MAVLink 的 Python 库github.com/ArduPilot/pymavlink

相关学习资料