Ardupilot移植经验分享(1)

2023-05-16

目录

  • 前言
  • 背景
  • 为什么写这篇文章
  • 移植Ardupilot的方法有两种
    • 底层适配
    • 提取应用层代码
    • 两种方法对比
  • 准备阅读源码
    • 阅读官方开发者wiki
    • 选择编译平台
    • 下载编译源代码
      • 切换版本的正确方式
      • 编译老版本
        • 手动下载编译器
        • 修改PATH配置
        • 配置ubuntu以支持运行32位程序
        • 老版本的编译命令
      • 下载太慢或者失败
    • 建立源码阅读环境
      • 使用Eclipse
        • 全貌
        • 跳转到定义
        • 选中符号高亮显示
        • 显示函数调用栈
        • 快速导航
        • 快速重命名
        • 记不住快捷键?
      • 创建Eclipse工程
        • 需要获取哪些信息
        • 如何获取工程配置信息
        • 自动化创建工程

前言

笔者写这篇文章的重点是分享移植方法。Ardupilot代码在不断的演化更新,一篇非常细致的讲解移植步骤的文章总会过时,只有掌握了方法才能随机应变。必要的移植步骤是有的,不过在此之前,笔者会讲解一些辅助性的内容,比如搭建高效阅读源码的环境,移植Ardupilot会遇到哪些困难。

如果你对Ardupilot很好奇,想深入研究一些东西,但是又畏惧其复杂庞大的代码工程,那么笔者强烈推荐你耐心阅读本文,相信一定会对你有所帮助。

背景

两年前接了个小项目,使用TI公司的单片机做飞控。这是学校的实验项目,主要是演示、推广之类的用途,要求不高。接触Ardupilot数年矣,一直想搞点大动作,正好借着这次机会,来一次真正的移植。

硬件平台为TM4C1294XL LaunchPad,软件平台为RT-Thread操作系统,基于Copter-3.5.7版本进行移植。
图片来源:TI使用手机
在这里插入图片描述
先放个截图,源码获取方式在文章结尾。
在这里插入图片描述

为什么写这篇文章

在10年前,四轴飞行器还是个冷门的东西,玩的人并不多,而接触过飞控开发的人就更少了。2013年全国大学生电子设计竞赛首次引入飞控类的赛题,越来越多的学生接触了飞控代码。许多开源飞控如雨后春笋般诞生,飞控开发的难度和门槛逐渐降低。

然而,历史悠久的Ardupilot的开发门槛却并未降低,个人感觉反而更高了。
图片来源:ardupilot.org
上图展示了Ardupilot的发展历史,2009年第一版硬件使用的是Ardunio Mega单片机,2013年使用了性能更高资源更丰富的Pixhawk(168 Mhz/256 KB RAM/2 MB Flash)。时至今日,Ardupilot支持众多的开源闭源硬件。
图片来源:ardupilot.org
Ardupilot能支持众多硬件平台,归功于其优秀的代码框架。业务、算法与驱动相分离,层次分明,从而具有强大的可扩展性。然而优秀的代码框架并不意味学习成本低,相反,需要花费一定的时间去了解它的层次和脉络。

学习成本高,导致Ardupilot令人望而生畏。笔者在决定移植Ardupilot之前,对其已有一定的了解,数年前还做过一次小移植(后面会细说)。纵使如此,也并无百分百的把握,是强烈的兴趣驱使我接下了这个项目。断断续续做了好几个月,经历了许多困难,中途都后悔了哈哈。

虽然困难,但Ardupilot是值得研究的。其包含的诸多功能,不仅仅可以用于飞行器。实际上,Ardupilot项目也不只有飞行器,其支持四种载具:

  • Copter(旋翼机)
  • Plane(固定翼)
  • Rover(小车)
  • Sub(潜艇)

随便挑个功能,下图是Ardupilot支持的无源导航(即无GPS信号时)方式,有3D摄像头,光流,UWB定位,外置摄像头捕捉等等。
图片来源:ardupilot.org
因此,笔者决定分享自己的移植经验,让更多对Ardupilot感兴趣的同学有所参考。

