笔者写这篇文章的重点是分享移植方法。Ardupilot代码在不断的演化更新,一篇非常细致的讲解移植步骤的文章总会过时,只有掌握了方法才能随机应变。必要的移植步骤是有的,不过在此之前,笔者会讲解一些辅助性的内容,比如搭建高效阅读源码的环境,移植Ardupilot会遇到哪些困难。
如果你对Ardupilot很好奇,想深入研究一些东西,但是又畏惧其复杂庞大的代码工程,那么笔者强烈推荐你耐心阅读本文,相信一定会对你有所帮助。
两年前接了个小项目,使用TI公司的单片机做飞控。这是学校的实验项目,主要是演示、推广之类的用途,要求不高。接触Ardupilot数年矣,一直想搞点大动作,正好借着这次机会,来一次真正的移植。
硬件平台为TM4C1294XL LaunchPad,软件平台为RT-Thread操作系统,基于Copter-3.5.7版本进行移植。
先放个截图,源码获取方式在文章结尾。
在10年前,四轴飞行器还是个冷门的东西,玩的人并不多,而接触过飞控开发的人就更少了。2013年全国大学生电子设计竞赛首次引入飞控类的赛题,越来越多的学生接触了飞控代码。许多开源飞控如雨后春笋般诞生,飞控开发的难度和门槛逐渐降低。
然而,历史悠久的Ardupilot的开发门槛却并未降低,个人感觉反而更高了。
上图展示了Ardupilot的发展历史,2009年第一版硬件使用的是Ardunio Mega单片机,2013年使用了性能更高资源更丰富的Pixhawk(168 Mhz/256 KB RAM/2 MB Flash)。时至今日,Ardupilot支持众多的开源闭源硬件。
Ardupilot能支持众多硬件平台,归功于其优秀的代码框架。业务、算法与驱动相分离,层次分明,从而具有强大的可扩展性。然而优秀的代码框架并不意味学习成本低,相反,需要花费一定的时间去了解它的层次和脉络。
学习成本高,导致Ardupilot令人望而生畏。笔者在决定移植Ardupilot之前,对其已有一定的了解,数年前还做过一次小移植(后面会细说)。纵使如此,也并无百分百的把握,是强烈的兴趣驱使我接下了这个项目。断断续续做了好几个月,经历了许多困难,中途都后悔了哈哈。
虽然困难,但Ardupilot是值得研究的。其包含的诸多功能,不仅仅可以用于飞行器。实际上,Ardupilot项目也不只有飞行器,其支持四种载具:
随便挑个功能,下图是Ardupilot支持的无源导航(即无GPS信号时)方式,有3D摄像头,光流,UWB定位,外置摄像头捕捉等等。
因此,笔者决定分享自己的移植经验,让更多对Ardupilot感兴趣的同学有所参考。
网上关于Ardupilot的代码分析和移植的文章有很多,笔者这篇文章的重点是分享方法。笔者移植的是Copter-3.5.7,而现在最新版本是Copter-4.0.3,代码肯定发生了很多变化。细节是死的,方法是活的。掌握了方法,才能灵活应变。
分析Ardupilot代码框架也是必不可少的,不过笔者不会讲解每一处需要修改的代码,比如关于外设驱动移植,会挑一些有代表性的来讲解。除此之外,笔者会详细地分享一些有助于成功移植的方法,比如如何加速源码的下载,命令行编译,搭建高效阅读源码的环境。
Ardupilot的代码框架
ArduPilot代码顶层目录
Ardupilot代码分为5个部分:
要重点讲的是HAL。libraries/ AP_HAL中有一个顶级AP_HAL,用于定义其余代码与特定板功能之间的接口,然后每种硬件平台都有一个AP_HAL_XXX子目录,例如,用于基于AVR的板的AP_HAL_AVR,用于Pixhawk板的AP_HAL_PX4和用于基于Linux板的AP_HAL_Linux。
换句话说,AP_HAL定义板级驱动接口,业务代码使用这些接口,而AP_HAL_XXX对应的硬件代码实现这些接口。这样一来,业务层不依赖于硬件代码,硬件代码也不依赖业务层,它们都依赖于HAL定义的接口。正是这种面向接口的设计模式,使得ArduPilot可以被移植到多种不同的平台。
至于AP_HAL中的接口具体是什么样子的,在之后讲驱动代码时再说。这里介绍Ardupilot的代码构架的目的是引出移植的两个方法。
每种硬件平台都有一个AP_HAL_XXX子目录,而我们要移植到新平台的话,就是添加一个AP_HAL_XXX,比如AP_HAL_TI,并且实现所有HAL接口。这就是第一种方法,从底层适配。
与之相对应的是提取应用层代码,将飞控业务和算法代码提取出来,并做适当的修改,以适配自己的硬件。
下面详细分析下两种方法的特点和优劣。
底层适配,即实现所有HAL接口。libraries/AP_HAL目录中的文件如下:
分为如下几类:
下图最左边的分支,就是笔者TI板子,使用RT-Thread操作系统来提供AP_HAL所需要的线程等功能。
不过笔者偷了个懒,并没有创建新的AP_HAL_TiLaunchPad,而是在AP_HAL_PX4上修改,偷梁换柱。如下图,AP_HAL_PX4中的文件都被笔者修改了,比如SPIDevice.cpp中的do_transfer函数,改成了调用RT-Thread的spi接口。
上图是飞控框架图的一小部分,此为飞控代码以及与具体硬件平台无关的共享库,可提取出包含如下功能的代码:
还可以更简单些,只提取姿态解算与控制,不管Ardupilot用了什么传感器,而是使用自己的传感器与电机驱动(比如pixhawk板子上使用的是L3GD20H和LSM303D,而你自己用mpu9250),可以实现最基础的飞行功能。笔者所说的数年前的小移植,就是这么干的,当时移植的是ArduCopter-2.9.1b。下图是移植出的姿态解算与控制代码的工程,提取并且转换成了C语言代码。
以fast_loop函数为例,给大家看下移植前后的区别。
原始代码
// Main loop - 100hz
static void fast_loop()
{
// IMU DCM Algorithm
// --------------------
read_AHRS();
// reads all of the necessary trig functions for cameras, throttle, etc.
// --------------------------------------------------------------------
update_trig();
// run low level rate controllers that only require IMU data
run_rate_controllers();
// write out the servo PWM values
// ------------------------------
set_servos_4();
// Inertial Nav
// --------------------
read_inertia();
// optical flow
// --------------------
#if OPTFLOW == ENABLED
if(g.optflow_enabled) {
update_optical_flow();
}
#endif // OPTFLOW == ENABLED
// Read radio and 3-position switch on radio
// -----------------------------------------
read_radio();
read_control_switch();
// custom code/exceptions for flight modes
// ---------------------------------------
update_yaw_mode();
update_roll_pitch_mode();
// update targets to rate controllers
update_rate_contoller_targets();
// agmatthews - USERHOOKS
#ifdef USERHOOK_FASTLOOP
USERHOOK_FASTLOOP
#endif
}
移植后
void arducopter_fast_loop(void)
{
// IMU DCM Algorithm
// --------------------
read_AHRS();
// custom code/exceptions for flight modes
// ---------------------------------------
update_yaw_mode();
update_roll_pitch_mode();
// update targets to rate controllers
update_rate_contoller_targets();
//control the motor
run_rate_controllers();
set_servos_4();
}
移植后的arducopter_fast_loop分为如下几个部分:
可以看出,这实现的是非常基础的飞行功能。其实笔者这里讲述几个函数的作用,并不是真想展开来讲飞控算法,而仅仅是想让大家体会一下:这种移植需要对飞控算法进行深入的了解。
底层适配需要深入了解HAL框架,了解每个HAL接口的功能并且实现它。这种方法,基本不用在应用层做改动,好处是:不需要深入研究应用层的代码,比如各种飞行模式,姿态解算与控制算法。再说直白点,就算你对飞控算法一无所知,只要你有扎实的嵌入式软件功底,就能完成移植。什么,软件功底也不是很厚实?不用担心,这正是我写这篇文章的目的啊。
相反,提取应用层代码则需要对飞控算法有更多的了解,并且需要深入分析Ardupilot的应用层代码,提取出你所需要的模块。好处也是与底层适配相反的,就是不需要了解HAL啦。你可以随意的修改应用层代码,包括调用驱动的地方。
应该选择哪一种,要看应用场景。
如果你只是想使用Ardupilot的一个小模块,比如姿态解算。Ardupilot将陀螺仪,加速度计,磁力计,GPS的数据进行融合,从而计算出稳定准确的姿态数据。把这些代码提取出来,应用到其他的项目中,是不错的想法。
如果你是想做一款飞行器,并且想使用到Ardupilot的诸多功能,如各种各样的飞行模式(定点悬停,路径规划),使用UWB进行无源导航,物体追踪,那就不宜提取应用层代码,而是做底层适配。否则,你可能要提取太多的代码,做太多的改动。
在开始移植之前,有两个必要的步骤:
熟练的意思是,会配置、校准飞控,装到四轴上,能操控它进行基础的飞行。
虽然我们的最终目标是将飞控代码移植到新的平台上,不过搭建参考平台的源码环境非常重要。得先在这个参考平台上把源码读懂,当遇到疑问时可稍微修改下源码并调试验证。笔者的参考平台是第一代Pixhawk,如下图:
官方开发者wiki是最应该仔细阅读的资料,建议先阅读下面两个章节,其他的章节可根据需求选读。Building这章,只看自己使用的平台即可,比如Linux/Ubuntu。
ardupilot支持以下平台,详见Building the code:
笔者当初移植时,是在VMWare虚拟机中跑Ubuntu。在虚拟机中跑系统,最大的问题就是卡顿。这次写文章,笔者试了下WSL。WSL的全称是Windows10 Subsystem for Linux,就是在Windows10下直接跑Linux,不需要虚拟机。Setting up the Build Environment on Windows10 using WSL1 or WSL2推荐使用WSL2,不过笔者的Windows10版本不支持,因此使用了WSL1。
笔者使用的WSL1,也是跑ubuntu,大部分操作没有区别,有区别时会说明。WSL1的安装,可以参考Win10安装Ubuntu子系统及图形化界面详细教程。
关于下载编译的步骤,Setting up the Build Environment (Linux/Ubuntu)说得非常清楚,ardupilot工程里面还包含了安装编译环境的脚本(Tools/environment_install/install-prereqs-ubuntu.sh,仅限于在Debian/Ubuntu系统中使用),笔者整理如下:
友情提示,这是下载并编译官方最新的源码,而并非是笔者移植所基于的3.5.7版本源码。
下载源码
git clone https://github.com/ArduPilot/ardupilot.git
cd ardupilot
git submodule update --init --recursive
安装编译环境
# 当前目录为工程根目录,即ardupilot
Tools/environment_install/install-prereqs-ubuntu.sh -y
重启终端(wsl)或者重新登录(ubuntu),或者执行
. ~/.profile
编译
# Ardupilot使用了Waf来管理项目编译。Waf是类似于make的项目构建系统,有Linux开发经验的同学肯定对make很熟悉。
./waf configure --board Pixhawk1
./waf copter
下载源码、安装编译环境和执行编译,总共10来行命令。大家实际操作时,可能会遇到几个问题。
git clone https://github.com/ArduPilot/ardupilot.git
上述命令下载的源码,默认处在master分支,若想切换到指定版本,需要使用git checkout来切换。比如切换到3.5.7版本并拉出一个新的分支,以便在其上做修改并且提交。
git checkout -b Copter-3.5-test Copter-3.5.7
# 切换了分支后,需要同步对子模块的引用。
git submodule update --init --recursive
ardupilot不仅使用git来管理自己的源码,还使用git的submodule功能来跟踪其所依赖的子模块。对于不熟悉git的同学,切不可在github下载源码的压缩包,这样无法下载其子模块,也无法进行编译。
还有一点要注意,之前提到的install-prereqs-ubuntu.sh,在3.5.7版本时是不存在的。可以在master分支上运行它来安装环境,安装完后再切换分支。
Tools/environment_install/install-prereqs-ubuntu.sh是官方为Debian/Ubuntu系统提供的一键安装编译调试环境的脚本,有兴趣的同学可以阅读下这个脚本,其内容非常简单:下载一些python库和编译器,下载的编译器为gcc-arm-none-eabi-6-2017-q2-update,位于/opt目录中。
用其编译最新源码是没问题的,若编译老版本代码,比如3.5.7,则会报错,比如笔者遇到的错误:
/opt/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include/c++/6.3.1/bits/basic_string.h: In function 'float std::__cxx11::stof(const string&, size_t*)':
/opt/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include/c++/6.3.1/bits/basic_string.h:5454:31: error: 'strtof' is not a member of 'std'
{ return __gnu_cxx::__stoa(&std::strtof, "stof", __str.c_str(), __idx); }
^~~
compilation terminated due to -Wfatal-errors.
make[3]: *** [src/platforms/nuttx/CMakeFiles/platforms__nuttx.dir/px4_nuttx_impl.cpp.obj] Error 1
make[2]: *** [src/platforms/nuttx/CMakeFiles/platforms__nuttx.dir/all] Error 2
make[1]: *** [src/firmware/nuttx/CMakeFiles/build_firmware_px4fmu-v2.dir/rule] Error 2
make: *** [build_firmware_px4fmu-v2] Error 2
需要替换成老的编译器,有如下几步:
下载老版本编译器gcc-arm-none-eabi-4_9-2015q3,密码vyqb。
这链接还包含了最新的编译器,这将两个编译器都安装在/opt目录。之后执行install-prereqs-ubuntu.sh时,其检测到新编译器已存在,就不会再去官方网站下载(从官网下载速度极慢)。
以gcc-arm-none-eabi-4_9-2015q3-20150921-linux.tar.bz2为例,将其放至/opt目录下,并执行:
cd /opt
sudo tar xvf gcc-arm-none-eabi-4_9-2015q3-20150921-linux.tar.bz2
sudo rm gcc-arm-none-eabi-4_9-2015q3-20150921-linux.tar.bz2
修改~/.profile,在最下面找到PATH变量,将gcc-arm-none-eabi-6-2017-q2-update改为gcc-arm-none-eabi-4_9-2015q3。
重启终端(wsl)或者重新登录(ubuntu)以重新加载~/.profile,查看PATH变量中,看其中的编译器是否已改为老编译器。
新编译器是64位的,老的是32位。默认情况下,64位ubuntu不支持运行32位程序,会报如下错误:
$ arm-none-eabi-gcc
-bash: /opt/gcc-arm-none-eabi-4_9-2015q3/bin/arm-none-eabi-gcc: cannot execute binary file: Exec format error
若是跑ubuntu,则执行如下操作即可:
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install libc6:i386 libncurses5:i386 libstdc++6:i386
若是跑WSL(据说WSL2是没问题的,与ubuntu的操作一致),由于其内核就不支持32位程序,只能曲线救国,使用qemu来运行编译器:
sudo apt install qemu-user-static
sudo update-binfmts --install i386 /usr/bin/qemu-i386-static --magic '\x7fELF\x01\x01\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x03\x00\x01\x00\x00\x00' --mask '\xff\xff\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xf8\xff\xff\xff\xff\xff\xff\xff'
sudo service binfmt-support start
使用qemu的缺点是,编译速度比虚拟机还慢。。。。。。也许确实该用WSL2,笔者这次算是跳坑里了。
现在就可以运行老编译器了,no input files是因为我们没传源码文件路径,这个不用管。
$ arm-none-eabi-gcc
arm-none-eabi-gcc: fatal error: no input files
compilation terminated.
如果关闭了所有WSL窗口,貌似WSL就关机了。再次启动WSL时,需要再次执行:
sudo service binfmt-support start
之前的编译命令第一步如下,这是在配置硬件类型。
./waf configure --board Pixhawk1
笔者在编译3.5.7版本时,选择的是px4-v2。其实Pixhawk1和px4-v2对应的都是第1代Pixhawk,是一样的硬件,不同的是底层的软件平台。
笔者的编译命令:
./waf configure --board px4-v2
./waf copter
本节原来是放在此处写的,结果发现内容较多,导致篇幅太大,故后续单独出一篇文章:Ardupilot移植经验分享(1)–加速下载ardupilot工程。
多年前,一个学弟研究Ardupilot的方法让我印象深刻,他将整个工程代码打印在纸上以进行阅读。
那时使用的是Ardunio版飞控,apm2.5,用Ardunio官方IDE来编译固件。IDE长这个样子:
这个IDE的代码浏览功能非常简陋,连函数跳转功能都没有。用它来学习庞大的Ardupilot代码简直是恶梦。对于习惯了Keil和IAR的同学来说,可能并不了解其他好用的代码浏览工具,这就是那个学弟打印出来看的原因。
其实Ardupilot开发团队肯定也不是用Ardunio IDE来开发,顶多是当成编译工具,肯定各有各自熟悉的开发利器,Editors and IDEs推荐了一些IDE。
笔者使用Eclipse阅读ardupilot源码,之后移植时用的是TI公司的Code Composer Studio,它也是基于Eclipse开发的。如果你并不打算使用Eclipse,“创建Eclipse工程”一节也值得一看,笔者将展示如何创建出与编译系统匹配的IDE工程,此方法是通用的,并不仅限于Eclipse。
笔者之所以钟爱Eclipse,是因为它的代码编译和浏览功能很强大,可以极大的提高开发效率,也有助于高效地阅读一个全新的并且庞大的工程。
简单介绍几个功能。
多种方法触发:
当前光标在current_loc上(截屏捕捉到光标),所有的current_loc都高亮显示了。此功能有助于阅读复杂流程时关注某个变量或函数的访问情况。
要想知道上图中的read_inertia函数被谁调用了。
结果如下,其被fast_loop调用了。
这就完了吗?当然不是,点击fast_loop左边的小三角,可查看fast_loop被谁调用了。此过程可反复进行,直到根节点。
最上层的是Ardupilot_main,找程序入口是不是超简单。
可使用大纲视图在当前文件中进行导航,跳转到某个函数或变量。不过还有更快的方法,如果你记得目标符号的名称,或者记得一部分,都可以使用弹出式大纲(Ctrl+O)快速导航。
使用Ctrl+Shift+T可在整个项目空间中导航。
想快速查找文件?没问题,Ctrl+Shift+R
如果对某个函数或者变量的名称不满意,但是它被多处引用了。没关系,Shift+Alt+R就触发重构功能,只需要修改一处,其他地方自动更新。
刚才在介绍功能时,有的仅给出了快捷键的触发方式。不必担心,所有的功能都有相应的菜单。比如说各种导航功能,都可以通过右键菜单或者菜单栏中的“导航”找到。
笔者还是以3.5.7版本为便进行讲解,方法是通用的。
选择IDE是一个问题,如何使用IDE来浏览ardupilot代码是另一个问题。Ardupilot工程不像大家平时用的Keil工程。如果你拿到一个可以编译的Keil工程,那么其所用到的代码自然就在工程配置之中。而Ardupilot使用Waf来编译,当我们选择了一个IDE后,需要自己来创建对应的工程。
笔者分享自己创建Eclipse工程的方法,从Waf提供的编译信息中提取相关信息以构建Eclipse工程,而且这是由代码自动完成。
为了方便说明,再次将ardupilot框架图放出来:
笔者要阅读的是中间的部分,即FlightCode,包含:
.
├── ArduCopter
└── libraries
├──AP_HAL
├──AP_HAL_PX4
└──others
我们的目标是创建一个工程,并将这些代码包含在工程的文件列表之类。ArduCopter和libraries目录中的所有文件并不都是需要的,有的并没有参与编译,所以我们需要知道哪些文件参与了编译。不仅如此,还需要获取到编译时用到的:
如下函数的行为受FRAME_CONFIG(机架类型配置)所控制,FRAME_CONFIG选择某一分支的代码参与编译。Eclipse有一项非常好用的功能,那就是如下图所示,其将宏未选中的代码灰化,使得用户可专注于选中的代码。
当然,这个前提是,得在Eclipse工程中配置相关的宏。
应用层代码(尤其是AP_HAL_PX4)总是会调用底层代码的,比如下面的应用层代码,调用了底层的hrt_call_after函数,图中还有一个子窗口,显示出了该函数的声明和注释。
如果再深入一下,比如查看hrt_abstime:
虽然笔者并不需要了解底层的具体实现,但是像刚才一样了解底层接口是有必要的。若想在IDE中实现上述的查看,就必须让IDE能找到相关的头文件。应用层代码通过
显然drivers目录并不在应用层代码的根目录,
从上节我们明确了目标,需要如下信息:
不仅我们需要这些信息,编译器更需要。ardupilot工程使用Waf来管理构建任务,所以,这些信息都在Waf的相关脚本文件之中,由其在调用编译器时进行传递。
不过笔者不了解Waf,也仅仅是在ardupilot中使用Waf,所以并不想花费额外的时间去深入了解它。笔者有一条以不变应万变的捷径,那就是从编译指令中提取。
gcc -I. -Ilibraries -DHELLO library.cpp -c -o library.o
gcc -I. -Ilibraries -DHELLO main.cpp -c -o main.o
gcc library.o main.o -o hello_world
上面是我随便写2条编译命令和1条链接命令,对于单片机开发者来说,若无linux开发经验,可能不太熟悉这些指令。笔者简单介绍下几个参数的意义:
ardupilot的指令远比上面要复杂,参数比上面要多,但是格式是一样的。只要我们想办法得到ardupilot的编译指令,那就可获得我们所需要的信息。
笔者在创建linux内核,RT-Thread之类的复杂系统的IDE工程时,是通过在命令中加入verbose之类的参数,从而让构建系统打印出详细的编译日志,从而提取相关信息。下图是RT-Thread的编译:
而ardupilot直接提供了相关文件,这是笔者在默认级别的日志中发现的:
compile_commands.json是用json格式存储的ardupilot应用层代码的编译指令,下图是其中一条,用于编译libraries/AP_NavEKF2/AP_NavEKF2_MagFusion.cpp。
浓缩一下:
{
"directory": "/mnt/g/ardupilot/src/ardupilot/build/px4-v2",
"file": "../../libraries/AP_NavEKF2/AP_NavEKF2_MagFusion.cpp",
"command": "此处省略1千字"
}
其实command和这条编译main.cpp的指令是一样的,只不过它有更多的宏定义和头文件路径参数,另外,还有些别的用于控制编译行为的参数。
gcc -I. -Ilibraries -DHELLO main.cpp -c -o main.o
笔者来拆分下这条超长的command指令给大家看。
编译器
/opt/gcc-arm-none-eabi-4_9-2015q3/bin/arm-none-eabi-g++
宏定义
-DCONFIG_ARCH_BOARD_PX4FMU_V2
-D__STDC_FORMAT_MACROS
-D__PX4_NUTTX
-D__DF_NUTTX
-DCONFIG_WCHAR_BUILTIN
-D__CUSTOM_FILE_IO__
-DCONFIG_HAL_BOARD=HAL_BOARD_PX4
-DHAVE_OCLOEXEC=0
-DHAVE_STD_NULLPTR_T=0
-DUAVCAN_CPP_VERSION=UAVCAN_CPP03
-DUAVCAN_NO_ASSERTIONS=1
-DUAVCAN_NULLPTR=nullptr
'-DSKETCHBOOK=\"/mnt/g/ardupilot/src/ardupilot\"'
头文件路径
-Include visibility.h
-Include ap_config.h
-Ilibraries
-Ilibraries/GCS_MAVLink
-Imodules/uavcan/libuavcan/include/dsdlc_generated
-I.
-I../../libraries
-I../../libraries/AP_Common/missing
-I../../modules/uavcan/libuavcan/include
-I../../modules/PX4Firmware/src
-Imodules/PX4Firmware
-Imodules/PX4Firmware/src
-I../../modules/PX4Firmware/src/modules
-I../../modules/PX4Firmware/src/include
-I../../modules/PX4Firmware/src/lib
-I../../modules/PX4Firmware/src/platforms
-I../../modules/PX4Firmware/src/drivers/boards/px4fmu-v2
-Imodules/PX4Firmware/src/modules/px4_messages
-Imodules/PX4Firmware/src/modules
-I../../modules/PX4Firmware/mavlink/include/mavlink
-I../../modules/PX4Firmware/src/lib/DriverFramework/framework/include
-Isrc/lib/matrix
-I../../src/lib/matrix
-Imodules/PX4Firmware/px4fmu-v2/NuttX/nuttx-export/include
-Imodules/PX4Firmware/px4fmu-v2/NuttX/nuttx-export/include/cxx
-Imodules/PX4Firmware/px4fmu-v2/NuttX/nuttx-export/arch/chip
-Imodules/PX4Firmware/px4fmu-v2/NuttX/nuttx-export/arch/common
其他的编译参数,这在移植时可能用到,不过目前,我们无需关心。
-g -fno-exceptions -fno-rtti -std=gnu++0x -fno-threadsafe-statics
-Wall -Werror -Wextra -Wno-sign-compare -Wfloat-equal -Wpointer-arith
-Wmissing-declarations -Wno-unused-parameter -Werror=format-security
-Werror=array-bounds -Wfatal-errors -Werror=unused-variable
-Werror=reorder -Werror=uninitialized -Werror=init-self -Wframe-larger-than=1024
-Werror=unused-but-set-variable -Wformat=1 -Wdouble-promotion -Werror=double-promotion
-Wno-missing-field-initializers -Os -fno-strict-aliasing -fomit-frame-pointer
-funsafe-math-optimizations -ffunction-sections -fdata-sections
-fno-strength-reduce -fno-builtin-printf -fvisibility=hidden
-mcpu=cortex-m4 -mthumb -march=armv7e-m -mfpu=fpv4-sp-d16 -mfloat-abi=hard
-nodefaultlibs -nostdlib -std=gnu++11 -fdata-sections -ffunction-sections -fno-exceptions
-fsigned-char -Wall -Wextra -Wformat -Wshadow -Wpointer-arith -Wcast-align -Wundef
-Wno-unused-parameter -Wno-missing-field-initializers -Wno-reorder -Wno-redundant-decls
-Wno-unknown-pragmas -Werror=format-security -Werror=array-bounds -Werror=uninitialized
-Werror=init-self -Werror=switch -Wfatal-errors -Werror=unused-but-set-variable -Wno-error=cast-align
-Wlogical-op -Wframe-larger-than=1300 -fsingle-precision-constant -Wno-error=double-promotion
-Wno-error=missing-declarations -Wno-error=float-equal -Wno-error=undef -Wno-error=cpp
代码文件
../../libraries/AP_NavEKF2/AP_NavEKF2_MagFusion.cpp
编译成.o
-c -o/mnt/g/ardupilot/src/ardupilot/build/px4-v2/libraries/AP_NavEKF2/AP_NavEKF2_MagFusion.cpp.0.o
拆分完后再看,是不是非常清晰了。宏定义,头文件路径,代码路径,都有了。当然,这只是一条编译指令。我们需要解析compile_commands.json中的所有几百条指令。
补充个注意事项,前面在说编译命令时,使用的是:
./waf copter
其不仅编译出多旋翼(四轴)的固件,还会编译直升机的。这两个固件有一个不同的宏定义,即上文提到的FRAME_CONFIG。
~/ardupilot/build/px4-v2/bin$ tree
.
├── arducopter
├── arducopter-heli
├── arducopter-heli.px4
└── arducopter.px4
笔者试的情况是,其先编译四轴,再编译直升机,最终的compile_commands.json中留的是直升机的编译指令。如果你需要四轴的,使用下面这条指令,仅编译四轴:
./waf --targets bin/arducopter
由于文件众多,一个个去手工解析是不现实的。我们可以写个程序来自动提取这些信息,笔者使用Java来解析:
解析命令时,使用正则表达式将非常方便,比如下面的正则可将编译命令拆分成几个部分,以便进一步解析。
/**
* used to parse compile command
*
* for example(ignore some parameter):
* /opt/gcc-arm-none-eabi-4_9-2015q3/bin/arm-none-eabi-g++ -DCONFIG_ARCH_BOARD_PX4FMU_V2
* -include visibility.h -mcpu=cortex-m4 -mthumb -march=armv7e-m
* -I../../modules/PX4Firmware/src/lib/DriverFramework/framework/include
* ../../libraries/AP_NavEKF2/AP_NavEKF2_MagFusion.cpp
* -c -o/home/dongbowen/work/ardupilot/ardupilot_gitee/ardupilot/build/px4-v2/libraries/AP_NavEKF2/AP_NavEKF2_MagFusion.cpp.0.o
*
* group(1) is the command which don't include "-c -o..."
* group(2) is the compiler: /opt/gcc-arm-none-eabi-4_9-2015q3/bin/arm-none-
* group(3) is the parameters
*/
private static final Pattern COMPILE_CMD_PATTERN =
Pattern.compile("^\\s*((\\S+-)[^-\\s]+\\s+(.*))\\s+-c\\s+-o");
其实这几百条指令的宏定义和头文件路径基本是一致,笔者解析时得出他们的并集,并且打印出每条重复的次数(在方括号之中)。
头文件路径如下,大部分的重复次数是429,这正是代码文件的个数,即它们在每条编译指令中都有。不过也有一些重复次数小于429的,最少的次数是81。这说明了自动化解析的重要性。如果仅手工解析第一条编译指令,那就会漏掉一些在后续编译指令中才出现的条目。
[0429] /mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src
[0348] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/src/lib/matrix
[0429] /mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src/lib
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/PX4Firmware/src/modules/px4_messages
[0429] /mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src/drivers/boards/px4fmu-v2
[0081] /mnt/g/ardupilot/src/ardupilot/ArduCopter/src/lib/matrix
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/PX4Firmware/px4fmu-v2/NuttX/nuttx-export/include/cxx
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/PX4Firmware/px4fmu-v2/NuttX/nuttx-export/include
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/libraries
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/PX4Firmware/src/modules
[0429] /mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/mavlink/include/mavlink
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/PX4Firmware/px4fmu-v2/NuttX/nuttx-export/arch/common
[0429] /mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src/lib/DriverFramework/framework/include
[0429] /mnt/g/ardupilot/src/ardupilot/modules/uavcan/libuavcan/include
[0348] /mnt/g/ardupilot/src/ardupilot/src/lib/matrix
[0429] /mnt/g/ardupilot/src/ardupilot/libraries/AP_Common/missing
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/.
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/uavcan/libuavcan/include/dsdlc_generated
[0429] /mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src/platforms
[0429] /mnt/g/ardupilot/src/ardupilot/libraries
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/PX4Firmware
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/PX4Firmware/px4fmu-v2/NuttX/nuttx-export/arch/chip
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/libraries/GCS_MAVLink
[0429] /mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src/modules
[0429] /mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src/include
[0081] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/ArduCopter/src/lib/matrix
[0429] /mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/PX4Firmware/src
宏定义的情况也一样,有少部分宏的重复次数小于429。
[0081] APM_BUILD_DIRECTORY=APM_BUILD_ArduCopter
[0429] __PX4_NUTTX
[0429] __CUSTOM_FILE_IO__
[0429] UAVCAN_NO_ASSERTIONS=1
[0429] __STDC_FORMAT_MACROS
[0429] UAVCAN_NULLPTR=nullptr
[0081] SKETCH="ArduCopter"
[0429] CONFIG_WCHAR_BUILTIN
[0081] SKETCHNAME="ArduCopter"
[0429] HAVE_OCLOEXEC=0
[0429] __DF_NUTTX
[0429] UAVCAN_CPP_VERSION=UAVCAN_CPP03
[0429] CONFIG_ARCH_BOARD_PX4FMU_V2
[0429] HAVE_STD_NULLPTR_T=0
[0065] FRAME_CONFIG=HELI_FRAME
[0429] SKETCHBOOK="/mnt/g/ardupilot/src/ardupilot"
[0429] CONFIG_HAL_BOARD=HAL_BOARD_PX4
由于大部分都是重复的,所以它们的并集并没有多大。由程序解析出宏定义和头文件路径后,手工配置到Eclipse工程中是可行的。不过,别忘记,还有那几百个文件呢,如果手工把它们一个个加进工程,那是很费事儿的,搞不好还会遗漏些文件。
笔者不仅用程序进行解析,还使用程序来创建Eclipse工程,这就是所谓的自动化创建工程。
Eclipse的工程文件是以XML文本的形式存储,Keil也是如此。只要我们了解下其文本的格式,自动创建工程并非难事。
我们先看下Eclipse工程文件中宏定义、头文件路径和代码路径中格式。Eclipse工程目录下有两个文件,.project和.cproject。
<listOptionValue builtIn="false" value=""__PX4_NUTTX""/>
头文件路径
其中/mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src对应的是
<listOptionValue builtIn="false" value=""/mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src""/>
代码路径
宏定义和头文件路径的格式非常简单,代码路径涉及到父目录的问题稍微复杂些。不过,很明显,写程序去追加条目是可行的。
笔者原本打算详细介绍自动化创建工程的步骤,不过突然发现这并不是一件简单的事情。需要准备更多的素材,需要更大的篇幅才能讲清楚讲透。因此就不在这里展开了,毕竟本文的宗旨是讲方法。如果大家感兴趣的话,笔者后续会专门出一篇文章来讲自动化创建工程。当然,ardupilot移植分享系列文件也不是仅此一篇,笔者规划的是至少3遍文章,第2篇分析代码,第3篇讲移植的细节。在这里向大家求一波三连支持,大家的认可是我创作的动力。
对了,项目源码,请观众笔者的公众号,回复:ardupilot-ti,即可获取。
别急,方法还未讲完。
除了宏定义、头文件路径和代码路径,工程中还有许多其他的内容。我们并不需要去了解这所有的内容,也不需要凭空生成工程文件。笔者使得的方法是,先创建一个模版工程,具体的步骤是:
下图是.cproject文件中的三个标记,分别对应宏、头文件路径、额外的头文件。
这是.project中的标记,用于添加代码文件。
使用特殊的字符串标记是一个取巧的方法,不过它有一个缺点:若后续再用Eclipse打开模版工程做修改的话,因为Eclipse并不认识这个标记,所以它将会消失。
这次真结束了,源码分析和移植,有待下回分解。