一、简介
主要介绍stm32h7移植到nuttx的修改内容。当前修改是在nuttx已经支持的stm32 f7平台的基础上进行的,即相关驱动先拷贝f7的现有代码,然后进行修改以支持h7。修改主要分为两部分:一部分是以stm32h7芯片驱动为主,相关代码主要是nuttx的驱动代码;另一部分是与飞控板本身具体硬件相关的,相关代码在px4的drivers/boards的对应目录下。
二、nuttx的芯片相关的驱动代码
nuttx与芯片驱动相关的代码主要在nuttx\arch目录下。对应stm32h7而言,有两个目录:nuttx\arch\arm\src\stm32h7,arch\arm\include\stm32h7。移植主要是增加这两个目录(从f7的对应目录copy过来),然后进行具体的修改。
修改时需要参考stm32h7的datasheet,根据不同模块看对应章节的内容,然后分析与stm32 f7的差异,进行相关的修改。
头文件主要定义了对应模块(如i2c)支持了哪些寄存器,这些寄存器的相对地址,以及对应的比特定义。因此头文件需要仔细校对一遍,确保与h7对应。
源文件要针对f7与h7的具体的不同进行相应的修改。例如,h7的dma机制与f7的不同,需要做额外的配置和修改,所以对应模块如果支持dma,就需要检查所有与dma相关的代码,分析是否需要进行相应修改。
当前库上的代码已经支持stm32h7。因此,可以通过比较stm32h7与stm32f7对应的目录代码来了解当前所做的修改。
三、与stm32h7飞控板本身硬件相关的修改
这部分代码主要在Firmware\src\drivers\boards\px4fmu-v6目录下,主要是适配具体飞控硬件的。其中最主要的是头文件board_config.h,这个文件定义了各个spi的片选,pwm输出使用的定时器等内容。其他代码不多并且不是很复杂,本文不再具体描述这部分代码。
四、nuttx的移植
1、nuttx简介
nuttx支持多种芯片平台,高度可定制,是一个类linux的rtos。与linux一样,编译nuttx时首先需要一个配置文件,如.config。这个文件就是通过宏来定制nuttx,例如选择单板的芯片平台,具体的芯片型号,是否支持硬浮点,是否支持I2C,SPI,UART,以及具体支持哪几路I2C,SPI,UART,是否支持操作系统的某个特性,等等诸如此类。配置文件的生成一般也是类似linux,可以执行make config来生成。最终makefile文件会包含这个config文件,从而根据该文件中定义的宏来选择编译哪些文件以及文件里面的哪些函数。
针对我们的h7飞控,nuttx的配置文件在Firmware\nuttx-configs\px4fmu-v6\nsh目录下,文件名为defconfig。Firmware\nuttx-configs\px4fmu-v6目录的内容都是与nuttx配置相关的。 include目录下的board.h:定义了与stm32h7芯片相关的配置,是一个需要修改的重要文件。里面定义的内容nuttx的芯片驱动会使用到。
scripts目录下的ld.script:编译器链接脚本,定义了编译的代码段数据段怎样放入到flash或ram中。也需要根据芯片进行对应修改。
2、移植的大致过程
基本的方法是根据系统的启动过程来进行移植和调试。找到系统最开始调用的函数,通过控制GPIO点灯或uart打印的方式,先确定系统调用了这个函数。然后再根据nuttx系统的启动过程,通过点灯或uart打印,逐步的调试。
对于stm32系列芯片,上电后首先需要配置时钟rcc模块,具体配置可以参考stm32cubemx工具自动生成的代码。之后如果调试具体的模块如uart,这个模块也需要进行相关的时钟配置。
3、nuttx编译的makefile简介
a) nuttx根目录下的makeifle:可以看到,根据具体平台选择不同的文件,px4是在linux 下编译的,使用的是Makefile.unix文件。
-include .config /*这个文件就是该单板针对nuttx的配置文件*/
ifeq($(CONFIG_WINDOWS_NATIVE),y)
include Makefile.win
else
includeMakefile.unix
endif
b) Makefile.unix:该文件是关键部分,我们摘取关键的内容进行分析。
Makefile大部分定义了一些宏,方便后面引用。然后定义了如何编译,主要是
按照如下的结构:
target: dep1 dep2 dep3
do_action1; do_action2
target为(编译)目标,这个目标有一些依赖项,如dep1,dep2,dep3。因此编译target 这个目标时,先完成这些依赖项;然后再做后面的action1,action2。所以这里面就存在 嵌套编译结构。
根据上面解释,从下面makefile.unix文件的内容看出,执行make操作时,默认的 target为all,all依赖$(BIN),进而又有四个依赖项。根据这些依赖项的名字可以看出, nuttx编译主要划分为两个阶段pass1,pass2。
pass1主要编译user或app层面的库或文 件。
pass2主要编译nuttx os层面的库或文件。
宏USERDEPDIRS,KERNDEPDIRS在文件Directories.mk中定义,USERLIBS、NUTTXLIBS 在文件FlatLibs.mk中定义。
注意pass2在执行完依赖项后会进入arch对应的目录下执行编译(见红色加粗的那 行)。对于stm32来说,会进入nuttx\arch\arm\src目录执行编译,该目录下有个makefile 文件。最后一步的链接操作就是在这里执行的。
BIN = nuttx$(EXEEXT)
all:$(BIN)
$(BIN): pass1depspass2deps pass1 pass2
pass1deps: pass1dep $(USERLIBS)
pass2deps:pass2dep $(NUTTXLIBS)
pass1dep: contexttools/mkdeps$(HOSTEXEEXT) tools/cnvwindeps$(HOSTEXEEXT)
$(Q) for dir in $(USERDEPDIRS) ; do \
$(MAKE) -C $$dirTOPDIR="$(TOPDIR)" depend ; \
done
pass2dep:contexttools/mkdeps$(HOSTEXEEXT) tools/cnvwindeps$(HOSTEXEEXT)
$(Q)for dir in $(KERNDEPDIRS) ; do \
$(MAKE) -C $$dirTOPDIR="$(TOPDIR)" EXTRADEFINES=$(KDEFINE) depend; \
done
pass1:pass1deps
ifeq ($(CONFIG_BUILD_2PASS),y)
$(Q)if [ -z "$(CONFIG_PASS1_BUILDIR)" ]; then \
echo "ERROR:CONFIG_PASS1_BUILDIR not defined"; \
exit1; \
fi
$(Q)if [ ! -d "$(CONFIG_PASS1_BUILDIR)" ]; then \
echo "ERROR:CONFIG_PASS1_BUILDIR does not exist"; \
exit1; \
fi
$(Q)if [ ! -f "$(CONFIG_PASS1_BUILDIR)/Makefile" ]; then \
echo"ERROR: No Makefile in CONFIG_PASS1_BUILDIR"; \
exit 1; \
fi
$(Q)$(MAKE) -C $(CONFIG_PASS1_BUILDIR) TOPDIR="$(TOPDIR)" LINKLIBS="$(LINKLIBS)"USERLIBS="$(USERLIBS)" "$(CONFIG_PASS1_TARGET)"
endif
pass2: pass2deps
$(Q) $(MAKE) -C$(ARCH_SRC) TOPDIR="$(TOPDIR)" EXTRA_OBJS="$(EXTRA_OBJS)"LINKLIBS="$(LINKLIBS)" EXTRADEFINES=$(KDEFINE) $(BIN)
$(Q)if [ -w /tftpboot ] ; then \
cp -f $(BIN)/tftpboot/$(BIN).${CONFIG_ARCH}; \
fi
ifeq($(CONFIG_RRLOAD_BINARY),y)
@echo "MK: $(BIN).rr"
$(Q) $(TOPDIR)/tools/mkimage.sh--Prefix $(CROSSDEV) $(BIN) $(BIN).rr
$(Q) if [ -w /tftpboot ] ; then \
cp -f $(BIN).rr/tftpboot/$(BIN).rr.$(CONFIG_ARCH); \
fi
endif
ifeq($(CONFIG_INTELHEX_BINARY),y)
@echo "CP: $(BIN).hex"
$(Q) $(OBJCOPY) $(OBJCOPYARGS) -O ihex$(BIN) $(BIN).hex
endif
ifeq($(CONFIG_MOTOROLA_SREC),y)
@echo "CP: $(BIN).srec"
$(Q) $(OBJCOPY) $(OBJCOPYARGS) -O srec$(BIN) $(BIN).srec
endif
ifeq($(CONFIG_RAW_BINARY),y)
@echo "CP:$(BIN).bin"
$(Q) $(OBJCOPY)$(OBJCOPYARGS) -O binary $(BIN) $(BIN).bin
endif
ifeq($(CONFIG_UBOOT_UIMAGE),y)
@echo "MKIMAGE: uImage"
$(Q) mkimage -A arm -O linux -C none -Tkernel -a $(CONFIG_UIMAGE_LOAD_ADDRESS)\
-e$(CONFIG_UIMAGE_ENTRY_POINT) -n $(BIN) -d $(BIN).bin uImage
$(Q) if [ -w /tftpboot ] ; then \
cp -f uImage/tftpboot/uImage; \
fi
endif
c) nuttx\arch\arm\src目录下的Makefile
从前面分析,makefile.unix文件执行了下面这行,进入到arm/src目录进行编译,target为$(BIN),即为nuttx$(EXEEXT)。
$(Q) $(MAKE) -C $(ARCH_SRC)TOPDIR="$(TOPDIR)" EXTRA_OBJS="$(EXTRA_OBJS)"LINKLIBS="$(LINKLIBS)" EXTRADEFINES=$(KDEFINE) $(BIN)
arm\src\Makefile正好有对应的target定义,如下。可以看到,最后做了链接操作。
nuttx$(EXEEXT):$(HEAD_OBJ)board$(DELIM)libboard$(LIBEXT)
$(Q) echo "LD: nuttx"
$(Q) $(LD) --entry=__start $(LDFLAGS)$(LIBPATHS) $(EXTRA_LIBPATHS) \
-o $(NUTTX) $(HEAD_OBJ) $(EXTRA_OBJS) \
$(LDSTARTGROUP)$(LDLIBS) $(EXTRA_LIBS) $(LIBGCC) $(LDENDGROUP)
4、px4的编译过程
a) 前面写的纯nuttx的编译过程,px4的编译更复杂一些,主要使用cmake来进行编 译的。本节只简单将整个过程描述一下。
b) Firmware目录下的Makefile
如下,单板相关的cmake配置文件都放在Firmware\cmake\configs目录下, ALL_CONFIG_TARGETS宏根据文件后缀.cmake来提取所有的target。 NUTTX_CONFIG_TARGETS为目标os为nuttx的target。
使用make px4fmu-v6_default编译时就会使用 Firmware\cmake\configs\nuttx_px4fmu-v6_default.cmake作为配置文件。
ALL_CONFIG_TARGETS := $(basename $(shellfind "$(SRC_DIR)/cmake/configs" ! -name '*_common*' ! -name '*_sdflight_*' -name '*.cmake' -print| sed -e 's:^.*/::' | sort))
# Strip off leading nuttx_
NUTTX_CONFIG_TARGETS :=$(patsubst nuttx_%,%,$(filter nuttx_%,$(ALL_CONFIG_TARGETS)))
$(NUTTX_CONFIG_TARGETS):
$(call cmake-build,nuttx_$@,$(SRC_DIR))
# describe how to build acmake config
define cmake-build
+@$(eval BUILD_DIR =$(SRC_DIR)/build_$@$(BUILD_DIR_SUFFIX))
+@if [ $(PX4_CMAKE_GENERATOR)= "Ninja" ] && [ -e $(BUILD_DIR)/Makefile ]; then rm -rf $(BUILD_DIR); fi
+@if [ ! -e$(BUILD_DIR)/CMakeCache.txt ]; then mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake$(2) -G"$(PX4_CMAKE_GENERATOR)" -DCONFIG=$(1) $(CMAKE_ARGS) || (rm -rf $(BUILD_DIR)); fi
+@(cd $(BUILD_DIR) &&$(PX4_MAKE) $(PX4_MAKE_ARGS) $(ARGS))
endef
c) 其后,使用Firmware\CMakeLists.txt文件
该文件是关键文件,主导了整个编译过程。其中使用了一些函数,这些函数主要在 Firmware\cmake\nuttx\px4_impl_nuttx.cmake,Firmware\cmake\common\px4_base.cmake 文件中定义。
以下只分析主要的编译部分。
下面这行语句执行nuttx的编译,px4_os_prebuild_targets函数在 px4_impl_nuttx.cmake中定义。这个函数会调用px4_nuttx_add_export,后者会执行 COMMAND ${MAKE} --no-print-directory--quiet -C ${nuttx_src}/nuttx -r CONFIG_ARCH_BOARD=${CONFIG}export > nuttx_build.log 执行nuttx的编译。
px4_os_prebuild_targets(OUTprebuild_targets BOARD ${BOARD}
THREADS ${THREADS})
下面这些语句编译px4本身的模块。config_module_list在 nuttx_px4fmu-v6_default.cmake文件中定义。
set(module_libraries)
foreach(module${config_module_list}) string(REGEXMATCH "^[./]" external_module${module})
if(external_module)STRING(REGEX REPLACE "//" "/" EXT_MODULE ${module})
STRING(REGEX REPLACE "/""__" EXT_MODULE_PREFIX ${EXT_MODULE}) add_subdirectory(${module}${PX4_BINARY_DIR}/${EXT_MODULE_PREFIX}) else() add_subdirectory(src/${module}) endif()
px4_mangle_name(${module} mangled_name)
list(APPEND module_libraries${mangled_name})
#message(STATUS "addingmodule: ${module}") endforeach()
下面这条语句会到Firmware\src\firmware\nuttx执行cmake,这里面会执行链接操 作。
add_subdirectory(src/firmware/${OS})
d) Firmware\src\firmware\nuttx目录下的CMakeLists.txt
该文件定义了最后的链接操作,并且处理romfs。
target_link_libraries(firmware_nuttx
-Wl,--warn-common
-Wl,--gc-sections
-Wl,--start-group
${startup_libs}
${module_libraries}
${df_driver_libs}
${config_extra_libs}
${nuttx_bootloader_wrapers}
${link_libs}
-Wl,--end-group)
5、系统启动过程分析
a) 系统初始调用函数分析
Firmware\nuttx-configs\px4fmu-v6\scripts\ld.script文件定义了生成的bin文件最前面的是vector数组。如下,.vectors放在text段的最前面。
.text: {
_stext= ABSOLUTE(.);
*(.vectors)
.= ALIGN(32);
/*
Thissignature provides the bootloader with a way to delay booting
*/
_bootdelay_signature= ABSOLUTE(.);
FILL(0xffecc2925d7d05c5)
.+= 8;
*(.text.text.*)
*(.fixup)
*(.gnu.warning)
*(.rodata.rodata.*)
*(.gnu.linkonce.t.*)
*(.glue_7)
*(.glue_7t)
*(.got)
*(.gcc_except_table)
*(.gnu.linkonce.r.*)
_etext= ABSOLUTE(.);
/*
* This is a hack to make the newlib libm__errno() call
* use the NuttX get_errno_ptr() function.
*/
__errno= get_errno_ptr;
}> flash
.vectors数组在nuttx\arch\arm\src\armv7-m\up_vectors.c文件中定义,从中可以看到初始调用的函数是__start函数,该函数在nuttx\arch\arm\src\stm32h7\stm32_start.c文件中定义。
unsigned_vectors[] __attribute__((section(".vectors"))) =
{
/* Initial stack */
IDLE_STACK,
/* Reset exception handler */
(unsigned)&__start,
/* Vectors 2 - n point directly atthe generic handler */
[2 ... (15 +ARMV7M_PERIPHERAL_INTERRUPTS)] = (unsigned)&exception_common
};
b) nuttx\arch\arm\src\stm32h7\stm32_start.c
从函数__start就可以开始进行芯片的移植调试了。该函数会调用stm32_clockconfig进行芯片时钟的配置,进行一些基本配置如使能cache后,调用os_start启动nuttx操作系统。
在函数stm32_clockconfig就可以通过gpio点灯方式进行调试。
up_earlyserialinit函数调用后就可以使用up_lowputc进行uart串口打印了。注意具体使用哪个uart进行打印在nuttx的配置文件(Firmware\nuttx-configs\px4fmu-v6\nsh\defconfig)中定义,如CONFIG_UART7_SERIAL_CONSOLE=y表示使用uart作为打印串口。