网上关于Ardupilot的代码分析和移植的文章有很多,笔者这篇文章的重点是分享方法。笔者移植的是Copter-3.5.7,而现在最新版本是Copter-4.0.3,代码肯定发生了很多变化。细节是死的,方法是活的。掌握了方法,才能灵活应变。

分析Ardupilot代码框架也是必不可少的,不过笔者不会讲解每一处需要修改的代码,比如关于外设驱动移植,会挑一些有代表性的来讲解。除此之外,笔者会详细地分享一些有助于成功移植的方法,比如如何加速源码的下载,命令行编译,搭建高效阅读源码的环境。

移植Ardupilot的方法有两种

Ardupilot的代码框架
图片来源:ardupilot.org
ArduPilot代码顶层目录
在这里插入图片描述

Ardupilot代码分为5个部分:

  • 载具代码:APMrover2, ArduCopter, AntennaTracker, ArduPlane, ArduSub。对于飞行器项目来说就是飞控代码,是业务层的代码,比如飞行器初始化,飞行模式控制。
  • 共享库:libraries中除以AP_HAL开头的所有子目录。包含传感器驱动,姿态位置估算与控制。
  • 硬件抽象层(HAL):libraries中以AP_HAL开头子目录,如AP_HAL, AP_HAL_PX4, AP_HAL_Linux。
  • 工具目录:Tools。
  • 外部支持代码:modules。具体硬件平台的代码,如操作系统,中间件。

要重点讲的是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目录中的文件如下:
在这里插入图片描述
分为如下几类:

  • 纲领型文件:AP_HAL.h, HAL.h, HAL.cpp,定义命名空间和HAL类,被其他文件所引用。
  • 通信类驱动:CAN.h, Device.h, UARTDriver.h, SPIDevice.h,等等,都是些通信类外设的驱动。
  • RC类驱动:RCInput.h, RCOutput.h,负责接收遥控信号,输出电机信号。
  • 操作系统接口:Semaphores.h, system.h,提供操作系统相关的接口,如线程,信号量,睡眠等功能。
  • 其他驱动:AnalogIn.h, GPIO.h。

下图最左边的分支,就是笔者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分为如下几个部分:

  • read_AHRS:从传感器读取角速度与加速度,使用DCM(方向余弦矩阵)计算姿态角。
  • update_yaw_mode和update_roll_pitch_mode:根据飞行模式,当前姿态,目标姿态等来计算目标角速度(大地坐标系)。
  • update_rate_contoller_targets:目标角速度转换(大地坐标系–>机体坐标系)。
  • run_rate_controllers:根据目标角速度计算并输出电机信号。

可以看出,这实现的是非常基础的飞行功能。其实笔者这里讲述几个函数的作用,并不是真想展开来讲飞控算法,而仅仅是想让大家体会一下:这种移植需要对飞控算法进行深入的了解

两种方法对比

底层适配需要深入了解HAL框架,了解每个HAL接口的功能并且实现它。这种方法,基本不用在应用层做改动,好处是:不需要深入研究应用层的代码,比如各种飞行模式,姿态解算与控制算法。再说直白点,就算你对飞控算法一无所知,只要你有扎实的嵌入式软件功底,就能完成移植。什么,软件功底也不是很厚实?不用担心,这正是我写这篇文章的目的啊。

相反,提取应用层代码则需要对飞控算法有更多的了解,并且需要深入分析Ardupilot的应用层代码,提取出你所需要的模块。好处也是与底层适配相反的,就是不需要了解HAL啦。你可以随意的修改应用层代码,包括调用驱动的地方。

应该选择哪一种,要看应用场景。

如果你只是想使用Ardupilot的一个小模块,比如姿态解算。Ardupilot将陀螺仪,加速度计,磁力计,GPS的数据进行融合,从而计算出稳定准确的姿态数据。把这些代码提取出来,应用到其他的项目中,是不错的想法。

