日期:2019.05.08
APM(Ardupilot)和PX4是当今世界上最为流行和活跃的两大开源飞控软件项目,它们均在Linux下基于gcc编译工具链开发,对于那些习惯与在Windows下使用Keil MDK开发单片机的童鞋,无疑带来了一定的门槛,除了编译环境搭建较为麻烦之外,还有一个最主要的原因便是难以实现类似Keil上的在线硬件调试(Debug)功能。
而本文的主要目的在于引导读者,在Ubuntu系统下为APM飞控搭建一个类似Keil的编译、烧录和调试一体的开发环境,降低开源飞控的入门和开发门槛。
ubuntu 18.04
这里先列出后续会使用到的软件工具版本:
VSCode是微软在2015年发布的一个现代化跨平台开源编辑器,原生支持Git,并拥有着最强大的插件社区,被称之为史上最好用的编辑器。在越来越多各种强大的插件支持下,你可以将VSCode演变成支持绝大多数语言的IDE,当然包括了我们做嵌入式开发常用的C/C++。
随着时代的发展,Keil之类的IDE,虽然上手简单,但是其编辑器功能已经远远落后与时代,还有着收费、不开源、不支持跨平台等各种问题。而VSCode正好弥补了这些缺点,并有着无比强大的编辑器功能与丰富插件支持。结合插件,我们可以将VSCode打造成一个有史以来最强大的飞控开发IDE,有着比拟Keil的硬件Debug功能,还有秒杀Keil几条大街的编辑器,那么我们还有什么理由不投入VSCode的怀抱呢?
在ubuntu上安装VSCode的方法有很多种,可自行百度。比较简单的一种方式是访问官网,直接下载deb包到本地,双击安装。
丰富的插件是VSCode的灵魂,也是短短两三年内造就了VSCode霸主地位的主要原因之一,不安装插件的VSCode比咸鱼好不了多少。。。
安装插件的方法是点击左侧的”Extensions“图标,或使用快捷键ctrl+shit+x
打开插件搜索栏,输入名称查找插件,并点击install,如下图:
喜欢原生英文界面的可跳过这一步。。。
快捷键ctrl+shift+p
打开命令面板,输入language,选择"Configure Display Language",然后点击"zh-cn",软件重启之后便切换至中文界面。
在你的工程目录下打开终端,克隆代码到本地
git clone https://github.com/ArduPilot/ardupilot
进入代码目录
cd ardupilot
更新子模块
git submodule update --init --recursive
项目提供了环境部署脚本,可以直接安装所有所需工具和包
Tools/environment_install/install-prereqs-ubuntu.sh -y
理论上这样就可以了,但实际上由于gcc-arm-none-eabi工具链体积比较大,通过wget方式下载速度过慢,实在没有耐心等待,于是我选择手动安装工具链。
注意的是工具链版本很重要,过低和过高的版本都可能导致编译失败,一般部署脚本里提供的版本不会存在问题,所以我选择这个版本来下载。
打开链接https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads ,选择gcc-arm-none-eabi-6-2017-q2-update,Linux 64-bit进行下载,使用一些多线程下载工具速度会快很多。
下载完毕后,移动压缩包至opt目录下
sudo mv gcc-arm-none-eabi-6-2017-q2-update-linux.tar.bz2 /opt/
进入opt目录,并解压
cd /opt
sudo tar -jxvf gcc-arm-none-eabi-6-2017-q2-update-linux.tar.bz2
配置工具链路径为环境变量
exportline="export PATH=/opt/gcc-arm-none-eabi-6-2017-q2-update/bin:\$PATH"
if grep -Fxq "$exportline" ~/.profile; then echo nothing to do ; else echo $exportline >> ~/.profile; fi
. ~/.profile
检测gcc-arm-none-eabi是否已正确安装
arm-none-eabi-gcc --version
reboot
启动VSCode,并打开ardupilot文件夹,快捷键Ctrl+Shift+`打开终端。
为保险起见,让读者能够按照此教程顺利实现,请使用本文中编译的分支
git checkout fd19b257
编译适配fmuv5硬件的所有飞机类型的固件
make fmuv5
编译成功后如下图所示。
通常我们只想编译多旋翼的固件,以节省时间,可以使用waf命令。
./waf configure --board fmuv5
./waf copter
由于手上并没有pixhawk系列的飞控硬件,只能为已有的F405飞控板实现APM的适配。
我将自己手上的飞控板命名为bluesky(天穹飞控),后续编译飞控代码都将会使用这个名称,而读者们则需要根据自己的硬件来更改为相对应的型号进行编译,如fmuv2,fmuv3之类的。
进入ardupilot/libraries/AP_HAL_ChibiOS/hwdef目录下,新建文件夹,命名为blueksy,在bluesky目录下新建两个名为hwdef.dat、hwdef-bl.dat的文件,它们分别为飞控主程序和Bootloader的编译配置文件,这两个文件的内容如下:
# hw definition file for bluesky hardware
# MCU class and specific type
MCU STM32F4xx STM32F405xx
# board ID for firmware load
APJ_BOARD_ID 888
# crystal frequency
OSCILLATOR_HZ 8000000
STM32_PLLM_VALUE 8
define STM32_ST_USE_TIMER 4
define CH_CFG_ST_RESOLUTION 16
FLASH_SIZE_KB 1024
# board voltage
STM32_VDD 330U
# only one I2C bus
I2C_ORDER I2C1
# order of UARTs (and USB)
UART_ORDER OTG1 USART1 USART3 UART4 USART6
# LEDs
PA6 LED_BLUE OUTPUT LOW GPIO(0)
PC4 LED_GREEN OUTPUT LOW GPIO(1)
PC5 LED_RED OUTPUT LOW GPIO(2)
define HAL_GPIO_A_LED_PIN 0
define HAL_GPIO_B_LED_PIN 1
define HAL_GPIO_C_LED_PIN 2
# buzzer
#PC13 BUZZER OUTPUT GPIO(80) LOW
#define HAL_BUZZER_PIN 80
#define HAL_BUZZER_ON 1
#define HAL_BUZZER_OFF 0
# spi1 bus for IMU
PB13 SPI2_SCK SPI2
PC2 SPI2_MISO SPI2
PC3 SPI2_MOSI SPI2
PA5 ICM20689_CS CS
PA4 MS5611_CS CS
# only one I2C bus in normal config
PB6 I2C1_SCL I2C1
PB7 I2C1_SDA I2C1
# analog pins
PC0 BATT_VOLTAGE_SENS ADC1 SCALE(1)
PC1 BATT_CURRENT_SENS ADC2 SCALE(1)
# define default battery setup
# PC5 - ADC12_CH15
define HAL_BATT_VOLT_PIN 15
# PC4 - ADC12_CH14
define HAL_BATT_CURR_PIN 14
define HAL_BATT_VOLT_SCALE 10.1
define HAL_BATT_CURR_SCALE 17.0
# USART1
PA9 USART1_TX USART1
PA10 USART1_RX USART1
# rcinput
PA8 TIM1_CH1 TIM1 RCININT FLOAT LOW
# USART3
PB10 USART3_TX USART3
PB11 USART3_RX USART3
# UART4
PA0 UART4_TX UART4
PA1 UART4_RX UART4
# UART6
PC7 USART6_RX USART6
PC6 USART6_TX USART6
# PA10 IO-debug-console
PA11 OTG_FS_DM OTG1
PA12 OTG_FS_DP OTG1
# USB detection
#PB12 VBUS INPUT OPENDRAIN
# debug
PA13 JTMS-SWDIO SWD
PA14 JTCK-SWCLK SWD
# PWM out pins. Note that channel order follows the ArduPilot motor
# order conventions
PA2 TIM2_CH3 TIM2 PWM(1) GPIO(50)
PA3 TIM2_CH4 TIM2 PWM(2) GPIO(51)
PB0 TIM3_CH3 TIM3 PWM(3) GPIO(52)
PB1 TIM3_CH4 TIM3 PWM(4) GPIO(53)
PA15 TIM2_CH1 TIM2 PWM(5) GPIO(54)
PB3 TIM2_CH2 TIM2 PWM(6) GPIO(55)
define HAL_STORAGE_SIZE 15360
define STORAGE_FLASH_PAGE 2
# reserve 32k for bootloader and 32k for flash storage
FLASH_RESERVE_START_KB 64
define HAL_INS_DEFAULT HAL_INS_ICM20689_SPI
define HAL_INS_DEFAULT_ROTATION ROTATION_ROLL_180_YAW_270
# no built-in compass, but probe the i2c bus for all possible
# external compass types
define ALLOW_ARM_NO_COMPASS
define HAL_COMPASS_DEFAULT HAL_COMPASS_NONE
define HAL_PROBE_EXTERNAL_I2C_COMPASSES
define HAL_I2C_INTERNAL_MASK 0
define HAL_COMPASS_AUTO_ROT_DEFAULT 2
define HAL_BARO_DEFAULT HAL_BARO_MS5611_SPI
# SPI devices
SPIDEV icm20689 SPI2 DEVID1 ICM20689_CS MODE3 2*MHZ 8*MHZ
SPIDEV ms5611 SPI2 DEVID2 MS5611_CS MODE3 20*MHZ 20*MHZ
# filesystem setup on sdcard
#define HAL_OS_FATFS_IO 1
#define HAL_BOARD_LOG_DIRECTORY "/APM/LOGS"
#define HAL_BOARD_TERRAIN_DIRECTORY "/APM/TERRAIN"
# 8 PWM available by default
define BOARD_PWM_COUNT_DEFAULT 8
# uncomment the lines below to enable strict API
# checking in ChibiOS
define CH_DBG_ENABLE_ASSERTS TRUE
define CH_DBG_ENABLE_CHECKS TRUE
define CH_DBG_SYSTEM_STATE_CHECK TRUE
define CH_DBG_ENABLE_STACK_CHECK TRUE
# hw definition file for processing by chibios_pins.py
# for minimal F405 bootloader
# MCU class and specific type
MCU STM32F4xx STM32F405xx
# board ID for firmware load
APJ_BOARD_ID 888
# crystal frequency
OSCILLATOR_HZ 8000000
STM32_PLLM_VALUE 8
FLASH_SIZE_KB 1024
# bootloader is installed at zero offset
FLASH_RESERVE_START_KB 0
# LEDs
PA6 LED_BOOTLOADER OUTPUT LOW
PC5 LED_ACTIVITY OUTPUT LOW
define HAL_LED_ON 0
# the location where the bootloader will put the firmware
FLASH_BOOTLOADER_LOAD_KB 64
# board voltage
STM32_VDD 330U
# order of UARTs
UART_ORDER OTG1
PA11 OTG_FS_DM OTG1
PA12 OTG_FS_DP OTG1
define HAL_USE_EMPTY_STORAGE 1
define HAL_STORAGE_SIZE 15360
# debug
PA13 JTMS-SWDIO SWD
PA14 JTCK-SWCLK SWD
hwdef.dat中HAL_INS_ICM20689_SPI这个宏定义在飞控代码中是不存在的,因为飞控的IMU安装方向已经被固化在代码中,因此我需要针对我的飞控板对代码进行一定修改:
打开ardupilot/libraries/AP_InertialSensor目录下的AP_InertialSensor.cpp,在大约845行下面增加如下代码
#elif HAL_INS_DEFAULT == HAL_INS_ICM20689_SPI && defined(HAL_INS_DEFAULT_ROTATION)
ADD_BACKEND(AP_InertialSensor_Invensense::probe(*this, hal.spi->get_device("icm20689"), ROTATION_ROLL_180));
定义了我的IMU安装方向为ROTATION_ROLL_180
。
注意这里的hwdef.dat只是我临时修改的一个配置文件,还不够完善,后续再补充。针对不同的飞控板,可以参考文件夹下其他相近的配置文件来编写你自己的。
./waf configure --board bluesky --bootloader
./waf clean
./waf bootloader
./waf configure --board bluesky
./waf copter
OpenOCD是被VSCode的Cortex-Debug插件调用于烧录和调试STM32的软件。
sudo apt install openocd
查看版本
openocd -v
我使用的是J-Link OB调试器,需要修改OpenOCD的jlink配置为swd接口
sudo gedit /usr/share/openocd/scripts/interface/jlink.cfg
打开jlink.cfg文件进行修改,在其中加入一行并保存
transport select swd
将jlink调试器连接飞控并接入PC,输入以下命令测试是否能正确识别jlink及单片机
openocd -f interface/jlink.cfg -f target/stm32f4x.cfg
如果提示“Warn : Failed to open device: LIBUSB_ERROR_ACCESS.”之类的错误,说明当前用户没有usb端口权限,需要使用
sudo usermod -a -G dialout user_name
重启后会将当前用户加入dialout组,注意user_name为你的用户名称。
OpenOCD无法识别单片机:如果你的飞控中已经烧录过某些程序,则存在禁用SWD接口的可能性,导致调试器无法连接单片机,这时候需要将BOOT0引脚拉高后再上电。
OpenOCD端口被占用:如果提示"Error: couldn’t bing xxx to socket: Address aleady in use"这个错误,可能是因为OpenOCD需要监听的端口已经被占用,其中xxx可能是telnet_port、gdb_port、tcl_port三者之一,这三个默认端口分别为4444、3333、6666。比如如果tcl_port被占用(这个是我在windows下经常碰到的情况),可以在.cfg
文件中加入一行:tcl_port 19998
,修改端口为19998,以解决这个问题。
Bootloader只需要烧录一次,如果是pixhawk之类的已经烧写过程序的板子,那便可跳过此步骤。
连接单片机
openocd -f interface/jlink.cfg -f target/stm32f4x.cfg
打开另一个终端,通过telnet连接openocd
telnet localhost 4444
烧录Bootloader,注意这里要把Bootloader文件路径修改成你自己的
program /home/bluesky/Project/ardupilot/build/bluesky/bootloader/AP_Bootloader
复位单片机
reset
重头戏来了,但是要实现这一步必须要保证以下几点:
在VSCode中打开ardupilot项目后,点击左侧图标或者使用快捷键Ctrl+Shift+D
打开Debug界面,如下图
点击小齿轮图标,并选择“Cortex-Debug",将会添加一个launch.json的配置文件,将下面的代码复制(覆盖)到launch.json并保存:
{
"version": "0.2.0",
"configurations": [
{
"name": "Cortex Debug",
"cwd": "${workspaceRoot}",
"executable": "./build/bluesky/bin/arducopter", //固件路径,需要根据实际情况修改
"request": "launch",
"type": "cortex-debug",
"servertype": "openocd",
"configFiles": [
"interface/jlink.cfg", //Jlink配置文件
"target/stm32f4x.cfg" //目标芯片配置文件,需要根据自己飞控的实际芯片修改,可以到/usr/share/openocd/scripts/target目录下查看存在的配置文件
]
}
]
}
其中"executable"和"configFiles"要根据自己的实际情况修改,比如飞控是基于STM32F7单片机的,那么
“target/stm32f4x.cfg"要修改为"target/stm32f7x.cfg”。
前面编译飞控固件时不带有Debug信息,这样会导致调试时无法识别到源代码文件,编译时加入--debug
参数来增加debug信息。
./waf configure --debug --board bluesky
./waf copter
增加debug参数后编译出现了一个错误:
从错误提示上来看是因为AP_ROMFS.cpp中有一个compressed_size变量在使用前未初始化,于是打开libraries/AP_ROMFS目录下的AP_ROMFS.cpp文件,将第50行中的
uint32_t compressed_size;
修改为
uint32_t compressed_size = 0;
注意如果前面在其它终端中使用openocd连接了单片机,此时需要将其关闭。
一切都准备就绪。
在VSCode的菜单栏->调试中点击“启动调试”,或者直接按下F5,将会进入到Debug状态,中间出现一个Debug菜单栏。
首先VSCode会给飞控板烧录固件,需要等待数秒钟(APM固件体积较大因此烧录时间较长),调试控制台中出现下图信息时说明固件已经加载完毕,再次按下F5,程序开始运行。
现在可以开始愉快地调试飞控程序了,可以在代码中放断点,查看变量等等。
主要调试快捷键说明
断点
可以在代码左侧单击放置断点,右键编辑断点属性,还可以设置条件断点:
查看变量
将鼠标悬停在变量上面,可以显示变量的数值,也可以在左侧监视窗口中手动输入变量名以同时观察多个变量数据。有个缺陷是似乎在运行的时候无法查看变量数据,而在Keil的调试窗口中是能够支持全局变量和静态变量实时刷新的。
使用过Keil的童鞋都知道,调试程序前会先自动编译当前程序,在VSCode中我们也可以利用Task功能来实现一键自动编译+调试。
快捷键Ctrl+Shift+p
打开命令面板,输入task,选择Tasks:Configure Task(配置任务),在弹出来的选项中随便选择一个,将会自动生成一个tasks.json文件,然后将下面代码复制(覆盖)进去并保存:
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "build",
"command": "./waf copter",
}
]
}
其中 "command"就是我们定义的要运行的命令,这里你可以根据自己的需求来改动。
然后再打开先前的launch.json文件,在其中加入一行,表示在Debug之前先运行定义好的编译任务:
"preLaunchTask": "build",
当再次按下F5开始烧录和调试的时候,会先进行固件编译。
理论上所有基于STM32并使用gcc编译的项目,均可以使用这种方式进行开发、烧录和调试,比如我的上一篇博文就说明了如何用这种方式来调试Betaflight飞控:Ubuntu下使用VSCode编译调试Betaflight飞控。
随着插件的进一步丰富与完善,未来VSCode会有着更强大的功能和更便捷的使用性,所以,现在可能会是你入手VSCode的最佳时间。
如果想在Win10下开发,也可以利用WSL子系统实现编译,Windows上运行VSCode配合win版的gcc-arm-none-eabi和openocd来实现同样的烧录及调试功能。