如果你是想做一款飞行器,并且想使用到Ardupilot的诸多功能,如各种各样的飞行模式(定点悬停,路径规划),使用UWB进行无源导航,物体追踪,那就不宜提取应用层代码,而是做底层适配。否则,你可能要提取太多的代码,做太多的改动。

准备阅读源码

在开始移植之前,有两个必要的步骤:

  1. 熟练使用一款Ardupilot飞控,比如Pixhawk。
  2. 搭建源码的阅读、编译、调试环境。

熟练的意思是,会配置、校准飞控,装到四轴上,能操控它进行基础的飞行。

虽然我们的最终目标是将飞控代码移植到新的平台上,不过搭建参考平台的源码环境非常重要。得先在这个参考平台上把源码读懂,当遇到疑问时可稍微修改下源码并调试验证。笔者的参考平台是第一代Pixhawk,如下图:

图片来源:ardupilot.org

阅读官方开发者wiki

官方开发者wiki是最应该仔细阅读的资料,建议先阅读下面两个章节,其他的章节可根据需求选读。Building这章,只看自己使用的平台即可,比如Linux/Ubuntu。

  • Building the code
  • Learning the code

选择编译平台

ardupilot支持以下平台,详见Building the code:

  • Linux/Ubuntu
  • Windows(Cygwin或WSL)
  • MacOSX

笔者当初移植时,是在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

需要替换成老的编译器,有如下几步:

  1. 手动下载编译器。
  2. 执行Tools/environment_install/install-prereqs-ubuntu.sh。
  3. 修改PATH配置:将PATH变量中的编译器路径改为老版本编译器的路径。
  4. 配置ubuntu以支持运行32位程序。
  5. 切换到目标分支,编译

手动下载编译器

下载老版本编译器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

修改PATH配置

修改~/.profile,在最下面找到PATH变量,将gcc-arm-none-eabi-6-2017-q2-update改为gcc-arm-none-eabi-4_9-2015q3。
在这里插入图片描述
重启终端(wsl)或者重新登录(ubuntu)以重新加载~/.profile,查看PATH变量中,看其中的编译器是否已改为老编译器。
在这里插入图片描述

配置ubuntu以支持运行32位程序

新编译器是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,是一样的硬件,不同的是底层的软件平台。

  • px4-v2:基于PX4Firmware和Nuttx,这不是Ardupilot开发的,而是Pixhawk硬件厂商开发。
  • Pixhawk1:Ardupilot团队使用ChibiOS重新开发了底层软件。

笔者的编译命令:

./waf configure --board px4-v2
./waf copter 

下载太慢或者失败

本节原来是放在此处写的,结果发现内容较多,导致篇幅太大,故后续单独出一篇文章:Ardupilot移植经验分享(1)–加速下载ardupilot工程

建立源码阅读环境

多年前,一个学弟研究Ardupilot的方法让我印象深刻,他将整个工程代码打印在纸上以进行阅读。

那时使用的是Ardunio版飞控,apm2.5,用Ardunio官方IDE来编译固件。IDE长这个样子:
图片来源:ardupilot.org
这个IDE的代码浏览功能非常简陋,连函数跳转功能都没有。用它来学习庞大的Ardupilot代码简直是恶梦。对于习惯了Keil和IAR的同学来说,可能并不了解其他好用的代码浏览工具,这就是那个学弟打印出来看的原因。

其实Ardupilot开发团队肯定也不是用Ardunio IDE来开发,顶多是当成编译工具,肯定各有各自熟悉的开发利器,Editors and IDEs推荐了一些IDE。

笔者使用Eclipse阅读ardupilot源码,之后移植时用的是TI公司的Code Composer Studio,它也是基于Eclipse开发的。如果你并不打算使用Eclipse,“创建Eclipse工程”一节也值得一看,笔者将展示如何创建出与编译系统匹配的IDE工程,此方法是通用的,并不仅限于Eclipse

使用Eclipse

笔者之所以钟爱Eclipse,是因为它的代码编译和浏览功能很强大,可以极大的提高开发效率,也有助于高效地阅读一个全新的并且庞大的工程。

简单介绍几个功能。

全貌

  • 左侧是项目浏览器视图,主要提供文件列表功能。
  • 右侧是大纲视图,显示当前文件中的符号表。
  • 中间是代码编辑器视图。

在这里插入图片描述

跳转到定义

多种方法触发:

  • Ctrl+鼠标左击
  • F3
  • 通过右键菜单

必备功能啊,不太好

选中符号高亮显示

当前光标在current_loc上(截屏捕捉到光标),所有的current_loc都高亮显示了。此功能有助于阅读复杂流程时关注某个变量或函数的访问情况。
在这里插入图片描述

显示函数调用栈

要想知道上图中的read_inertia函数被谁调用了。

  • 右键菜单
  • Ctrl+Alt+H

在这里插入图片描述
结果如下,其被fast_loop调用了。
在这里插入图片描述
这就完了吗?当然不是,点击fast_loop左边的小三角,可查看fast_loop被谁调用了。此过程可反复进行,直到根节点。
最上层的是Ardupilot_main,找程序入口是不是超简单。
在这里插入图片描述

快速导航

可使用大纲视图在当前文件中进行导航,跳转到某个函数或变量。不过还有更快的方法,如果你记得目标符号的名称,或者记得一部分,都可以使用弹出式大纲(Ctrl+O)快速导航。
在这里插入图片描述
使用Ctrl+Shift+T可在整个项目空间中导航。
在这里插入图片描述
想快速查找文件?没问题,Ctrl+Shift+R
在这里插入图片描述

快速重命名

如果对某个函数或者变量的名称不满意,但是它被多处引用了。没关系,Shift+Alt+R就触发重构功能,只需要修改一处,其他地方自动更新。
在这里插入图片描述

记不住快捷键?

刚才在介绍功能时,有的仅给出了快捷键的触发方式。不必担心,所有的功能都有相应的菜单。比如说各种导航功能,都可以通过右键菜单或者菜单栏中的“导航”找到。

创建Eclipse工程

笔者还是以3.5.7版本为便进行讲解,方法是通用的

选择IDE是一个问题,如何使用IDE来浏览ardupilot代码是另一个问题。Ardupilot工程不像大家平时用的Keil工程。如果你拿到一个可以编译的Keil工程,那么其所用到的代码自然就在工程配置之中。而Ardupilot使用Waf来编译,当我们选择了一个IDE后,需要自己来创建对应的工程。

笔者分享自己创建Eclipse工程的方法,从Waf提供的编译信息中提取相关信息以构建Eclipse工程,而且这是由代码自动完成。

为了方便说明,再次将ardupilot框架图放出来:
图片来源:ardupilot.org
笔者要阅读的是中间的部分,即FlightCode,包含:

  • Vehicle specific flight code(对应Arducopter)
  • Shared Libraries(对应AP_HAL,AP_HAL_PX4)
  • Hardware Abstraction Layer(对应libraries中的其他文件)
.
├── 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/drv_hrt.h>来引用头文件。
在这里插入图片描述
显然drivers目录并不在应用层代码的根目录,<drivers/drv_hrt.h>是一个相对路径,因此需要配置路径前缀
在这里插入图片描述

如何获取工程配置信息

从上节我们明确了目标,需要如下信息:

  • 代码文件列表
  • 宏定义
  • 头文件路径

不仅我们需要这些信息,编译器更需要。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开发经验,可能不太熟悉这些指令。笔者简单介绍下几个参数的意义:

  • -I添加头文件路径,这个路径+include中的路径构成完整的路径。比如前面那个drv_hrt.h的路径是由/mnt/g/ardupilot/src/ardupilot/build/px4-v2/modules/PX4Firmware/src + rivers/drv_hrt.h。.表示当前路径。
  • -D添加宏定义。

ardupilot的指令远比上面要复杂,参数比上面要多,但是格式是一样的。只要我们想办法得到ardupilot的编译指令,那就可获得我们所需要的信息。

笔者在创建linux内核,RT-Thread之类的复杂系统的IDE工程时,是通过在命令中加入verbose之类的参数,从而让构建系统打印出详细的编译日志,从而提取相关信息。下图是RT-Thread的编译:
 RT-Thtread编译图

而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千字"
}
  • directory:编译时的工作目录
  • file:代码文件路径,是相对于directory的路径。
  • command:编译指令,包含了头文件路径和宏定义的信息。

其实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
  • 四轴:FRAME_CONFIG=MULTICOPTER_FRAME
  • 直升机:FRAME_CONFIG=HELI_FRAME

笔者试的情况是,其先编译四轴,再编译直升机,最终的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。

  • .cproject存储配置,包含了宏定义和头文件路径。
  • .project存储代码文件路径。

宏定义
在这里插入图片描述
其中__PX4_NUTTX对应的是

<listOptionValue builtIn="false" value="&quot;__PX4_NUTTX&quot;"/>

头文件路径
在这里插入图片描述
其中/mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src对应的是

<listOptionValue builtIn="false" value="&quot;/mnt/g/ardupilot/src/ardupilot/modules/PX4Firmware/src&quot;"/>

代码路径
在这里插入图片描述
宏定义和头文件路径的格式非常简单,代码路径涉及到父目录的问题稍微复杂些。不过,很明显,写程序去追加条目是可行的。

笔者原本打算详细介绍自动化创建工程的步骤,不过突然发现这并不是一件简单的事情。需要准备更多的素材,需要更大的篇幅才能讲清楚讲透。因此就不在这里展开了,毕竟本文的宗旨是讲方法。如果大家感兴趣的话,笔者后续会专门出一篇文章来讲自动化创建工程。当然,ardupilot移植分享系列文件也不是仅此一篇,笔者规划的是至少3遍文章,第2篇分析代码,第3篇讲移植的细节。在这里向大家求一波三连支持,大家的认可是我创作的动力

对了,项目源码,请观众笔者的公众号,回复:ardupilot-ti,即可获取。
在这里插入图片描述

别急,方法还未讲完。

除了宏定义、头文件路径和代码路径,工程中还有许多其他的内容。我们并不需要去了解这所有的内容,也不需要凭空生成工程文件。笔者使得的方法是,先创建一个模版工程,具体的步骤是:

  1. 使用Eclipse创建空工程。
  2. 使用文本编辑器打开工程文件,在宏定义、头文件路径和代码路径的位置上加上特殊的字符串标记,这就是模版工程。
  3. 写程序自动创建工程时,先组装好宏定义、头文件路径和代码路径的内容,然后替换模版工程中对应的字符串标记即可。

下图是.cproject文件中的三个标记,分别对应宏、头文件路径、额外的头文件。
在这里插入图片描述
这是.project中的标记,用于添加代码文件。
在这里插入图片描述
使用特殊的字符串标记是一个取巧的方法,不过它有一个缺点:若后续再用Eclipse打开模版工程做修改的话,因为Eclipse并不认识这个标记,所以它将会消失。

这次真结束了,源码分析和移植,有待下回分解。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Ardupilot移植经验分享(1) 的相关文章

  • 剖析Linux内存中的/proc/meminfo参数

    PROC MEMINFO之谜 proc meminfo是了解Linux系统内存使用状况的主要接口 xff0c 我们最常用的 free vmstat 等命令就是通过它获取数据的 xff0c proc meminfo所包含的信息比 free 等
  • 看完秒懂:Linux DMA mapping机制分析

    说明 xff1a Kernel版本 xff1a 4 14ARM64处理器 xff0c Contex A53 xff0c 双核使用工具 xff1a Source Insight 3 5 xff0c Visio 1 概述 DMA xff08 D
  • linux网络编程-多进程实现TCP并发服务器

    服务端流程步骤 socket函数创建监听套接字lfd bind函数将监听套接字绑定ip和端口 listen函数设置服务器为被动监听状态 xff0c 同时创建一条未完成连接队列 xff08 没走完tcp三次握手流程的连接 xff09 xff0
  • Linux内核中断下半部工作队列(work queue)

    工作队列work queue 工作队列 xff08 work queue xff09 是中断下半部的一种实现机制 xff0c 主要用于耗时任务处理 xff0c 由内核线程代表进程执行 工作队列运行于进程上下文 xff0c 因此允许阻塞 运行
  • 手把手带你部署Ceph集群

    前言 xff1a Ceph作为开源的分布式文件系统 xff0c 可以轻松地将存储容量扩展到PB以上并拥有不错的性能 Ceph提供对象存储 块存储和文件系统三种存储方式 xff0c 如果不想花时间安装ceph xff0c 可以通过ceph d
  • Linux 内核安全增强—— stack canary

    一 背景知识 aarch64的函数栈 1 栈生长方向与push pop操作 栈是一种运算受限的线性表 入栈的一端为栈顶 xff0c 另一端则为栈底 其生长方向和操作顺序理论上没有限定 而在aarch64平台上 栈是向低地址方向增长的 STA
  • Linux下Makefile的简单编写与使用

    Makefile 一个工程文件中的源文件可能有很多 xff0c 并且不同的功能 模块等都放在不同的目录中 xff0c 常规的编译已经不能高效化的处理这样的问题 xff0c 而Makefile就是为解决这一问题而来 Makefile一旦写好
  • STM32 USART 串口DMA收发注意事项

    正常情况这里不介绍 目录 1 低波特率情况 xff0c 接收信号可能会出现干扰 2 波特率300时 xff0c DMA接收无法工作 3 波特率1200时DMA发送 4 具体现象如下 环境 xff1a 主频72M STM32F103C8 注意
  • STM32学习笔记———几种简单传感器的数据读取

    引言 传感器正如计算机的眼睛 从广义上讲 xff0c 传感器就是一种能感知外界信息 xff0c 并将这些信息按照一定规律转换成可用的电信号或其他形式的输出信号的装置 xff0c 达到对信息的存储 xff0c 传输 xff0c 控制的目的 本
  • 基于STM32F103C8T6的USART1串口的中断接收

    一 串口介绍 二 项目所需硬件 1 USB转串口模块 三 项目代码 一 串口介绍 USART Universal Synchronous Asynchronous Receiver Transmitter 通用同步 异步串行接收 发送器 U
  • 第5讲—寄存器编程

    编程方式两种 xff0c 一种是寄存器编程 xff0c 一种是函数库编程 什么是寄存器 stm32芯片 61 ARM内核生产Cortex内核 43 st公司 xff08 在内核基础上 xff09 开发stm32 寄存器是用来地址操作的 寄存
  • C语言运行HTTP代码示例

    include lt iostream gt include lt algorithm gt include lt cstring gt include 34 curl curl h 34 usingnamespace std static
  • 在Linux上实现HTTP请求

    在Linux上实现HTTP请求是一种非常有用的技能 xff0c 有时候我们需要从Web服务器获取数据 xff0c 而不是使用浏览器发起网络调用 HTTP是一种常用的网络协议 xff0c 可以用于在Linux上实现HTTP请求 要在Linux
  • Linux下设置堆栈系统参数

    Linux系统下有几个与堆栈相关的系统参数 xff1a 1 ulimit s xff1a 此参数用于限制进程的堆栈大小 可以使用该命令来查看和更改进程的堆栈大小限制 2 proc sys kernel stack protect xff1a
  • Leetcode每日一题——“消失的数字”

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰又开新专栏了 xff0c 以后会在Leetcode上面进行刷题 xff0c 尽量每天写一道 xff0c 请大家监督我 xff01 xff01 xff01 好啦 xff0c 让
  • Leetcode每日一题——“轮转数组”

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰的内容是轮转数组 xff0c 下面 xff0c 让我们进入轮转数组的世界吧 小雅兰之前其实就已经写过了字符串旋转的问题了 xff1a C语言刷题 xff08 7 xff09
  • Python一行命令搭建HTTP服务器并外网访问【内网穿透】

    文章目录 1 前言2 本地http服务器搭建2 1 Python的安装和设置2 2 Python服务器设置和测试 3 cpolar的安装和注册3 1 Cpolar云端设置3 2 Cpolar本地设置 4 公网访问测试5 结语 转载自远程内网
  • 顺序表(更新版)——“数据结构与算法”

    各位CSDN的uu们你们好呀 xff0c 今天小雅兰又来更新新专栏啦 xff0c 其实之前我就已经写过了顺序表的内容 xff0c 只是之前的内容不是最新版的顺序表 xff0c 现在 xff0c 我来更新一下最新版的顺序表 xff0c 下面
  • Leetcode每日一题——“移除元素”

    各位CSDN的uu们你们好呀 xff0c 小雅兰又来啦 xff0c 今天 xff0c 小雅兰的内容是移除元素 xff0c 下面 xff0c 让我们进入Leetcode的世界吧 说明 为什么返回数值是整数 xff0c 但输出的答案是数组呢 请
  • Leetcode每日一题——“回文数”

    各位CSDN的uu们 xff0c 你们好呀 xff0c 今天小雅兰又来刷力扣啦 xff0c 今天的题目是回文数 xff0c 下面 xff0c 让我们进入回文数的世界吧 示例 1 xff1a 输入 xff1a x 61 121 输出 xff1

随机推荐

  • 【社区图书馆】《新程序员005:开源深度指南 & 新金融背后的科技力量》

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰来给大家推荐一本书 xff0c 此书的书名为新程序员005 xff1a 开源深度指南 amp 新金融背后的科技力量 xff0c 为什么小雅兰今天要给大家推荐这样一本书呢 xf
  • Nodejs快速搭建简单的HTTP服务器,并发布公网远程访问

    文章目录 前言1 安装Node js环境2 创建node js服务3 访问node js 服务4 内网穿透4 1 安装配置cpolar内网穿透4 2 创建隧道映射本地端口 5 固定公网地址 转载自内网穿透工具的文章 xff1a 使用Node
  • Leetcode每日一题——“合并两个有序数组”

    各位CSDN的uu们你们好呀 xff0c 又到小雅兰的愉快题解时候啦 xff0c 今天 xff0c 我们的题目内容是合并两个有序数组 xff0c 下面 xff0c 让我们进入合并两个有序数组的世界吧 示例 1 xff1a 输入 xff1a
  • Leetcode每日一题——“链表的中间结点”

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰愉快的刷题内容是链表的中间结点 嘿嘿 xff0c 小雅兰的单链表还在偷懒ing xff0c 一直没有更新 xff0c 最近应该会更新出来 下面 xff0c 就让我们进入链表的
  • VRPN-体验

    VRPN简介 VRPN提供封装在库里的一套类 xff0c 在VR系统中 xff0c 用来为应用程序和物理外设提供网络传输接口 详细点击这里 VRPN源码获取 git clone https github com vrpn vrpn git
  • 单链表——“数据结构与算法”

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰的内容终于是我们心心念念的单链表啦 xff0c 这一块呢 xff0c 是一个很重要的部分 xff0c 也是一个对目前的我来说 xff0c 比较困难的部分 xff0c 下面 x
  • springboot服务端接口外网远程调试,并实现HTTP服务监听【内网穿透】

    文章目录 前言1 本地环境搭建1 1 环境参数1 2 搭建springboot服务项目 2 内网穿透2 1 安装配置cpolar内网穿透2 1 1 windows系统2 1 2 linux系统 2 2 创建隧道映射本地端口2 3 测试公网地
  • MCSM面板一键搭建我的世界服务器-外网远程联机【内网穿透】

    文章目录 前言1 Mcsmanager安装2 创建Minecraft服务器3 本地测试联机4 内网穿透4 1 安装cpolar内网穿透4 2 创建隧道映射内网端口 5 远程联机测试6 配置固定远程联机端口地址6 1 保留一个固定TCP地址6
  • 初识MySQL数据库——“MySQL数据库”

    各位CSDN的uu们你们好呀 xff0c 小雅兰好久没有更文啦 xff0c 确实是心有余而力不足 xff0c 最近学习的内容太难了 xff0c 这篇博客又是小雅兰的新专栏啦 xff0c 主要介绍的是一些MySQL数据库的知识点 xff0c
  • Windows10本地搭建网站教程【内网穿透】

    文章目录 概述1 搭建一个静态Web站点2 本地浏览测试站点是否正常3 本地站点发布公网可访问3 1 安装cpolar内网穿透3 2 创建隧道映射公网地址3 3 获取公网URL地址 4 公网远程访问内网web站点5 配置固定二级子域名5 1
  • MySQL环境搭建——“MySQL数据库”

    各位CSDN的uu们你们好呀 xff0c 小雅兰又来啦 xff0c 好久没有更文啦 xff0c 今天继续 xff01 xff01 xff01 今天小雅兰的内容是MySQL环境搭建 xff0c 下面 xff0c 让我们进入MySQL数据库的世
  • 在树莓派上搭建WordPress博客网站【内网穿透】

    文章目录 概述安装 PHP安装MySQL数据库安装 Wordpress设置您的 WordPress 数据库设置 MySQL MariaDB创建 WordPress 数据库 WordPress configuration将WordPress站
  • 双链表——“数据结构与算法”

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰又回来了 xff0c 到了好久没有更新的数据结构与算法专栏 xff0c 最近确实发现自己有很多不足 xff0c 需要学习的内容也有很多 xff0c 所以之后更新文章可能不会像
  • Windows在外远程桌面控制macOS【macOS自带VNC远程】

    文章目录 前言1 测试局域网内远程控制1 1 macOS打开屏幕共享1 2 测试局域网内VNC远程控制 2 测试公网远程控制2 1 macOS安装配置cpolar内网穿透2 2 创建tcp隧道 xff0c 指向5900端口 3 测试公网远程
  • 初学正点原子Ministm32板串口实验

    本章将实现如下功能 xff1a STM32 通过串口和上位机的对话 xff0c STM32 在收到上位机发过来的字符串后 xff0c 原原本本的返回给上位机 本章分为如下几个小节 xff1a 1 STM32串口简介 2 硬件设计 3 软件设
  • Qt网络通信实战(聊天室小项目TCP实现)

    一 UDP与TCP的区别 用一个表格来显示这两者的区别 比较项TCPUDP是否连接面向连接无连接传输是否可靠可靠不可靠流量控制提供不提供工作方式全双工可以是全双工应用场合大量数据少量数据速度慢 快 我们这里采用TCP 当有新的连接出现时就会
  • 常用坐标系及坐标系之间的变换

    坐标系统 xff1a 有哪些坐标系 xff0c 他们之间的变换矩阵是怎样的 xff1f xff08 主要是3个坐标系 3 个角 xff09 地理坐标系 xff08 n系 xff09 坐标系原点On取为飞行器质心 Xn轴向指向北 xff0c
  • ulimit命令用法详解

    ulimit用来限制每个用户可使用的资源 xff0c 如CPU 内存 句柄等 一 用法 ulimit SHacdefilmnpqrstuvx 限制 参数详解 S xff1a 表示软限制 超出设定的值会告警 H xff1a 表示硬限制 xff
  • java的数组反转

    Java中数组反转一般有两个思路 xff0c 第一个是新建一个临时数组 xff0c 把原来的数组内各值倒着给放进去 public class ArrayReverse public static void main String args
  • Ardupilot移植经验分享(1)

    目录 前言背景为什么写这篇文章移植Ardupilot的方法有两种底层适配提取应用层代码两种方法对比 准备阅读源码阅读官方开发者wiki选择编译平台下载编译源代码切换版本的正确方式编译老版本手动下载编译器修改PATH配置配置ubuntu以支持