分享

PX4 Bootloader解析

 limao164 2023-02-06 发布于北京

1 引言

半年前入手了Pixhawk V2全套硬件,编译好的开源固件也下了,四轴也飞了,一直想对这套开源飞控进行一个系统地解析,由于工作原因一直没时间。最近翻开了PX4飞控源代码,它基于NUTTX操作系统,在github上更新十分迅速。为了能够全面地掌握这套软硬件设计思想,同时对硬件系统有全面的认识,我决定对PX4 Bootloader进行详细解析。凡涉及到硬件相关的部分,本文以Pixhawk V2的主控STM32F427和IO协处理器STM32F100为基础进行解析,其他硬件可参照此方法进行类比,基本结构都是相似的。

感谢韦东山老师u-boot视频,我用同样的方法对PX4 Bootloader采用类似的方法进行了分析。针对我解析中存在的问题,希望同行和前辈们能够不吝赐教,谢谢。

2 PX4 Bootloader系统架构

从github上下载之后,打开Bootloader文件夹,可以看到libopencm3和Tools共2个文件夹和一共30个文件。

libopencm3和Tools这两个文件夹的主要功能如下:

  • Tools文件夹主要用于coding style和git submodule的检查,不需要太注意。
  • libopencm3是github上针对STM32系列芯片的开源库,Bootloader使用其中的库函数对相应型号的芯片进行操作,用到的地方将对库函数功能作简要说明,这里并不展开讨论。

其余30个文件主要功能如下:

  • 工程文件:包括Bootloader.sublime-project共1个文件,为sublime编辑器的工程文件。
  • Readme:包括Readme.md文件共1个,自己看,都能看懂。
  • License:包括LISENCE.md文件1个,权限文件,自己看。
  • Makefile文件:包括Makefile、Makefile.f1、Makefile.f2、Makefile.f3、Makefile.f4、Makefile.f7共5个文件,为系统的编译过程提供了依据。
  • Jtag配置文件:以.cfg结尾,包括jig_px4fmu.cfg、stm32f1x.cfg、stm32f3x.cfg、stm32f4x.cfg、stm32f102.cfg共5个文件,分别对应不同的Jtag烧写器、不同芯片使用openocd进行烧写时的配置。根据原理图可知,PX4 Bootloader可以使用Jtag接口进行程序烧写,某宝上买的Pixhawk V2拆开后在板上可以看到两个Jtag接口(分别对应主控STM32F427和协处理STM32F100),但是没有焊接出来,如需烧写bootloader程序可自行焊接。
  • 硬件配置头函数:包括hw_config.h共1个文件,规定了各类可运行PX4开源飞控程序的硬件配置,可与原理图对应查看。
  • 通用bootloader调用函数集:包括bl.h和bl.c共2个文件。提供了bootloader的main函数中需要调用的通用函数,这些函数与具体硬件无关,属顶层调用函数。
  • USB调试接口函数集:包括cdcacm.h和cdcacm.c共2个文件。提供了bootloader程序基于USB接口进行调试时所需的输入输出函数。
  • 串口调试接口集:包括usart.h和usart.c共2个文件。提供了bootloader程序基于串口进行调试时所需的输入输出函数。
  • main函数:包括main_f1.c、main_f3.c、main_f4.c、main_f7.c共4个文件。分别对应STM32F1、STM32F3、STM32F4和STM32F7芯片上运行的Bootloader的主函数。
  • 链接文件:以.ld结尾,包括stm32f1.ld、stm32f3.ld、stm32f4.ld、stm32f7.ld共4个文件。分别对应STM32F1、STM32F3、STM32F4和STM32F7芯片上Bootloader程序的链接方式。
  • PX固件生成脚本:包括px_mkfw.py共1个文件。根据文件说明,PX4固件为基于JSON编码的python对象,此脚本用于生成包含额外编码域的压缩文件固件,类似于u-boot中mkimage程序,生成的固件上传前需要用此脚本进行打包。PX4的固件本身已经具有这项功能,这个脚本应该不再需要了,应该很快会被丢弃。
  • PX固件上传脚本:包括px_uploader.py共1个文件。根据说明,此文件不再使用,已被固件中的上传脚本取代。

综上所述,这里需要重点分析的文件包括Makefile文件,链接文件,main函数文件,硬件配置头文件(hw_config.h),通用bootloader调用函数集文件(bl.h和bl.c),USB虚拟串口函数集文件(cdcacm.h和cdcacm.c),串口函数集文件(包括usart.h和usart.c)。

3 Makefile文件解析

PX4 Bootloader工程包含5个Makefile文件。其中Makefile是make命令的入口文件,将根据不同的编译对象,调用不同的Makefile.*文件。为了更清晰地了解Bootloader的编译过程,本节将对工程的Makefile进行详细注释。

3.1 主Makefile文件注释

Bootloader编译的make命令入口为根目录下的Makefile,这是所有编译命令开始的文件。


Makefile

  1. #
  2. # Paths to common dependencies
  3. #
  4. export BL_BASE ?= $(wildcard .) #获取Bootloader本地工程所在目录,并赋给变量BL_BASE
  5. export LIBOPENCM3 ?= $(wildcard libopencm3) #获取开源库libopencm3所在目录,并赋给变量LIBOPENCM3
  6. #
  7. # Tools
  8. #
  9. export CC = arm-none-eabi-gcc #定义交叉编译工具链变量CC
  10. export OBJCOPY = arm-none-eabi-objcopy #定义二进制生成工具链变量OBJCOPY
  11. #
  12. # Common configuration
  13. #
  14. export FLAGS = -std=gnu99 \ #使用GNU99的优化C语言标准
  15. -Os \ #专门针对生成目标文件大小进行
  16. -g \ #生成调试信息
  17. -Wundef \ #当没有定义的符号出现在#if中时警告
  18. -Wall \ #打开一些有用的警告选项
  19. -fno-builtin \ #不接受没有__buildin__前缀的函数作为内建函数
  20. -I$(LIBOPENCM3)/include \ #包含开源库libopencm3的头文件
  21. -ffunction-sections \ #要求编译器为每个function分配独立的section
  22. -nostartfiles \ #链接时不使用标准的启动文件
  23. -lnosys \ #链接libnosys.a文件
  24. -Wl,-gc-sections \ #传递-gc-sections给连接器,删除没有使用的section
  25. -Wl,-g \ #传递-g选项给链接器,兼容其他工具
  26. -Werror #把警告当错误,出现警告就放弃编译
  27. export COMMON_SRCS = bl.c cdcacm.c usart.c #定义通用源文件变量COMMON_SRCS
  28. #
  29. # Bootloaders to build
  30. #
  31. TARGETS = \ #定义编译目标
  32. aerofcv1_bl \
  33. auavx2v1_bl \
  34. crazyflie_bl \
  35. mindpxv2_bl \
  36. px4aerocore_bl \
  37. px4discovery_bl \
  38. px4flow_bl \
  39. px4fmu_bl \
  40. px4fmuv2_bl \
  41. px4fmuv4_bl \
  42. px4fmuv4pro_bl \
  43. px4fmuv5_bl \
  44. px4io_bl \
  45. px4iov3_bl \
  46. tapv1_bl
  47. all: $(TARGETS) #编译目标all=$(TARGETS)
  48. clean: #定义清理工程的操作(即make clean)
  49. cd libopencm3 && make --no-print-directory clean && cd .. # 从Bootloader本地工程目录进入libopencm3文件夹,执行make clean清理命令,然后回到上一级目录。
  50. rm -f *.elf *.bin #删除.efl和.bin文件
  51. #
  52. # Specific bootloader targets.
  53. # 各编译目标的具体操作,主要规定了编译需要使用的Makefile文件,硬件目标TARGET_HW,链接脚本LINKER_FILE,编译目标TARGET_FILE_NAME
  54. #
  55. auavx2v1_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  56. make -f Makefile.f4 TARGET_HW=AUAV_X2V1 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  57. px4fmu_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  58. make -f Makefile.f4 TARGET_HW=PX4_FMU_V1 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  59. px4fmuv2_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  60. make -f Makefile.f4 TARGET_HW=PX4_FMU_V2 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  61. px4fmuv4_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  62. make -f Makefile.f4 TARGET_HW=PX4_FMU_V4 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  63. px4fmuv4pro_bl:$(MAKEFILE_LIST) $(LIBOPENCM3)
  64. make -f Makefile.f4 TARGET_HW=PX4_FMU_V4_PRO LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@ EXTRAFLAGS=-DSTM32F469
  65. px4fmuv5_bl:$(MAKEFILE_LIST) $(LIBOPENCM3)
  66. make -f Makefile.f7 TARGET_HW=PX4_FMU_V5 LINKER_FILE=stm32f7.ld TARGET_FILE_NAME=$@
  67. mindpxv2_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  68. make -f Makefile.f4 TARGET_HW=MINDPX_V2 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  69. px4discovery_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  70. make -f Makefile.f4 TARGET_HW=PX4_DISCOVERY_V1 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  71. px4flow_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  72. make -f Makefile.f4 TARGET_HW=PX4_FLOW_V1 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  73. px4aerocore_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  74. make -f Makefile.f4 TARGET_HW=PX4_AEROCORE_V1 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  75. crazyflie_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  76. make -f Makefile.f4 TARGET_HW=CRAZYFLIE LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  77. # Default bootloader delay is *very* short, just long enough to catch
  78. # the board for recovery but not so long as to make restarting after a
  79. # brownout problematic.
  80. #
  81. px4io_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  82. make -f Makefile.f1 TARGET_HW=PX4_PIO_V1 LINKER_FILE=stm32f1.ld TARGET_FILE_NAME=$@
  83. px4iov3_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  84. make -f Makefile.f3 TARGET_HW=PX4_PIO_V3 LINKER_FILE=stm32f3.ld TARGET_FILE_NAME=$@
  85. tapv1_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  86. make -f Makefile.f4 TARGET_HW=TAP_V1 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  87. aerofcv1_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  88. make -f Makefile.f4 TARGET_HW=AEROFC_V1 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@
  89. #
  90. # Binary management
  91. # 二进制文件压缩操作:deploy命令
  92. #
  93. .PHONY: deploy
  94. deploy:
  95. zip Bootloader.zip *.bin
  96. #
  97. # Submodule management
  98. # 子模块libopencm3操作与管理,进行的操作如下:
  99. # 1. 更新git子工程libopencm3。
  100. # 2. 调用Tools/check_submodules.sh检查当前libopencm3的版本信息是否正确。
  101. # 3. 编译libopencm3工程,生成库文件,供bootloader使用。
  102. #
  103. $(LIBOPENCM3): checksubmodules
  104. make -C $(LIBOPENCM3) lib
  105. .PHONY: checksubmodules
  106. checksubmodules: updatesubmodules
  107. $(Q) ($(BL_BASE)/Tools/check_submodules.sh)
  108. .PHONY: updatesubmodules
  109. updatesubmodules:
  110. $(Q) (git submodule init)
  111. $(Q) (git submodule update)

3.2 主控Makefile.f4文件注释

由主Makefile中目标生成语句可以看出,Pixhawk V2硬件系统的主控stm32f427对应的bootloader调用了如下语句:


Makefile

  1. px4fmuv2_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  2. make -f Makefile.f4 TARGET_HW=PX4_FMU_V2 LINKER_FILE=stm32f4.ld TARGET_FILE_NAME=$@

因此,目标px4fmuv2_bl生成的Makefile脚本为Makefile.f4,同时,它还定义了如下变量

  • 硬件目标:$(TARGET_HW)=PX4_FMU_V2
  • 链接文件:$(LINKER_FILE)=stm32f4.ld
  • 目标名称:$(TARGET_FILE_NAME)=$@=px4fmuv2_bl

同时,在主Makefile中规定的全局变量BL_BASE、LIBOPENCM3、CC、OBJCOPY、FLAGS、COMMON_SRCS在Makefile.f4中同样有效。下面将对Makefile.f4进行详细注释。


Makefile.f4

  1. OPENOCD ?= openocd #定义OPENOCD命令
  2. JTAGCONFIG ?= interface/olimex-jtag-tiny.cfg #定义openocd对应的jtag烧写器配置文件
  3. #JTAGCONFIG ?= interface/jtagkey-tiny.cfg
  4. # 5 seconds / 5000 ms default delay
  5. PX4_BOOTLOADER_DELAY ?= 5000 #定义启动变量PX4_BOOTLOADER_DELAY为5000ms
  6. SRCS = $(COMMON_SRCS) main_f4.c #定义编译源文件为bl.c cdcacm.c usart.c和main_f4.c
  7. FLAGS = -mthumb\ #使用thumb指令集
  8. -mcpu=cortex-m4\ #使用cortex m4对应的CPU指令
  9. -mfloat-abi=hard\ #浮点为硬件浮点运算
  10. -mfpu=fpv4-sp-d16 \ #浮点运算协处理器为fpv4-sp-d16
  11. -DTARGET_HW_$(TARGET_HW) \ #定义变量TARGET_HW_PX4_FMU_V2为1
  12. -DSTM32F4 \ #定义变量STM32F4为1
  13. -T$(LINKER_FILE) \ #使用链接文件stm32f4.ld
  14. -L$(LIBOPENCM3)/lib \ #添加库文件搜索目录libopencm3/lib
  15. -lopencm3_stm32f4 \ #链接库文件libopencm3_stm32f4.a
  16. $(EXTRAFLAGS)
  17. ELF = $(TARGET_FILE_NAME).elf #定义目标变量ELF=px4fmuv2_bl.elf
  18. BINARY = $(TARGET_FILE_NAME).bin #定义目标二进制变量BINARY=px4fmuv2_bl.bin
  19. all: $(ELF) $(BINARY) #定义目标all,为px4fmuv2_bl.elf和px4fmuv2_bl.bin
  20. #px4fmuv2_bl.elf的生成规则:
  21. #px4fmuv2_bl.elf: bl.c cdcacm.c usart.c main_f4.c Makefile
  22. # arm-none-eabi-gcc -o px4fmuv2_bl.elf bl.c cdcacm.c usart.c main_f4.c $(FLAGS)
  23. $(ELF): $(SRCS) $(MAKEFILE_LIST)
  24. $(CC) -o $@ $(SRCS) $(FLAGS)
  25. #px4fmuv2_bl.bin的生成规则:
  26. #px4fmuv2_bl.bin: px4fmuv2_bl.elf
  27. # arm-none-eabi-objcopy -O binary px4fmuv2_bl.elf px4fmuv2_bl.bin
  28. $(BINARY): $(ELF)
  29. $(OBJCOPY) -O binary $(ELF) $(BINARY)
  30. #upload: all flash flash-bootloader
  31. #定义upload目标,依赖于all flash-bootloader
  32. upload: all flash-bootloader
  33. #定义flash-bootloader目标的命令规则
  34. #执行openocd命令,查找与本工程目录平行的px4_bootloader目录中interface/olimex-jtag-tiny.cfg文件作为jtag烧写器的配置文件,以stm32f4x.cfg作为主控stm32f427的SOC配置文件。然后在openocd环境下依次执行init, reset halt, flash write_image erase px4fmuv2_bl.bin 0x08000000, reset run, shutdown命令进行烧写。
  35. flash-bootloader:
  36. $(OPENOCD) --search ../px4_bootloader -f $(JTAGCONFIG) -f stm32f4x.cfg -c init -c 'reset halt' -c 'flash write_image erase $(BINARY) 0x08000000' -c 'reset run' -c shutdown
  37. # Use to upload to a stm32f4-discovery devboard, requires the latest version of openocd (from git)
  38. # build openocd with 'cd openocd; ./bootstrap; ./configure --enable-maintainer-mode --enable-stlink'
  39. #定义upload-discovery目标的命令规则
  40. #执行openocd命令,默认使用stlink烧写器及其配置文件,查找board/stm32f4discovery.cfg配置文件,在openocd环境下依次执行init, reset halt, flash probe 0, stm32f2x mass_erase 0, flash write_image erase px4fmuv2_bl.bin 0x08000000, reset, shutdown命令进行烧写。
  41. upload-discovery:
  42. $(OPENOCD) --search ../px4_bootloader -f board/stm32f4discovery.cfg -c init -c 'reset halt' -c 'flash probe 0' -c 'stm32f2x mass_erase 0' -c 'flash write_image erase $(BINARY) 0x08000000' -c 'reset' -c shutdown

3.3 IO协处理器Makefile.f1文件注释

由主Makefile中目标生成语句可以看出,Pixhawk V2硬件系统的IO协处理器stm32f100对应的bootloader调用了如下语句:


Makefile

  1. px4io_bl: $(MAKEFILE_LIST) $(LIBOPENCM3)
  2. make -f Makefile.f1 TARGET_HW=PX4_PIO_V1 LINKER_FILE=stm32f1.ld TARGET_FILE_NAME=$@

因此,目标px4io_bl生成的Makefile脚本为Makefile.f1,同时,它还定义了如下变量

  • 硬件目标:$(TARGET_HW)=PX4_PIO_V1
  • 链接文件:$(LINKER_FILE)=stm32f1.ld
  • 目标名称:$(TARGET_FILE_NAME)=$@=px4io_bl

同理,在主Makefile中规定的全局变量BL_BASE、LIBOPENCM3、CC、OBJCOPY、FLAGS、COMMON_SRCS在Makefile.f1中同样有效。下面将对Makefile.f1进行详细注释。


Makefile.f1

  1. OPENOCD ?= ../../sat/bin/openocd #定义openocd命令变量
  2. JTAGCONFIG ?= interface/olimex-jtag-tiny.cfg #定义Jtag烧写器配置文件
  3. #JTAGCONFIG ?= interface/jtagkey-tiny.cfg
  4. # 3 seconds / 3000 ms default delay
  5. PX4_BOOTLOADER_DELAY ?= 3000 #定义bootloader延时3000ms
  6. SRCS = $(COMMON_SRCS) main_f1.c #定义源文件为bl.c cdcacm.c usart.c和main_f1.c
  7. FLAGS = -mthumb\ #使用thumb指令集
  8. -mcpu=cortex-m3\ #使用cortex m3指令集
  9. -DTARGET_HW_$(TARGET_HW) \ #定义TARGET_HW_PX4_PIO_V1变量为1
  10. -DSTM32F1 \ #定义STM32F1变量为1
  11. -T$(LINKER_FILE) \ #使用链接脚本stm32f1.ld
  12. -L$(LIBOPENCM3)/lib \ #增加库文件搜索目录libopencm3/lib
  13. -lopencm3_stm32f1 #链接库文件libopencm3_stm32f1.a
  14. ELF = $(TARGET_FILE_NAME).elf #定义ELF目标变量px4io_bl.elf
  15. BINARY = $(TARGET_FILE_NAME).bin #定义BINARY目标变量px4io_bl.bin
  16. all: $(ELF) $(BINARY) #定义目标all为px4io_bl.elf和px4io_bl.bin
  17. #px4io_bl.elf生成规则
  18. #px4io_bl.elf: bl.c cdcacm.c usart.c main_f1.c Makefile
  19. # arm-none-eabi-gcc -o px4io_bl.elf bl.c cdcacm.c usart.c main_f1.c $(FLAGS)
  20. $(ELF): $(SRCS) $(MAKEFILE_LIST)
  21. $(CC) -o $@ $(SRCS) $(FLAGS)
  22. #px4io_bl.bin生成规则
  23. #px4io_bl.bin: px4io_bl.elf
  24. # arm-none-eabi-objcopy -O binary px4io_bl.elf px4io_bl.bin
  25. $(BINARY): $(ELF)
  26. $(OBJCOPY) -O binary $(ELF) $(BINARY)
  27. #upload: all flash flash-bootloader
  28. #定义upload目标,依赖于all flash-bootloader
  29. upload: all flash-bootloader
  30. #flash-bootloader目标生成命令
  31. #执行openocd命令,查找与本工程目录平行的px4_bootloader目录中interface/olimex-jtag-tiny.cfg文件作为jtag烧写器的配置文件,以stm32f1.cfg作为IO协处理器stm32f100的SOC配置文件。然后在openocd环境下依次执行init, reset halt, flash write_image erase px4io_bl.bin, reset run, shutdown命令进行烧写。
  32. flash-bootloader:
  33. $(OPENOCD) --search ../px4_bootloader -f $(JTAGCONFIG) -f stm32f1.cfg -c init -c 'reset halt' -c 'flash write_image erase $(BINARY)' -c 'reset run' -c shutdown

4 链接文件解析

PX4 Bootloader中包含4个链接文件stm32f1.ld、stm32f3.ld、stm32f4.ld和stm32f7.ld。与pixhawk V2硬件相关的为主控stm32f4.ld和IO协处理器stm32f1.ld两个链接脚本,它们将作为本文的分析重点。

PX4 Bootloader的链接文件可分为如下4个部分:

  • 声明可使用存储结构变量
  • 声明向量表名称
  • 定义段区
  • 定义栈顶地址变量值

4.1 主控链接脚本stm32f4.ld解析

链接脚本stm32f4.ld和libopencm3/lib/cm3/vector.c文件之间的关系十分密切。stm32f4.ld中定义了一系列在vector.c文件中使用的变量,包括_data_loadaddr,_data,_edata,_ebss,_stack,__preinit_array_start,__preinit_array_end,__init_array_start,__init_array_end,__fini_array_start,__fini_array_end,这些变量被用于Cortex M4的向量表初始化,主要是第一项的堆栈指针初始化和第二项的reset_handler函数的初始化。链接脚本stm32f4.ld中声明的向量表变量vector_table的定义在vector.c文件中。

与其它常见的链接脚本不同,stm32f4.ld中没有关键字ENTRY来规定程序的入口地址,在这种情况下程序就是从代码段(text)开始的。代码段最开始位置的是向量表,且ST公司的Cortex M4内核默认入口首地址(0x0)为堆栈指针(SP寄存器)的值;第二字为入口地址,因此reset_handler函数被调用为程序入口地址。


stm32f4.ld

  1. /* Define memory regions. */
  2. /* 声明存储区域,分为rom和ram两部分 */
  3. MEMORY
  4. {
  5. rom (rx) : ORIGIN = 0x08000000, LENGTH = 16K /* 定义rom区,即片内flash的部分区域,权限可读可执行,起始于flash起始地址0x08000000,使用前16K(即Section 0) */
  6. ram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K /* 定义ram区,即片内SRAM的部分区域,权限可读可写可执行,起始于SRAM1起始地址0x20000000,包含SRAM1和SRAM2的全部区域(128K) */
  7. }
  8. /* Enforce emmission of the vector table. */
  9. /* 声明外部定义的vector_table变量,此变量被定义在libopencm3/lib/cm3/vector.c中 */
  10. EXTERN (vector_table)
  11. /* Define sections. */
  12. SECTIONS /* 定义段 */
  13. {
  14. . = ORIGIN(rom); /* 链接器指针定位于rom区起始地址 */
  15. .text : { /* 定义代码段text */
  16. *(.vectors) /* Vector table 中断向量表,定义于libopencm3/lib/cm3/vector.c文件中,仅包含vector_table变量 */
  17. *(.text*) /* Program code 程序代码段 */
  18. . = ALIGN(4); /* 4字节对齐 */
  19. *(.rodata*) /* Read-only data 程序只读数据段 */
  20. . = ALIGN(4); /* 4字节对齐 */
  21. _etext = .; /* 定义变量_etext为当前地址 */
  22. } >rom /* 代码段text的程序地址位于rom中 */
  23. /* C Static constructors/destructors, also used for __attribute__
  24. * ((constructor)) and the likes */
  25. .preinit_array : { /* 定义C 构造函数段preinit_array */
  26. . = ALIGN(4); /* 4字节对齐 */
  27. __preinit_array_start = .; /* 定义变量__preinit_array_start为当前地址 */
  28. KEEP (*(.preinit_array)) /* 强制链接器保留preinit_array段 */
  29. __preinit_array_end = .; /* 定义变量__preinit_array_end为当前地址 */
  30. } >rom /* C 构造函数段preinit_array的程序地址位于rom中 */
  31. .init_array : { /* 定义C 构造函数段init_array */
  32. . = ALIGN(4); /* 4字节对齐 */
  33. __init_array_start = .; /* 定义变量__init_array_start为当前地址 */
  34. KEEP (*(SORT(.init_array.*))) /* 强制链接器保留init_array.* 段,并对满足字符串模式的内容进行升序排列 */
  35. KEEP (*(.init_array)) /* 强制链接器保留init_array段 */
  36. __init_array_end = .; /* 定义变量__init_array_end为当前地址 */
  37. } >rom /* C 构造函数段init_array的程序地址位于rom中 */
  38. .fini_array : { /* 定义C 析构函数段fini_array */
  39. . = ALIGN(4); /* 4字节对齐 */
  40. __fini_array_start = .; /* 定义变量__fini_array_start为当前地址 */
  41. KEEP (*(.fini_array)) /* 强制链接器保留fini_array段 */
  42. KEEP (*(SORT(.fini_array.*))) /* 强制链接器保留fini_array.*段,并对满足字符串模式的内容进行排序 */
  43. __fini_array_end = .; /* 定义变量__fini_array_end为当前地址 */
  44. } >rom /* C 析构函数段fini_array的程序地址位于rom中 */
  45. . = ORIGIN(ram); /* 链接器指针定位于ram区起始地址 */
  46. .data : AT(_etext) { /* 定义数据段data,加载地址位于变量_etext定义的位置 */
  47. _data = .; /* 定义变量_data为当前地址 */
  48. *(.data*) /* Read-write initialized data,程序数据段 */
  49. . = ALIGN(4); /* 4字节对齐 */
  50. _edata = .; /* 定义变量_edata为当前地址 */
  51. } >ram /* 数据段data的程序地址位于ram中 */
  52. _data_loadaddr = LOADADDR(.data); /* 定义变量_data_loadaddr为数据段加载地址的起始位置 */
  53. .bss : { /* 定义bss段 */
  54. *(.bss*) /* Read-write zero initialized data,程序bss段 */
  55. *(COMMON) /* COMMON段 */
  56. . = ALIGN(4); /* 4字节对齐 */
  57. _ebss = .; /* 定义变量_ebss为当前地址 */
  58. } >ram AT >rom /* bss段的程序地址位于ram中,加载地址位于rom中 */
  59. /*
  60. * The .eh_frame section appears to be used for C exception handling.
  61. * You may need to fix this if you're using C .
  62. */
  63. /DISCARD/ : { *(.eh_frame) } /* 被丢弃段eh_frame不会出现在输出文件中 */
  64. . = ALIGN(4); /* 4字节对齐 */
  65. end = .; /* 定义变量end为当前地址 */
  66. }
  67. PROVIDE(_stack = 0x20020000); /* 定义变量_stack,仅在被使用时生效,堆栈指针指向ram区最高地址 */

4.2 IO协处理器链接脚本stm32f1.ld解析

链接脚本stm32f1.ld与stm32f4.ld情况十分相近,不再复述,这里仅对其进行注释。


stm32f1.ld

  1. /* Define memory regions. */
  2. /* 声明存储区域,分为rom和ram两部分 */
  3. MEMORY
  4. {
  5. rom (rx) : ORIGIN = 0x08000000, LENGTH = 4K /* 定义rom区,即片内flash的部分区域,权限可读可执行,起始于flash起始地址0x08000000,使用前4K(即Page 0~3) */
  6. ram (rwx) : ORIGIN = 0x20000000, LENGTH = 8K /* 定义ram区,即片内SRAM的部分区域,权限可读可写可执行,起始于SRAM1起始地址0x20000000,使用全部8K(注意F100系列片内RAM最高为8K) */
  7. }
  8. /* Enforce emmission of the vector table. */
  9. /* 声明外部定义的vector_table变量,此变量被定义在libopencm3/lib/cm3/vector.c中 */
  10. EXTERN (vector_table)
  11. /* Define sections. */
  12. SECTIONS /* 定义段 */
  13. {
  14. . = ORIGIN(rom); /* 链接器指针定位于rom区起始地址 */
  15. .text : { /* 定义代码段text */
  16. *(.vectors) /* Vector table 中断向量表,定义于libopencm3/lib/cm3/vector.c文件中,仅包含vector_table变量 */
  17. *(.text*) /* Program code 程序代码段 */
  18. . = ALIGN(4); /* 4字节对齐 */
  19. *(.rodata*) /* Read-only data 程序只读数据段 */
  20. . = ALIGN(4); /* 4字节对齐 */
  21. _etext = .; /* 定义变量_etext为当前地址 */
  22. } >rom /* 代码段text的程序地址位于rom中 */
  23. /* C Static constructors/destructors, also used for __attribute__
  24. * ((constructor)) and the likes */
  25. .preinit_array : { /* 定义C 构造函数段preinit_array */
  26. . = ALIGN(4); /* 4字节对齐 */
  27. __preinit_array_start = .; /* 定义变量__preinit_array_start为当前地址 */
  28. KEEP (*(.preinit_array)) /* 强制链接器保留preinit_array段 */
  29. __preinit_array_end = .; /* 定义变量__preinit_array_end为当前地址 */
  30. } >rom /* C 构造函数段preinit_array的程序地址位于rom中 */
  31. .init_array : { /* 定义C 构造函数段init_array */
  32. . = ALIGN(4); /* 4字节对齐 */
  33. __init_array_start = .; /* 定义变量__init_array_start为当前地址 */
  34. KEEP (*(SORT(.init_array.*))) /* 强制链接器保留init_array.* 段,并对满足字符串模式的内容进行升序排列 */
  35. KEEP (*(.init_array)) /* 强制链接器保留init_array段 */
  36. __init_array_end = .; /* 定义变量__init_array_end为当前地址 */
  37. } >rom /* C 构造函数段init_array的程序地址位于rom中 */
  38. .fini_array : { /* 定义C 析构函数段fini_array */
  39. . = ALIGN(4); /* 4字节对齐 */
  40. __fini_array_start = .; /* 定义变量__fini_array_start为当前地址 */
  41. KEEP (*(.fini_array)) /* 强制链接器保留fini_array段 */
  42. KEEP (*(SORT(.fini_array.*))) /* 强制链接器保留fini_array.* 段,并对满足字符串模式的内容进行升序排列 */
  43. __fini_array_end = .; /* 定义变量__fini_array_end为当前地址 */
  44. } >rom /* C 析构函数段fini_array的程序地址位于rom中 */
  45. . = ORIGIN(ram); /* 链接器指针定位于ram区起始地址 */
  46. .data : AT(_etext) { /* 定义数据段data,加载地址位于变量_etext定义的位置 */
  47. _data = .; /* 定义变量_data为当前地址 */
  48. *(.data*) /* Read-write initialized data 程序数据段 */
  49. . = ALIGN(4); /* 4字节对齐 */
  50. _edata = .; /* 定义变量_edata为当前地址 */
  51. } >ram /* 数据段data的程序地址位于ram中 */
  52. _data_loadaddr = LOADADDR(.data); /* 定义变量_data_loadaddr为数据段加载地址的起始位置 */
  53. .bss : { /* 定义bss段 */
  54. *(.bss*) /* Read-write zero initialized data 程序bss段 */
  55. *(COMMON) /* COMMON段 */
  56. . = ALIGN(4); /* 4字节对齐 */
  57. _ebss = .; /* 定义变量_ebss为当前地址 */
  58. } >ram AT >rom /* bss段的程序地址位于ram中,加载地址位于rom中 */
  59. /*
  60. * The .eh_frame section appears to be used for C exception handling.
  61. * You may need to fix this if you're using C .
  62. */
  63. /DISCARD/ : { *(.eh_frame) } /* 被丢弃段eh_frame不会出现在输出文件中 */
  64. . = ALIGN(4); /* 4字节对齐 */
  65. end = .; /* 定义变量end为当前地址 */
  66. }
  67. PROVIDE(_stack = 0x20002000); /* 定义变量_stack,仅在被使用时生效,堆栈指针指向ram区最高地址 */

5 硬件配置头文件

PX4 Bootloader的硬件配置头文件仅有一个,hw_config.h。这套代码支持的所有硬件板配置均可在这里找到。它的结构很简单,针对不同的硬件配置使用#if-#elif-#end宏条件编译语句进行配置。针对Pixhawk V2这套硬件,仅涉及其中TARGET_HW_PX4_FMU_V2和TARGET_HW_PX4_PIO_V1两个条件编译块中的内容。由于本节将涉及具体的硬件信息,将结合芯片手册和原理图对这两部分内容进行详细解析。

5.1 主控宏TARGET_HW_PX4_FMU_V2对应的配置

在Makefile.f4的FLAGS变量中定义了TARGET_HW_PX4_FMU_V2和STM32F4两个变量,这两个变量主要用于宏条件编译分支的判断上,对硬件板载配置的时候起到了决定性的作用。


Makefile.f4

  1. FLAGS = -mthumb\ #使用thumb指令集
  2. -mcpu=cortex-m4\ #使用cortex m4对应的CPU指令
  3. -mfloat-abi=hard\ #浮点为硬件浮点运算
  4. -mfpu=fpv4-sp-d16 \ #浮点运算协处理器为fpv4-sp-d16
  5. -DTARGET_HW_$(TARGET_HW) \ #定义变量TARGET_HW_PX4_FMU_V2为1
  6. -DSTM32F4 \ #定义变量STM32F4为1
  7. -T$(LINKER_FILE) \ #使用链接文件stm32f4.ld
  8. -L$(LIBOPENCM3)/lib \ #添加库文件搜索目录libopencm3/lib
  9. -lopencm3_stm32f4 \ #链接库文件libopencm3_stm32f4.a
  10. $(EXTRAFLAGS)

hw_config.h文件中对应主控宏TARGET_HW_PX4_FMU_V2的代码如下:


hw_config.h

  1. #elif defined(TARGET_HW_PX4_FMU_V2) /* 若TARGET_HW_PX4_FMU_V2为真,下面的宏定义有效 */
  2. # define APP_LOAD_ADDRESS 0x08004000 /* APP_LOAD_ADDRESS,为Bootloader初始化完成后的飞控固件跳转地址(实际入口地址在0x08004004,这一点在bl.c文件的源代码中可以看到,0x08004000被用于标识后面的代码是否有效) */
  3. # define BOOTLOADER_DELAY 5000 /* BOOTLOADER_DELAY,为Bootloader初始化完毕到跳转到飞控固件需等待的时间 */
  4. # define BOARD_FMUV2 /* BOARD_FMUV2,在全部代码中没有用到 */
  5. # define INTERFACE_USB 1 /* INTERFACE_USB,表示Bootloader可采用USB口与上位机通信 */
  6. # define INTERFACE_USART 1 /* INTERFACE_USART,表示Bootloader可采用串口与上位机通信 */
  7. # define USBDEVICESTRING 'PX4 BL FMU v2.x' /* USBDEVICESTRING,表示对应USB ID的字符串 */
  8. # define USBPRODUCTID 0x0011 /* USBPRODUCTID,USB接口的PID */
  9. # define BOOT_DELAY_ADDRESS 0x000001a0 /* BOOT_DELAY_ADDRESS,flash上对应存放Bootloader等待时间的内存地址 */
  10. # define BOARD_TYPE 9 /* BOARD_TYPE,对应不同硬件配置板子上处理器的编号,每个不同的处理器对应不同的值 */
  11. # define _FLASH_KBYTES (*(uint16_t *)0x1fff7a22) /* _FLASH_KBYTES,程序运行期间flash以KB计的大小的存储地址(为何位于Reserved地址?) */
  12. # define BOARD_FLASH_SECTORS ((_FLASH_KBYTES == 0x400) ? 11 : 23) /* BOARD_FLASH_SECTORS,STM32F4芯片若片内flash为1M,则Sector数量为11;若不为1M(2M),则Sector数量为23,详见芯片手册 */
  13. # define BOARD_FLASH_SIZE (_FLASH_KBYTES * 1024) /* BOARD_FLASH_SIZE,flash总大小 */
  14. # define OSC_FREQ 24 /* OSC_FREQ,外部晶振频率24kHz */
  15. /* 主控FMU指示灯在板上一共有2个,FMU的PWR和B/E;PWR(LED704)为绿色常亮,上电即亮,B/E(LED701)由FMU的GPIOE12控制,为闪烁红色 */
  16. # define BOARD_PIN_LED_ACTIVITY 0 // no activity LED,无命令处理显示LED灯
  17. # define BOARD_PIN_LED_BOOTLOADER GPIO12 /* BOARD_PIN_LED_BOOTLOADER,主控Bootloader的LED指示灯为GPIOE12 */
  18. # define BOARD_PORT_LEDS GPIOE /* BOARD_PORT_LEDS,主控FMU的LED指示灯控制引脚在GPIO Port E */
  19. # define BOARD_CLOCK_LEDS RCC_AHB1ENR_IOPEEN /* BOARD_CLOCK_LEDS,RCC_AHB1ENR_IOPEEN被libopencm3/include/libopencm3/stm32/f4/rcc.h定义为(1<<4),对应RCC_AHB1ENR寄存器的第4位,GPIOE的时钟使能位 */
  20. # define BOARD_LED_ON gpio_clear /* BOARD_LED_ON,gpio_clear函数定义在libopencm3/lib/stm32/common/gpio_common_all.c中,实际为GPIO pin清零功能 */
  21. # define BOARD_LED_OFF gpio_set /* BOARD_LED_OFF,gpio_set函数定义在libopencm3/lib/stm32/common/gpio_common_all.c中,实际为GPIO pin置1功能 */
  22. # define BOARD_USART USART2 /* BOARD_USART,通信串口,USART2被libopencm3/include/libopencm3/stm32/common/usart_common_all.h定义为USART2_BASE(0x40004400) */
  23. # define BOARD_USART_CLOCK_REGISTER RCC_APB1ENR /* BOARD_USART_CLOCK_REGISTER,被定义为RCC_APB1ENR寄存器,用于使能USART2的时钟 */
  24. # define BOARD_USART_CLOCK_BIT RCC_APB1ENR_USART2EN /* BOARD_USART_CLOCK_BIT,RCC_APB1ENR_USART2EN被libopencm3/include/libopencm3/stm32/f4/rcc.h定义为(1<<17),对应RCC_APB1ENR的17位,用于使能USART2的时钟 */
  25. # define BOARD_PORT_USART GPIOD /* BOARD_PORT_USART,USART2的引脚位于GPIO Port D */
  26. # define BOARD_PORT_USART_AF GPIO_AF7 /* BOARD_PORT_USART_AF,GPIO_AF7被libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h定义为0x7,对应AF7的alternative function功能为USART1~3 */
  27. # define BOARD_PIN_TX GPIO5 /* BOARD_PIN_TX,USART2的发送引脚为GPIOD5 */
  28. # define BOARD_PIN_RX GPIO6 /* BOARD_PIN_RX,USART2的接收引脚为GPIOD6 */
  29. # define BOARD_USART_PIN_CLOCK_REGISTER RCC_AHB1ENR /* BOARD_USART_PIN_CLOCK_REGISTER,被定义为RCC_AHB1ENR寄存器,用于使能USART2引脚的时钟 */
  30. # define BOARD_USART_PIN_CLOCK_BIT RCC_AHB1ENR_IOPDEN /* BOARD_USART_PIN_CLOCK_BIT,RCC_AHB1ENR_IOPDEN被libopencm3/include/libopencm3/stm32/f4/rcc.h定义为(1<<3),对应RCC_AHB1ENR寄存器的第3位,用于使能GPIOD的时钟 */

5.2 IO协处理器宏TARGET_HW_PX4_PIO_V1对应的配置

在Makefile.f1的FLAGS变量中定义了TARGET_HW_PX4_PIO_V1和STM32F1两个变量,这两个变量主要用于宏条件编译分支的判断上,对硬件板载配置的时候起到了决定性的作用。


Makefile.f1

  1. FLAGS = -mthumb\ #使用thumb指令集
  2. -mcpu=cortex-m3\ #使用cortex m3指令集
  3. -DTARGET_HW_$(TARGET_HW) \ #定义TARGET_HW_PX4_PIO_V1变量为1
  4. -DSTM32F1 \ #定义STM32F1变量为1
  5. -T$(LINKER_FILE) \ #使用链接脚本stm32f1.ld
  6. -L$(LIBOPENCM3)/lib \ #增加库文件搜索目录libopencm3/lib
  7. -lopencm3_stm32f1 #链接库文件libopencm3_stm32f1.a

hw_config.h文件中对应主控宏TARGET_HW_PX4_PIO_V1的代码如下:


hw_config.h

  1. #elif defined(TARGET_HW_PX4_PIO_V1) || defined(TARGET_HW_PX4_PIO_V2) /* 若TARGET_HW_PX4_FMU_V2为真,下面的宏定义有效 */
  2. # define APP_LOAD_ADDRESS 0x08001000 /* APP_LOAD_ADDRESS,为bootloader初始化完成后的飞控固件跳转地址(实际入口地址在0x08001004,这一点在bl.c文件的源代码中可以看到,0x08001000被用于标识后面的代码是否有效) */
  3. # define APP_SIZE_MAX 0xf000 /* APP_SIZE_MAX,板载系统程序(Bootloader)大小上限 */
  4. # define BOOTLOADER_DELAY 200 /* BOOTLOADER_DELAY,为Bootloader初始化完毕到跳转到飞控固件需等待的时间 */
  5. # define BOARD_PIO /* 未见引用 */
  6. # define INTERFACE_USB 0 /* INTERFACE_USB,表示Bootloader不能用USB与上位机通信 */
  7. # define INTERFACE_USART 1 /* INTERFACE_USART,表示Bootloader可以用串口与上位机通信 */
  8. # define USBDEVICESTRING '' /* USBDEVICESTRING,因INTERFACE_USB为0,不使用USB与上位机通信,故USB ID的字符串设置为空 */
  9. # define USBPRODUCTID -1 /* USBPRODUCTID,因INTERFACE_USB为0,不使用USB与上位机通信,故USB设备ID设为非法值 */
  10. # define OSC_FREQ 24 /* OSC_FREQ,外部晶振频率24kHz */
  11. /* IO协处理器指示灯在板上一共有3个,IO的PWR、B/E和ACT;PWR(LED702)为绿色常亮,上电即亮,B/E(LED703)由IO协处理器的GPIOB15控制,为闪烁红色,ACT(LED705)由IO协处理器的GPIOB14控制,为闪烁蓝色 */
  12. # define BOARD_PIN_LED_ACTIVITY GPIO14 /* BOARD_PIN_LED_ACTIVITY,Bootloader有命令要处理时亮起,由IO协处理器的GPIOB14控制 */
  13. # define BOARD_PIN_LED_BOOTLOADER GPIO15 /* BOARD_PIN_LED_BOOTLOADER,IO协处理器Bootloader的LED指示灯为GPIOB15 */
  14. # define BOARD_PORT_LEDS GPIOB /* BOARD_PORT_LEDS,IO协处理器的LED指示灯控制引脚在GPIO Port B */
  15. # define BOARD_CLOCK_LEDS_REGISTER RCC_APB2ENR /* BOARD_CLOCK_LEDS_REGISTER,被定义为RCC_APB2ENR寄存器,用于使能LED时钟 */
  16. # define BOARD_CLOCK_LEDS RCC_APB2ENR_IOPBEN /* BOARD_CLOCK_LEDS,RCC_APB2ENR_IOPBEN被libopencm3/include/libopencm3/stm32/f1/rcc.h定义为(1<<3),对应RCC_APB2ENR寄存器的第3,GPIOB的时钟使能位 */
  17. # define BOARD_LED_ON gpio_clear /* BOARD_LED_ON,gpio_clear函数定义在libopencm3/lib/stm32/common/gpio_common_all.c中,实际为GPIO pin清零功能 */
  18. # define BOARD_LED_OFF gpio_set /* BOARD_LED_OFF,gpio_set函数定义在libopencm3/lib/stm32/common/gpio_common_all.c中,实际为GPIO pin置1功能 */
  19. /* IO协处理器的通信串口为USART2,与主控FMU的USART6通信 */
  20. # define BOARD_USART USART2 /* BOARD_USART,通信串口,USART2被libopencm3/include/libopencm3/stm32/common/usart_common_all.h定义为USART2_BASE(0x40004400) */
  21. # define BOARD_USART_CLOCK_REGISTER RCC_APB1ENR /* BOARD_USART_CLOCK_REGISTER,被定义为RCC_APB1ENR寄存器,用于使能USART2的时钟 */
  22. # define BOARD_USART_CLOCK_BIT RCC_APB1ENR_USART2EN /* BOARD_USART_CLOCK_BIT,RCC_APB1ENR_USART2EN被libopencm3/include/libopencm3/stm32/f1/rcc.h定义为(1<<17),对应RCC_APB1ENR的17位,用于使能USART2的时钟 */
  23. # define BOARD_PORT_USART GPIOA /* BOARD_PORT_USART,USART2的引脚位于GPIO Port A */
  24. # define BOARD_PIN_TX GPIO_USART2_TX /* BOARD_PIN_TX,GPIO_USART2_TX被libopencm3/include/libopencm3/stm32/f1/gpio.h中定义为GPIO2,PA2 */
  25. # define BOARD_PIN_RX GPIO_USART2_RX /* BOARD_PIN_RX,GPIO_USART2_RX被libopencm3/include/libopencm3/stm32/f1/gpio.h中定义为GPIO3,PA3 */
  26. # define BOARD_USART_PIN_CLOCK_REGISTER RCC_APB2ENR /* BOARD_USART_PIN_CLOCK_REGISTER,被定义为寄存器RCC_APB2ENR,用于使能USART2引脚的时钟 */
  27. # define BOARD_USART_PIN_CLOCK_BIT RCC_APB2ENR_IOPAEN /* BOARD_USART_PIN_CLOCK_BIT,RCC_APB2ENR_IOPAEN被libopencm3/include/libopencm3/stm32/f1/rcc.h定义为(1<<2),对应RCC_APB2ENR的第2位,用于使能GPIOA的时钟 */
  28. /* IO协处理器的GPIOB5接J702安全开关 */
  29. # define BOARD_FORCE_BL_PIN GPIO5 /* BOARD_FORCE_BL_PIN,板载强制Bootloader引脚为GPIOB5 */
  30. # define BOARD_FORCE_BL_PORT GPIOB /* BOARD_FORCE_BL_PIN,板载强制Bootloader引脚在GPIOB中 */
  31. # define BOARD_FORCE_BL_CLOCK_REGISTER RCC_APB2ENR /* BOARD_FORCE_BL_CLOCK_REGISTER,被定义为RCC_APB2ENR,用于使能GPIOB5的时钟 */
  32. # define BOARD_FORCE_BL_CLOCK_BIT RCC_APB2ENR_IOPBEN /* BOARD_FORCE_BL_CLOCK_BIT,RCC_APB2ENR_IOPBEN被libopencm3/include/libopencm3/stm32/f1/rcc.h定义为(1<<3),对应RCC_APB2ENR寄存器的第3,GPIOB的时钟使能位 */
  33. # define BOARD_FORCE_BL_PULL GPIO_CNF_INPUT_FLOAT // depend on external pull BOARD_FORCE_BL_PULL,GPIO_CNF_INPUT_FLOAT被libopencm3/include/libopencm3/stm32/f1/gpio.h定义为0x01,根据芯片手册表示GPIOB5为浮动输入状态,其值取决于外部硬件输入,即安全开关的状态
  34. # define BOARD_FORCE_BL_VALUE BOARD_FORCE_BL_PIN /* BOARD_FORCE_BL_VALUE,BOARD_FORCE_BL_PIN为GPIO5的值,用于判定IO协处理器是否永远进入Bootloader的循环状态 */
  35. # define BOARD_FLASH_SECTORS 60 /* BOARD_FLASH_SECTORS,STM32F1片内flash页数,Bootloader认为片内flash的前60页有效 */
  36. # define BOARD_TYPE 10 /* BOARD_TYPE,对应不同硬件配置板子上处理器的编号,每个不同的处理器对应不同的值 */
  37. # define FLASH_SECTOR_SIZE 0x400 /* FLASH_SECTOR_SIZE,STM32F1片内flash每页大小为1KB */

6 核心数据结构

与u-boot类似,PX4 Bootloader运行时需要一些核心数据结构的支持。这些数据结构一般是全局变量,它们的值能够充分反映整个程序运行的状态。Pixhawk V2板上有两个处理器分别跑着各自的Bootloader,因此各自维持这自己的核心数据结构。无论主控FMU还是IO协处理器的Bootloader程序都是从向量表开始的,而且PX4 Bootloader对所有处理器的向量表处理方式完全相同,因此本节将向量表数据结构单独拿出来分析。

6.1 向量表vector_table_t

STM32的Cortex M核心启动时均需要中断向量表支持,因此这个数据结构对程序的启动至关重要。这个数据结构在库文件libopencm3/include/libopencm3/cm3/vector.h中定义,宏NVIC_IRQ_COUNT在libopencm3/include/libopencm3/dispatch/nvic.h中被定义为0,故一般中断向量irq数量为0,可理解为不启用一般中断。


vector.h

  1. typedef void (*vector_table_entry_t)(void); /* 定义向量表函数指针类型 */
  2. typedef struct {
  3. unsigned int *initial_sp_value; /* 初始堆栈指针地址 */
  4. vector_table_entry_t reset; /* 重启reset向量函数指针 */
  5. vector_table_entry_t nmi; /* 不可屏蔽中断NMI向量函数指针 */
  6. vector_table_entry_t hard_fault; /* 硬件错误hard_fault向量函数指针 */
  7. vector_table_entry_t memory_manage_fault; /* 内存管理错误memory_manage_fault向量函数指针,Cortex M0核心不含此项 */
  8. vector_table_entry_t bus_fault; /* 总线错误bus_fault向量函数指针,Cortex M0核心不含此项 */
  9. vector_table_entry_t usage_fault; /* 指令使用错误usage_fault向量函数指针,Cortex M0核心不含此项 */
  10. vector_table_entry_t reserved_x001c[4]; /* 预留 */
  11. vector_table_entry_t sv_call; /* 管理模式sv_call向量函数指针 */
  12. vector_table_entry_t debug_monitor; /* 调试监控debug_monitor向量函数指针,Cortex M0核心不含此项(手册显示M4和M3也没有) */
  13. vector_table_entry_t reserved_x0034; /* 预留 */
  14. vector_table_entry_t pend_sv; /* pend_sv向量函数指针 */
  15. vector_table_entry_t systick; /* 系统时钟systick向量函数指针 */
  16. vector_table_entry_t irq[NVIC_IRQ_COUNT]; /* 一般中断irq向量函数指针数组,宏NVIC_IRQ_COUNT被定义为0 */
  17. } vector_table_t;

vector_table_t数据结构在文件libopencm3/lib/cm3/vector.c中实体化为变量vector_table并被初始化,单独构成vectors段,被链接脚本规定放在代码段最开始位置。初始化的重点时栈指针initial_sp_value和重启reset向量函数reset_handler,其余或者被初始化为什么都不做的null_handler,或者被初始化为死循环函数blocking_handler。null_handler和blocking_handler的定义均在vector.c文件中。宏IRQ_HANDLERS在文件libopencm3/lib/dispatch/vector_nvic.c中被定义为空,说明Bootloader不含中断处理函数。

vector.c中,向量函数的初始化函数均被定义为弱属性(weak),表示若外部没有同名函数定义,则用此弱属性函数;若有其他定义,则采用其他定义的函数。从这一点也可看到,对Bootloader的向量函数进行重写时十分方便,不需要修改软件框架。由于reset_handeler作为入口函数,因此还增加了裸属性(naked)。


vector.c

  1. __attribute__ ((section('.vectors'))) /* 变量vector_table属于vectors段 *
  2. vector_table_t vector_table = {
  3. .initial_sp_value = &_stack, /* 栈指针指向的地址,_stack在链接脚本中被定义为ram区最高地址 */
  4. .reset = reset_handler, /* 重启reset向量函数,reset_handler被定义在vector.c中 */
  5. .nmi = nmi_handler, /* 不可屏蔽中断nmi向量函数,nmi_handeler在vector.c中被定义为什么都不做的函数null_handler */
  6. .hard_fault = hard_fault_handler, /* 硬件错误hard_fault向量函数,hard_fault_handler在vector.c中被定义为死循环函数blocking_handler */
  7. /* Those are defined only on CM3 or CM4 */
  8. #if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) /* 若为CM3和CM4 */
  9. .memory_manage_fault = mem_manage_handler, /* 内存管理错误memory_manage_fault向量函数,mem_manage_handler在vector.c中被定义为死循环函数blocking_handler */
  10. .bus_fault = bus_fault_handler, /* 总线错误bus_fault向量函数,bus_fault_handler在vector.c中被定义为死循环函数blocking_handler */
  11. .usage_fault = usage_fault_handler, /* 指令使用错误usage_fault向量函数,usage_fault_handler在vector.c中被定义为死循环函数blocking_handler */
  12. .debug_monitor = debug_monitor_handler, /* 调试监控debug_monitor向量函数,debug_monitor_handler在vector.c中被定义为什么都不做的函数null_handler */
  13. #endif
  14. .sv_call = sv_call_handler, /* 调试监控debug_monitor向量函数,sv_call_handler在vector.c中被定义为什么都不做的函数null_handler */
  15. .pend_sv = pend_sv_handler, /* pend_sv向量函数,pend_sv_handler在vector.c中被定义为什么都不做的函数null_handler */
  16. .systick = sys_tick_handler, /* 系统时钟systick向量函数,sys_tick_handler在vector.c中被定义为什么都不做的函数null_handler */
  17. /* 由于sys_tick_handler被vector.c定义为弱属性(weak),该函数在bl.c中被重新定义,实际定义在bl.c中。 */
  18. .irq = {
  19. IRQ_HANDLERS /* IRQ_HANDLERS,在vector_nvic.c中被定义为空 */
  20. }
  21. };

6.2 主控FMU核心数据结构

除向量表vector外,主控FMU的核心数据结构包括boardinfo,flash_sector,mcu_des_t和mcu_rev_t,它们均为全局变量,表示板载FMU的基本配置情况。

6.2.1 结构体boardinfo

结构体boardinfo被定义在bl.h文件中,在main_f4.c中被初始化,用于描述运行此Bootloader的板载系统的最基本配置。


bl.h

  1. struct boardinfo {
  2. uint32_t board_type; /* 板载处理器编号 */
  3. uint32_t board_rev; /* 修订版本,程序中几乎没有用到 */
  4. uint32_t fw_size; /* 飞控固件大小的最大值 */
  5. uint32_t systick_mhz; /* 系统时钟的输入值,即CPU主频 */
  6. } __attribute__((packed)); /* 编译时使用紧凑数据模式,不进行优化对齐,即使用1字节对齐模式 */


main_f4.c

  1. struct boardinfo board_info = {
  2. .board_type = BOARD_TYPE, /* board_type,BOARD_TYPE板载处理器编号在hw_config.h中定义,主控为9*/
  3. .board_rev = 0, /* board_rev,修订版本为0 */
  4. .fw_size = 0, /* fw_size,飞控固件大小的最大值,这里的0没有意义,在board_init函数中被重新初始化 */
  5. .systick_mhz = 168, /* systick_mhz,系统时钟输入,即CPU主频168MHz */
  6. };

6.2.2 结构体flash_sector

结构体flash_sector用于描述STM32F4芯片内部flash的结构,它被定义并初始化在main_f4.c中。根据芯片手册,STM32F4片内flash大小为1M或2M两种,具体参见芯片手册。


main_f4.c

  1. static struct {
  2. uint32_t sector_number;
  3. uint32_t size;
  4. } flash_sectors[] = {
  5. {0x01, 16 * 1024},
  6. {0x02, 16 * 1024},
  7. {0x03, 16 * 1024},
  8. {0x04, 64 * 1024},
  9. {0x05, 128 * 1024},
  10. {0x06, 128 * 1024},
  11. {0x07, 128 * 1024},
  12. {0x08, 128 * 1024},
  13. {0x09, 128 * 1024},
  14. {0x0a, 128 * 1024},
  15. {0x0b, 128 * 1024},
  16. /* flash sectors only in 2MiB devices */
  17. {0x10, 16 * 1024},
  18. {0x11, 16 * 1024},
  19. {0x12, 16 * 1024},
  20. {0x13, 16 * 1024},
  21. {0x14, 64 * 1024},
  22. {0x15, 128 * 1024},
  23. {0x16, 128 * 1024},
  24. {0x17, 128 * 1024},
  25. {0x18, 128 * 1024},
  26. {0x19, 128 * 1024},
  27. {0x1a, 128 * 1024},
  28. {0x1b, 128 * 1024},
  29. };

6.2.3 结构体mcu_des_t

结构体mcu_des_t存储了STM32F4类MCU的所有型号信息,它被定义在main_f4.c文件中,并被实体化为变量数组mcu_descriptions,此结构用于程序中自动识别当前MCU类型。MCU类型信息被存储在DBGMCU_IDCODE寄存器中,地址为0xE0042000。此结构体在check_silicon函数中使用。


main_f4.c

  1. // address of MCU IDCODE
  2. #define DBGMCU_IDCODE 0xE0042000
  3. #define STM32_UNKNOWN 0
  4. #define STM32F40x_41x 0x413
  5. #define STM32F42x_43x 0x419
  6. #define STM32F42x_446xx 0x421
  7. typedef struct mcu_des_t {
  8. uint16_t mcuid;
  9. const char *desc;
  10. char rev;
  11. } mcu_des_t;
  12. mcu_des_t mcu_descriptions[] = {
  13. { STM32_UNKNOWN, 'STM32F???', '?'},
  14. { STM32F40x_41x, 'STM32F40x', '?'},
  15. { STM32F42x_43x, 'STM32F42x', '?'},
  16. { STM32F42x_446xx, 'STM32F446XX', '?'},
  17. };

6.2.4 结构体mcu_rev_t

结构体mcu_rev_t存储了MCU的版本信息,它被定义在main_f4.c文件中,并被实体化为变量数组silicon_revs,此结构除了可以识别处理器版本外,还可以据此判断MCU的内部flash信息。


main_f4.c

  1. typedef enum mcu_rev_e { /* 版本号枚举定义,感觉不全啊, */
  2. MCU_REV_STM32F4_REV_A = 0x1000, /* REV A for STM32F405/407/415/417/42X/43X */
  3. MCU_REV_STM32F4_REV_Z = 0x1001, /* REV Z for STM32F405/407/415/417 */
  4. MCU_REV_STM32F4_REV_Y = 0x1003, /* REV Y for STM32F42X/43X */
  5. MCU_REV_STM32F4_REV_1 = 0x1007, /* REV 1 for STM32F42X/43X */
  6. MCU_REV_STM32F4_REV_3 = 0x2001 /* REV 3 for STM32F42X/43X */
  7. } mcu_rev_e;
  8. typedef struct mcu_rev_t {
  9. mcu_rev_e revid;
  10. char rev;
  11. } mcu_rev_t;
  12. const mcu_rev_t silicon_revs[] = {
  13. {MCU_REV_STM32F4_REV_3, '3'}, /* Revision 3 */
  14. {MCU_REV_STM32F4_REV_A, 'A'}, /* Revision A */ // FIRST_BAD_SILICON_OFFSET (place good ones above this line and update the FIRST_BAD_SILICON_OFFSET accordingly)
  15. {MCU_REV_STM32F4_REV_Z, 'Z'}, /* Revision Z */
  16. {MCU_REV_STM32F4_REV_Y, 'Y'}, /* Revision Y */
  17. {MCU_REV_STM32F4_REV_1, '1'}, /* Revision 1 */
  18. };

6.3 IO协处理器核心数据结构

除向量表vector外,IO协处理器的核心数据结构仅有结构体boardinfo,它表征了IO协处理器的运行状态。它同样定义在bl.h中,在main_f1.c中被初始化。


main_f1.c

  1. struct boardinfo board_info = {
  2. .board_type = BOARD_TYPE, /* board_type,BOARD_TYPE板载处理器编号在hw_config.h中定义,IO协处理器为10。 */
  3. .board_rev = 0, /* board_rev,修订版本为0 */
  4. .fw_size = APP_SIZE_MAX, /* fw_size,APP_SIZE_MAX飞控固件大小的最大值,在hw_config.h中被定义为0xf000,60KB */
  5. .systick_mhz = OSC_FREQ, /* systick_mhz,系统时钟输入等于OSC_FREQ,在hw_config.h中被定义为24MHz */
  6. };

7 PX4 Bootloader主线程序

铺垫了这么多,重点终于来了。与u-boot类似,无论是在主控FMU还是IO协处理器上,PX4 Bootloader本身只是一个规模较大的单片机程序,它的主要功能有:

  • 初始化板载芯片并跳转到飞控固件入口
  • 提供板载调试功能,便于系统调试

由于Pixhawk V2硬件的主控FMU和IO协处理器分别运行着自己的那套Bootloader代码,因此它将有2个Bootloader主线。这两个主线程序的引导(main函数之前)由libopencm3库提供支持,premain阶段的代码是相同的。

7.1 初始化阶段(reset_handler函数)程序

Pixhawk V2的主控FMU芯片型号为STM32F427,内核为Cortex M4;IO协处理器芯片的型号为STM32F100,内核为Cortex M3,因此它们启动后MCU的入口地址为向量表的第二项。向量表的内容参见本文“核心数据结构”一节中关于向量表的内容(文件libopencm3/lib/cm3/vector.c),本节的重点是reset_handler函数的详细解析。

reset_handler函数的主要功能如下:

  • 用数据段存储的参数初始化内存空间
  • 清零BSS段
  • 设置异常处理堆栈指针对齐模式
  • 调用与架构相关的pre_main函数
  • 运行所有构造函数
  • 调用main函数
  • 调用所有析构函数

libopencm3/lib/cm3/vector.c

  1. /* 变量声明 */
  2. extern unsigned _data_loadaddr, _data, _edata, _ebss, _stack; /* 段初始化变量,定义在链接脚本中 */
  3. typedef void (*funcp_t) (void); /* 定义函数指针类型funcp_t,其原型为void (*functionname) (void) */
  4. extern funcp_t __preinit_array_start, __preinit_array_end; /* C 构造函数地址变量 */
  5. extern funcp_t __init_array_start, __init_array_end; /* C 构造函数地址变量 */
  6. extern funcp_t __fini_array_start, __fini_array_end; /* C 析构函数地址变量 */
  7. /* 定义为弱属性,可直接在其他地方重写此函数;定义为裸属性,确保编译器对reset_handler函数不添加任何加载和卸载代码 */
  8. void __attribute__ ((weak, naked)) reset_handler(void)
  9. {
  10. volatile unsigned *src, *dest;
  11. funcp_t *fp;
  12. /* 用数据段存储的参数初始化内存空间,变量定义见链接脚本。 */
  13. /* _data_loadaddr:数据段的起始加载地址,即在flash上的首地址 */
  14. /* _data:数据段的起始运行地址,即在RAM中的首地址 */
  15. /* _edata:数据段的截止运行地址,即在RAM中的末地址 */
  16. for (src = &_data_loadaddr, dest = &_data; dest < &_edata; src , dest ) {
  17. *dest = *src;
  18. }
  19. /* BSS段清零,变量定义见各自的链接脚本 */
  20. /* _ebss:bss段的截止运行地址,其起始运行地址紧接数据段的末地址 */
  21. while (dest < &_ebss) {
  22. *dest = 0;
  23. }
  24. /* 确保进入异常模式时堆栈指针8字节对齐。此设置对M3和R1架构不起作用 */
  25. SCB_CCR |= SCB_CCR_STKALIGN;
  26. /* 调用与架构相关的pre_main函数(主控FMU在文件libopencm3/lib/stm32/f4/vector_chipset.c中;IO协处理器在文件libopencm3/lib/cm3/vector.c中,且为空函数)。 */
  27. pre_main();
  28. /* 依次调用所有C 定义的构造函数,变量定义见链接脚本。 */
  29. /* __preinit_array_start:preinit_array段首地址 */
  30. /* __preinit_array_end:preinit_array段末地址 */
  31. /* __init_array_start:init_array段首地址 */
  32. /* __init_array_end:init_array段末地址 */
  33. for (fp = &__preinit_array_start; fp < &__preinit_array_end; fp ) {
  34. (*fp)();
  35. }
  36. for (fp = &__init_array_start; fp < &__init_array_end; fp ) {
  37. (*fp)();
  38. }
  39. /* 调用main函数(主控FMU在文件main_f4.c中,IO协处理器在文件main_f1.c中)。 */
  40. main();
  41. /* 依次调用所有C 定义的析构函数,变量定义见链接脚本 */
  42. /* __fini_array_start:fini_array段首地址 */
  43. /* __fini_array_end:fini_array段末地址 */
  44. for (fp = &__fini_array_start; fp < &__fini_array_end; fp ) {
  45. (*fp)();
  46. }
  47. }

本小节结束前,再简单提一下pre_main函数。该函数只对主控FMU有意义,被定义在文件libopencm3/lib/stm32/f4/vector_chipset.c中,IO协处理器中的pre_main是个空函数。

主控FMU的pre_main函数只做了一件事:使能硬件浮点运算


libopencm3/lib/stm32/f4/vector_chipset.c

  1. static void pre_main(void)
  2. {
  3. /* 使能硬件浮点运算 */
  4. SCB_CPACR |= SCB_CPACR_FULL * (SCB_CPACR_CP10 | SCB_CPACR_CP11);
  5. }

7.2 主控FMU的main函数

主控FMU的主线程序的核心是main函数,它被入口函数reset_handler调用。main函数的主要功能是初始化主控FMU芯片并启动飞控固件;若成功代码将永远运行飞控程序,若失败则可与上位机通信方便调试飞控板。主控FMU的main函数流程如下:

  1. 开启浮点运算功能(premain函数中已开启)。
  2. 调用board_init函数来初始化board_info结构体、GPIOA9(VBUS)端口、USART2端口、LED灯等。
  3. 调用clock_init函数来设置时钟、flash访问。
  4. 检查寄存器BOOT_RTC_REG中的值是否与宏BOOT_RTC_SIGNATURE相同。若相同则停留在bootloader。中,若不相同则可以跳转至飞控固件
  5. 通过检查飞控固件在flash地址0x080041a0和0x080041a4的值,来判定是否停留在bootloader中。
  6. 检查USB是否连接,若连接则必须等待timeout再跳转到飞控固件。
  7. 检测串口是否收到break信号(字节0),若收到需等待timeout时间再跳转到飞控固件。
  8. 若运行到此处try_boot依然为真,则调用函数jump_to_app启动飞控固件---------------------------------------------跳转,若函数返回(跳转失败)则继续9步。
  9. 若函数jump_to_app返回,跳转失败;设置寄存器BOOT_RTC_REG值确保下次启动能够检测到,设置timeout使程序永远停留在bootloader中。
  10. 现在bootloader首次启动飞控固件失败,初始化上位机通信接口(USB和USART2)。
  11. 调用bootloader函数与上位机通信,可运行各种调试命令、烧写新的固件等。
  12. 若串口USART2收到break信号(0字节),跳转到11步。
  13. 调用函数jump_to_app启动飞控固件---------------------------------------------跳转,若函数返回(跳转失败)则timeout赋值为0且跳到11步。

值得注意的是,main函数调用的jump_to_app函数依然有很多操作。这部分代码在bl.c文件中定义,其流程具有通用性,但jump_to_app调用的函数又与架构相关,因此这部分将放在后续中。


main_f4.c

  1. #if INTERFACE_USART /* 宏INTERFACE_USART定义为1,代码有效,hw_config.h */
  2. # define BOARD_INTERFACE_CONFIG_USART (void *)BOARD_USART /* 宏BOARD_USART定义为USART2,hw_config.h */
  3. #endif
  4. #if INTERFACE_USB /* 宏INTERFACE_USB定义为1,代码有效,hw_config.h */
  5. # define BOARD_INTERFACE_CONFIG_USB NULL
  6. #endif
  7. int main(void)
  8. {
  9. bool try_boot = true; /* bootloader是否立即跳转到飞控固件入口的标志(不等待timeout) */
  10. unsigned timeout = BOOTLOADER_DELAY; /* bootloader初始化完毕后跳转到飞控固件入口地址时,所需等待的时间,以ms计算。宏BOOTLOADER_DELAY在hw_config.h中被定义为5000 */
  11. SCB_CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); /* 使能浮点运算,其功能与pre_main函数相同,实际上可以去掉。 */
  12. #if defined(BOARD_POWER_PIN_OUT) /* 宏BOARD_POWER_PIN_OUT未定义,下面函数不编译 */
  13. if (board_get_rtc_signature() == POWER_DOWN_RTC_SIGNATURE) {
  14. board_set_rtc_signature(0);
  15. while (1);
  16. }
  17. #endif
  18. /* 调用board_init函数,此函数在main_f4.c中被定义,功能如下: */
  19. /* 1. 赋值board_info.fw_size,确定飞控固件size允许的最大值 */
  20. /* 2. 初始化USB端口GPIOA9(VBUS) */
  21. /* 3. 初始化串口USART2 */
  22. /* 4. 初始化LED并点亮(B/E,LED701) */
  23. /* 5. 使能能耗控制器时钟 */
  24. board_init();
  25. /* 调用clock_init函数,此函数在main_f4.c中被定义,功能如下: */
  26. /* 1. PLL时钟设置,并选择main PLL作为系统时钟sysclk,fsysclk=fPLL=168MHz,fUSBSDRNG=48MHz */
  27. /* 2. AHB、APB1、APB2时钟设置,fAHB=fsysclk=168MHz,fAPB1=42MHz,fAPB2=84MHz */
  28. /* 3. 选择高能耗模式(Scale 2 mode) */
  29. /* 4. flash访问控制设置,启动Icache和Dcache,并设置等待时间5周期 */
  30. /* 5. 更新库libopencm3中AHB,APB1,APB2对应的全局变量 */
  31. clock_init();
  32. /* 检查寄存器BOOT_RTC_REG中的值是否与宏BOOT_RTC_SIGNATURE相同,若相同则停留在bootloader中,若不相同则可以跳转至飞控固件 */
  33. /* BOOT_RTC_REG:RTC_BKPxR的第一个32位寄存器,重启不改变存储值,定义在main_f4.c */
  34. /* BOOT_RTC_SIGNATURE:值0xb007b007,被定义在main_f4.c中 */
  35. /* board_get_rtc_signature:被定义在main_f4.c中,用于获取寄存器BOOT_RTC_REG的存储值 */
  36. /* board_set_rtc_signature:被定义在main_f4.c中,设置寄存器BOOT_RTC_REG的值 */
  37. if (board_get_rtc_signature() == BOOT_RTC_SIGNATURE) {
  38. /* bootloader跳转标志为假,不再立即跳转到飞控固件 */
  39. try_boot = false;
  40. /* timeout清零,若无新的飞控固件上传,不再跳转至飞控固件 */
  41. timeout = 0;
  42. /* 寄存器BOOT_RTC_REG清零,确保下次重启可以跳转至飞控固件 */
  43. board_set_rtc_signature(0);
  44. }
  45. #ifdef BOOT_DELAY_ADDRESS /* 宏BOOT_DELAY_ADDRESS在hw_config.h中有定义,下列代码有效 */
  46. {
  47. /* 这里给定一个机会,通过飞控固件自身的设置可以影响bootloader的行为。 */
  48. /* 设置地址在0x080041a0和0x080041a4的flash值满足一系列逻辑要求,可以防止bootloader自动跳转到飞控固件,给调试工作带来方便。 */
  49. /* BOOT_DELAY_ADDRESS:值0x000001a0,定义在hw_config.h */
  50. /* flash_func_read_word:定义在main_f4.c,用于读取飞控固件内某地址 */
  51. /* 这里,sig1为flash中地址在0x080041a0的32位值,sig2为flash中地址在0x080041a4的32位地址值,在飞控固件地址范围内 */
  52. uint32_t sig1 = flash_func_read_word(BOOT_DELAY_ADDRESS);
  53. uint32_t sig2 = flash_func_read_word(BOOT_DELAY_ADDRESS 4);
  54. /* BOOT_DELAY_SIGNATURE1:值0x92c2ecff,定义在bl.h */
  55. /* BOOT_DELAY_SIGNATURE2:值0xc5057d5d,定义在bl.h */
  56. /* BOOT_DELAY_MAX:值30,定义在bl.h */
  57. /* 在满足一系列逻辑下,设定try_boot为假,且重新设置timeout,保证跳转到飞控固件前等待timeout时间 */
  58. if (sig2 == BOOT_DELAY_SIGNATURE2 && (sig1 & 0xFFFFFF00) == (BOOT_DELAY_SIGNATURE1 & 0xFFFFFF00)) {
  59. unsigned boot_delay = sig1 & 0xFF;
  60. if (boot_delay <= BOOT_DELAY_MAX) {
  61. try_boot = false;
  62. if (timeout < boot_delay * 1000) {
  63. timeout = boot_delay * 1000;
  64. }
  65. }
  66. }
  67. }
  68. #endif
  69. /* board_test_force_pin:定义在main_f4.c,若bootloader引脚为真,则设置try_boot为假,程序不立即跳转至飞控固件,需等待timeout时间 */
  70. /* 由于函数中条件编译的3个宏(BOARD_FORCE_BL_PIN_OUT、BOARD_FORCE_BL_PIN_IN和BOARD_FORCE_BL_PIN)均未被定义,此函数返回恒为假 */
  71. if (board_test_force_pin()) {
  72. try_boot = false;
  73. }
  74. /* 检查USB是否连接,若连接则必须等待timeout再跳转到飞控固件,否则立即跳转。这样的设置为飞控固件烧写以及调试带来极大方便。 */
  75. #if INTERFACE_USB /* 宏INTERFACE_USB定义在hw_config.h,以下代码有效 */
  76. #if defined(BOARD_USB_VBUS_SENSE_DISABLED) /* 宏BOARD_USB_VBUS_SENSE_DISABLED未定义 */
  77. try_boot = false;
  78. #else
  79. /* GPIOA:地址为0x40020000的寄存器(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  80. /* GPIO9:值1<<9(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  81. /* gpio_get:获取某GPIO组的值,定义在libopencm3/lib/stm32/common/gpio_common_all.c */
  82. /* 根据原理图,GPIOA9对应VBUS/3.1A,高电平表示USB已连接,低电平表示USB未接 */
  83. if (gpio_get(GPIOA, GPIO9) != 0) {
  84. /* 设置try_boot为假,bootloader不会立即跳转至飞控固件,需等待timeout时间 */
  85. try_boot = false;
  86. }
  87. #endif
  88. #endif
  89. /* 检测串口是否收到break信号(字节0),若收到置try_boot为假,需等待timeout时间再跳转到飞控固件 */
  90. #if INTERFACE_USART /* 宏INTERFACE_USB定义在hw_config.h,以下代码有效 */
  91. /* board_test_usart_receiving_break:定义在main_f4.c,连续接收3个字符,若串口上受到字节0则返回真,否则返回假 */
  92. if (board_test_usart_receiving_break()) {
  93. try_boot = false;
  94. }
  95. #endif
  96. /* 若运行到此处try_boot依然为真,则立即跳转到飞控固件(实际上jump_to_app函数代码依然不短,这里可以近似这样认为); */
  97. /* 若跳转未成功,则设置RTC备份寄存器BOOT_RTC_REG为预定值,确保下次重启若不更新飞控固件依然无法跳转。 */
  98. if (try_boot) {
  99. #ifdef BOARD_BOOT_FAIL_DETECT /* 宏BOARD_BOOT_FAIL_DETECT未定义,以下代码无效 */
  100. board_set_rtc_signature(BOOT_RTC_SIGNATURE);
  101. #endif
  102. /* jump_to_app:定义在bl.c,跳转到飞控固件 */
  103. jump_to_app();
  104. /* BOOT_RTC_SIGNATURE: */
  105. /* board_set_rtc_signature: */
  106. /* 设置RTC备份寄存器BOOT_RTC_REG值为BOOT_RTC_SIGNATURE,下次重启检测到后若不更新飞控固件则不跳转,程序一直运行在bootloader中。 */
  107. board_set_rtc_signature(BOOT_RTC_SIGNATURE);
  108. /* 置timeout为0,程序一直在bootloader中 */
  109. timeout = 0;
  110. }
  111. /* 现在bootloader首次启动飞控固件失败,初始化上位机通信接口(USB和USART2) */
  112. #if INTERFACE_USART /* 宏INTERFACE_USART定义在hw_config.h,下列代码有效 */
  113. /* BOARD_INTERFACE_CONFIG_USART:值USART2(值0x40004400,指向USART2寄存器首地址)。 */
  114. /* BOARD_INTERFACE_CONFIG_USART在main_f4.c中被定义为BOARD_USART,BOARD_USART在hw_config.h中被定义为USART2。*/
  115. /* USART2被定义为USART2_BASE,值0x40004400,指向USART2寄存器首地址(libopencm3/include/libopencm3/stm32/common/usart_common_all.h)。 */
  116. /* USART:值1,枚举型,定义在bl.h */
  117. /* cinit:定义在bl.c,用于初始化通信端口 */
  118. /* 初始化串口USART2为与上位机的通信接口 */
  119. cinit(BOARD_INTERFACE_CONFIG_USART, USART);
  120. #endif
  121. #if INTERFACE_USB /* 宏INTERFACE_USB定义在hw_config.h,下列代码有效 */
  122. /* BOARD_INTERFACE_CONFIG_USB:值NULL,定义在main_f4.c */
  123. /* USB:值2,枚举型,定义在bl.h */
  124. /* cinit:定义在bl.c,用于初始化通信端口 */
  125. /* 初始化USB为虚拟串口作为与上位机的通信接口 */
  126. cinit(BOARD_INTERFACE_CONFIG_USB, USB);
  127. #endif
  128. #if 0 /* 下列代码无效 */
  129. // MCO1/02
  130. gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO8);
  131. gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_100MHZ, GPIO8);
  132. gpio_set_af(GPIOA, GPIO_AF0, GPIO8);
  133. gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO9);
  134. gpio_set_af(GPIOC, GPIO_AF0, GPIO9);
  135. #endif
  136. while (1) {
  137. /* bootloader:bootloader与上位机的命令处理函数,烧写新的固件或者timeout(不为0)时间到返回,定义在bl.c中 */
  138. bootloader(timeout);
  139. /* board_test_force_pin:定义在main_f4.c,若bootloader引脚为真,则继续循环 */
  140. /* 由于函数中条件编译的3个宏(BOARD_FORCE_BL_PIN_OUT、BOARD_FORCE_BL_PIN_IN和BOARD_FORCE_BL_PIN)均未被定义,此函数返回恒为假 */
  141. if (board_test_force_pin()) {
  142. continue;
  143. }
  144. #if INTERFACE_USART /* 宏INTERFACE_USART定义在hw_config.h,下列代码有效 */
  145. /* board_test_usart_receiving_break:连续接收3个字节的内容,如果收到一个0字节则返回真,否则返回假。定义在main_f4.c */
  146. /* 若串口USART2收到break信号(0字节),继续循环 */
  147. if (board_test_usart_receiving_break()) {
  148. continue;
  149. }
  150. #endif
  151. #ifdef BOARD_BOOT_FAIL_DETECT /* 宏BOARD_BOOT_FAIL_DETECT未定义,下列代码无效 */
  152. board_set_rtc_signature(BOOT_RTC_SIGNATURE);
  153. #endif
  154. /* jump_to_app:跳转至飞控固件 */
  155. jump_to_app();
  156. /* 跳转失败,timeout设置为0,永久停留在bootloader中 */
  157. timeout = 0;
  158. }
  159. }

7.2 IO协处理器的main函数

IO协处理器的主线程序的核心是main函数,它被入口函数reset_handler调用。main函数的主要功能是初始化主控FMU芯片并启动飞控固件;若成功代码将永远运行飞控程序,若失败则可与上位机通信方便调试飞控板。IO协处理器的main函数流程如下:

  1. 调用board_init函数初始化,包括LED灯、强制Bootloader引脚GPIOB5、准备RTC备份寄存器、设置引脚GPIOA2为USART2-TX
  2. 判定,若RTC备份寄存器BKP_DR1中存储了预定值,则timeout赋值为200ms
  3. 若GPIO5为高电平,则timeout赋值为0xffffffff,永久停留在bootloader中
  4. 若timeout为0,则调用jump_to_app立即启动飞控固件---------------------------------------------启动;若jump_to_app函数返回,则timeout赋值为0
  5. 现在bootloader首次启动飞控固件失败,初始化系统时钟为PLL(使用HSI)
  6. 函数初始化串口USART2
  7. 调用bootloader函数与上位机通信,可运行各种调试命令、烧写新的固件等
  8. 调用函数jump_to_app启动飞控固件---------------------------------------------跳转,若函数返回(跳转失败)则timeout赋值为0且跳到7步。

值得注意的是,main函数调用的jump_to_app函数依然有很多操作。这部分代码在bl.c文件中定义,其流程具有通用性,但jump_to_app调用的函数又与架构相关,因此这部分将放在后续中。


  1. #ifdef INTERFACE_USART /* 宏INTERFACE_USART定义为1,代码有效,hw_config.h */
  2. # define BOARD_INTERFACE_CONFIG (void *)BOARD_USART /* 宏BOARD_USART定义为USART2,hw_config.h */
  3. #else
  4. # define BOARD_INTERFACE_CONFIG NULL
  5. #endif
  6. int main(void)
  7. {
  8. unsigned timeout = 0; /* bootloader初始化完毕后跳转到飞控固件入口地址时,所需等待的时间,以ms计算。 */
  9. /* 调用board_init函数,此函数在main_f1.c中被定义,功能如下 */
  10. /* 1. 初始化LED控制并点亮LED灯 */
  11. /* 2. 设置强制Bootloader引脚GPIOB5为浮动输入模式,方便采集安全开关的状态 */
  12. /* 3. 使能能源接口时钟和备份接口时钟,准备RTC备份寄存器 */
  13. /* 4. 设置USART2-TX引脚GPIOA2,对应原理图SERIAL_IO_TO_FMU,用于IO协处理器发送信息到主控FMU */
  14. board_init();
  15. #if defined(INTERFACE_USART) | defined (INTERFACE_USB) /* 宏INTERFACE_USART和INTERFACE_USB分别被定义为1和0,代码有效,hw_config.h */
  16. timeout = BOOTLOADER_DELAY; /* BOOTLOADER_DELAY:值200,表示200ms,hw_config.h */
  17. #endif
  18. #ifdef INTERFACE_I2C /* 宏INTERFACE_I2C未定义,代码无效 */
  19. # error I2C bootloader detection logic not implemented
  20. #endif
  21. /* 若RTC备份寄存器BKP_DR1中存储了预定值,则timeout赋值为200ms */
  22. /* should_wait:判定RTC备份寄存器BKP_DR1中是否存储了预定值。若存储了返回真,未存储则返回假 */
  23. /* BOOTLOADER_DELAY:值200,表示200ms,hw_config.h */
  24. if (should_wait()) {
  25. timeout = BOOTLOADER_DELAY;
  26. }
  27. #ifdef BOARD_FORCE_BL_PIN /* 宏BOARD_FORCE_BL_PIN定义为GPIO5,代码有效,hw_config.h */
  28. /* 若GPIO5为高电平,则timeout赋值为0xffffffff,永久停留在bootloader中 */
  29. /* BOARD_FORCE_BL_VALUE:被定义为GPIO5(1<<5),hw_config.h */
  30. /* BOARD_FORCE_BL_PORT:被定义为GPIOB(GPIOB的首寄存器),hw_config.h */
  31. /* BOARD_FORCE_BL_PIN:被定义为GPIO5(1<<5),hw_config.h */
  32. /* gpio_get:获取某GPIO组的值,定义在libopencm3/lib/stm32/common/gpio_common_all.c */
  33. if (BOARD_FORCE_BL_VALUE == gpio_get(BOARD_FORCE_BL_PORT, BOARD_FORCE_BL_PIN)) {
  34. timeout = 0xffffffff;
  35. }
  36. #endif
  37. /* 若timeout为0,则调用jump_to_app立即启动飞控固件。 */
  38. /* 若jump_to_app函数返回,则timeout赋值为0 */
  39. if (timeout == 0) {
  40. jump_to_app();
  41. timeout = 0;
  42. }
  43. /* 初始化系统时钟为PLL(使用HSI) */
  44. /* clock_init:使用HSI将MCU的系统时钟为PLL,频率48MHz,定义在main_f1.c */
  45. clock_init();
  46. /* 函数初始化串口USART2 */
  47. /* BOARD_INTERFACE_CONFIG:定义为BOARD_USART(USART2),main_f1.c */
  48. /* USART:值1,枚举型,定义在bl.h中 */
  49. cinit(BOARD_INTERFACE_CONFIG, USART);
  50. while (1) {
  51. /* bootloader:bootloader与上位机的命令处理函数,烧写新的固件或者timeout(不为0)时间到返回,定义在bl.c中 */
  52. bootloader(timeout);
  53. /* jump_to_app:跳转至飞控固件 */
  54. jump_to_app();
  55. /* 跳转失败,timeout设置为0,永久停留在bootloader中 */
  56. timeout = 0;
  57. }
  58. }

7.3 飞控固件启动函数jump_to_app

主控FMU或IO协处理器的main函数的一个目标就是调用jump_to_app函数来启动对应的飞控固件。jump_to_app函数定义在文件bl.c中,它的主要功能如下:

  • 通过飞控固件的烧写约定,检测固件是否有效。若无效函数返回,跳转失败
  • 若飞控固件有效,反向初始化MCU外设,准备将外设的控制权交给飞控固件
  • 更改向量表地址至飞控固件的向量表(通过向量表偏移寄存器SCB_VTOR)
  • 重置堆栈并跳转至飞控固件

bl.c

  1. /* do_jump函数,重置堆栈指针并跳转至飞控固件入口的函数,jump_to_app函数的终极目标 */
  2. static void do_jump(uint32_t stacktop, uint32_t entrypoint)
  3. {
  4. /* 汇编嵌入C语言代码 */
  5. asm volatile( /* 汇编代码声明 */
  6. 'msr msp, %0 \n' /* msr:无条件赋值指令,msp:主堆栈寄存器,%0:0号输入(stacktop)。将stacktop赋值给msp */
  7. 'bx %1 \n' /* bx:寄存器寻址跳转指令,%1:1号输入(entrypoint)。程序跳转至寄存器entrypoint */
  8. : : 'r'(stacktop), 'r'(entrypoint) :); /* 定义汇编代码中%0代表stacktop,%1代表entrypoint */
  9. /* 程序应该不会运行到这里,如果不幸到了,进入死循环 */
  10. for (;;) ;
  11. }
  12. /* jump_to_app函数 */
  13. void jump_to_app()
  14. {
  15. /* APP_LOAD_ADDRESS,值0x08004000(主控FMU)0x08001000(IO协处理器),飞控固件起始地址(hw_config.h) */
  16. const uint32_t *app_base = (const uint32_t *)APP_LOAD_ADDRESS;
  17. /* 1. 根据飞控固件的烧写约定,检测固件是否有效 */
  18. /* 飞控固件烧写时,我们特意约定最后烧写固件的首地址(飞控固件使用的堆栈首地址)。若固件首地址为0xffffffff,表明固件烧写未完成(或固件无效)无法跳转,函数直接返回 */
  19. if (app_base[0] == 0xffffffff) {
  20. return;
  21. }
  22. /* 飞控固件的第二个32位代表飞控固件的入口地址。若此地址未在固件指定的地址范围内,则表示固件有问题无法跳转,函数直接返回 */
  23. if (app_base[1] < APP_LOAD_ADDRESS) {
  24. return;
  25. }
  26. if (app_base[1] >= (APP_LOAD_ADDRESS board_info.fw_size)) {
  27. return;
  28. }
  29. /* 2. 现在飞控固件有效,反向初始化以便把外设控制权交给飞控固件 */
  30. /* flash_lock:主控FMU,锁定flash,操作寄存器FLASH_CR的域LOCK(bit31),定义libopencm3/lib/stm32/common/flash_common_f234.c */
  31. /* flash_lock:IO协处理器,锁定flash,操作寄存器FLASH_CR的域LOCK(bit7),定义libopencm3/lib/stm32/common/flash_common_f01.c */
  32. /* bootloader程序中可能解锁flash进行固件烧写功能,这里确保flash上锁 */
  33. flash_lock();
  34. /* 关闭systick中断 */
  35. systick_interrupt_disable(); /* 关闭systick中断(libopencm3/lib/cm3/systick.c) */
  36. systick_counter_disable(); /* systick倒计时关闭(libopencm3/lib/cm3/systick.c) */
  37. /* 关闭串口USART2和USB虚拟串口,配置不变 */
  38. /* cfini:串口反向初始化函数,即关闭串口和USB虚拟串口,定义在main_f?.c */
  39. cfini();
  40. /* 关闭时钟源,将所有内部时钟设置为重启后的初始状态 */
  41. /* clock_deinit:时钟反向初始化函数,定义在main_f?.c */
  42. clock_deinit();
  43. /* 反向初始化板载外设(包括USART2、LED控制) */
  44. /* board_deinit:板载外设反向初始化函数,定义在main_f?.c */
  45. board_deinit();
  46. /* 3. 更改向量表地址至飞控固件的向量表 */
  47. /* SCB_VTOR:寄存器,向量表偏移地址,地址0xE000ED08 */
  48. /* APP_LOAD_ADDRESS:飞控固件起始地址。0x08004000(主控FMU),0x08001000(IO协处理器) */
  49. SCB_VTOR = APP_LOAD_ADDRESS;
  50. /* 4. 重置堆栈并跳转至飞控固件 */
  51. do_jump(app_base[0], app_base[1]);
  52. }

7.4 命令处理函数bootloader

主控FMU或IO协处理器的main函数调用jump_to_app函数失败(返回)后,就会调用bootloader函数来与上位机进行通信,bootloader定义在文件bl.c中,它的主要功能如下:

  • 1.(重新)启动systick定时器和中断,周期为1ms
  • 2.若timeout不为0,启动0号时钟(TIMER_BL_WAIT),周期为timeout,单位ms
  • 3.设置闪烁B/E LED灯(主控FMU为LED701,IO协处理器为LED703)
  • 4.进入命令处理循环
    • 4.1 关闭ACT LED灯(主控FMU没有,IO协处理器为LED705)
    • 4.2 死循环读取串口数据(1字节),只有读到数据方可跳出循环;此处判断timeout有效且到时函数返回----------------------------------->bootloader函数返回
    • 4.3 读到串口数据(命令),开启ACT LED灯
    • 4.4 处理获取到的指令字
      • (1)若为同步命令PROTO_GET_SYNC
        • 指令非法跳转到cmd_bad
        • 运行正常跳转4.5
      • (2)若为获取设备ID命令PROTO_GET_DEVICE
        • 指令非法跳转到cmd_bad
        • 运行正常输出信息并跳转到4.5
      • (3)若为擦除flash与准备烧写飞控固件指令PROTO_CHIP_ERASE
        • 指令非法跳转到cmd_bad
        • 芯片版本错误跳转到bad_silicon
        • 运行错误跳转到cmd_fail
        • 运行正常跳转到4.5
      • (4)若为flash烧写命令PROTO_PROG_MULTI
        • 指令非法跳转到cmd_bad
        • 芯片版本错误跳转到bad_silicon
        • 运行错误跳转到cmd_fail
        • 运行正常跳转到4.5
      • (5)若为CRC32校验命令PROTO_GET_CRC
        • 指令非法跳转到cmd_bad
        • 运行正常输出校验值并跳转到4.5
      • (6)若为读取OTP区域命令PROTO_GET_OTP
        • 指令非法跳转到cmd_bad
        • 运行正常输出OTP区信息并跳转到4.5
      • (7)若为获取MCU的UDID PROTO_GET_SN
        • 指令非法跳转到cmd_bad
        • 运行正常输出UDID并跳转到4.5
      • (8)若为获取芯片ID和版本信息PROTO_GET_CHIP
        • 指令非法跳转到cmd_bad
        • 运行正常输出芯片ID和版本信息并跳转到4.5
      • (9)若为获取芯片描述信息PROTO_GET_CHIP_DES
        • 指令非法跳转到cmd_bad
        • 运行正常输出芯片描述信息(长度 信息本身)并跳转到4.5
      • (10)若为设置飞控固件启动延时PROTO_SET_DELAY
        • 指令非法跳转到cmd_bad
        • 运行错误跳转到cmd_fail
        • 运行正常跳转到4.5
      • (11)若为完成烧写并启动飞控固件PROTO_BOOT
        • 指令非法跳转到cmd_bad
        • 运行错误跳转到cmd_fail
        • 运行正常发送PROTO_INSYNC PROTO_OK,等待100ms,函数返回----------------------------------->bootloader函数返回
      • (12)若为输出调试信息(命令暂未完成)PROTO_DEBUG
        • 跳转至4.5
      • 若为其他指令字
        • 跳转至4
    • 4.5 命令处理完毕,运行成功后的处理,发送PROTO_INSYNC PROTO_OK,跳转至4
    • cmd_bad:指令非法,发送PROTO_INSYNC PROTO_INVALID,跳转至4
    • cmd_fail:指令错误,发送PROTO_INSYNC PROTO_FAILED,跳转至4
    • bad_silicon:芯片版本错误,发送PROTO_INSYNC PROTO_BAD_SILICON_REV,跳转至4

bl.c

  1. #define BL_PROTOCOL_VERSION 5 /* Bootloader协议版本信息 */
  2. /* 协议字 */
  3. #define PROTO_INSYNC 0x12 /* 同步字 */
  4. #define PROTO_EOC 0x20 /* 命令终止字 */
  5. /* 回告字(https:///help/errata) */
  6. #define PROTO_OK 0x10 /* 指令运行成功回告字 */
  7. #define PROTO_FAILED 0x11 /* 指令运行错误回告字 */
  8. #define PROTO_INVALID 0x13 /* 非法指令回告字 */
  9. #define PROTO_BAD_SILICON_REV 0x14 /* 主控MCU(F4芯片)版本错误回告字 */
  10. /* 命令字 */
  11. #define PROTO_GET_SYNC 0x21 /* 获取同步命令 */
  12. #define PROTO_GET_DEVICE 0x22 /* 获取设备ID命令 */
  13. #define PROTO_CHIP_ERASE 0x23 /* 擦除flash与准备烧写飞控固件命令 */
  14. #define PROTO_PROG_MULTI 0x27 /* 烧写flash命令 */
  15. #define PROTO_GET_CRC 0x29 /* 计算CRC32校验命令 */
  16. #define PROTO_GET_OTP 0x2a /* 读取OTP区域某地址的1字节 */
  17. #define PROTO_GET_SN 0x2b /* 读取MCU的UDID(Unique Device ID) */
  18. #define PROTO_GET_CHIP 0x2c /* 读取芯片ID和版本 */
  19. #define PROTO_SET_DELAY 0x2d /* 设置飞控固件启动等待时间 */
  20. #define PROTO_GET_CHIP_DES 0x2e /* 获取芯片描述信息 */
  21. #define PROTO_BOOT 0x30 /* 完成烧写并启动飞控固件 */
  22. #define PROTO_DEBUG 0x31 /* 输出调试信息(未完成) */
  23. /* 命令PROTO_GET_DEVICE后跟的参数 */
  24. #define PROTO_DEVICE_BL_REV 1 /* Bootloader协议版本 */
  25. #define PROTO_DEVICE_BOARD_ID 2 /* 板载处理器编号 */
  26. #define PROTO_DEVICE_BOARD_REV 3 /* 修订版本 */
  27. #define PROTO_DEVICE_FW_SIZE 4 /* 飞控固件大小的最大值 */
  28. #define PROTO_DEVICE_VEC_AREA 5 /* 飞控固件向量表7-10项的函数地址(debug_monitor、sv_call、pend_sv、systick) */
  29. /* 全局变量 */
  30. volatile unsigned timer[NTIMERS]; /* NTIMERS:值4,通用倒计时器数量,定义在bl.h;timer:通用计时器数组变量 */
  31. static enum led_state {LED_BLINK, LED_ON, LED_OFF} _led_state; /* _led_state:LED状态枚举型变量 */
  32. static uint8_t bl_type; /* bl_type:接口类型静态全局变量,被bootloader函数定义 */
  33. static uint8_t last_input; /* last_input:静态全局变量,上次获取串口数据的方式 */
  34. static volatile unsigned cin_count; /* cin_count:串口和USB虚拟串口接收到的数据数量,全局变量 */
  35. /* BL_PROTOCOL_VERSION:值5,表示Bootloader协议版本 */
  36. static const uint32_t bl_proto_rev = BL_PROTOCOL_VERSION; /* bl_proto_rev:全局变量,存储Bootloader的版本号,是获取设备ID命令参数PROTO_DEVICE_BL_REV的回告值 */
  37. /* 根据输入设置LED状态的函数,被bootloader函数调用,功能如下: */
  38. /* 若输入为LED_OFF,则关闭B/E LED灯 */
  39. /* 若输入为LED_ON,则关闭B/E LED灯 */
  40. /* 若输入为LED_BLINK,则2号计时器(TIMER_LED)清零,systick中断函数立即,准备闪烁B/E LED灯 */
  41. static void led_set(enum led_state state)
  42. {
  43. /* 更改全局变量_led_state为当前LED状态 */
  44. _led_state = state;
  45. /* LED_OFF:值2,led_state枚举型,定义在bl.c */
  46. /* LED_ON:值1,led_state枚举型,定义在bl.c */
  47. /* LED_BLINK:值0,led_state枚举型,定义在bl.c */
  48. /* LED_BOOTLOADER:值2,定义在bl.h */
  49. /* TIMER_LED:值2,定义在bl.h */
  50. /* timer:通用计时器数组变量,定义在bl.c */
  51. /* led_off:关闭LED灯,定义在main_f?.c */
  52. /* led_on:开启LED灯,定义在main_f?.c */
  53. switch (state) {
  54. case LED_OFF:
  55. led_off(LED_BOOTLOADER);
  56. break;
  57. case LED_ON:
  58. led_on(LED_BOOTLOADER);
  59. break;
  60. case LED_BLINK:
  61. timer[TIMER_LED] = 0;
  62. break;
  63. }
  64. }
  65. /* systick时钟的中断处理函数,向量表中systick域的赋值在libopencm3/lib/cm3/vector.c */
  66. void sys_tick_handler(void)
  67. {
  68. unsigned i;
  69. /* tick到来,各时钟减1 */
  70. /* NTIMERS:值4,通用倒计时器数量,定义在bl.h */
  71. /* timer:通用计时器数组变量,定义在bl.c */
  72. for (i = 0; i < NTIMERS; i )
  73. if (timer[i] > 0) {
  74. timer[i]--;
  75. }
  76. /* 处理B/E LED(主控FMU为LED701,IO协处理器为LED703)闪烁功能 */
  77. /* 时钟设为50ms,每个周期就是100ms。 */
  78. /* _led_state:全局LED状态枚举(led_state枚举型)变量,存储B/E LED的状态,定义在bl.c */
  79. /* TIMER_LED:值2,定义在bl.h */
  80. /* LED_BLINK:值0,led_state枚举型,定义在bl.c */
  81. /* LED_BOOTLOADER:值2,定义在bl.h */
  82. /* led_toggle:使输入的LED反向,定义在main_f?.c */
  83. if ((_led_state == LED_BLINK) && (timer[TIMER_LED] == 0)) {
  84. led_toggle(LED_BOOTLOADER);
  85. timer[TIMER_LED] = 50;
  86. }
  87. }
  88. /* 延时函数,被bootloader函数调用 */
  89. void delay(unsigned msec)
  90. {
  91. /* timer:通用计时器数组变量,定义在bl.c */
  92. /* TIMER_DELAY:值3,定义在bl.h */
  93. timer[TIMER_DELAY] = msec;
  94. /* 若TIMER_DELAY时钟未到,一直停留在此函数中,确保延时 */
  95. while (timer[TIMER_DELAY] > 0);
  96. }
  97. /* 获取串口数据函数,可为USART和USB虚拟串口 */
  98. inline int cin(void)
  99. {
  100. #if INTERFACE_USB /* 对主控FMU,宏INTERFACE_USART定义为1,代码有效,hw_config.h */
  101. /* 对IO协处理器,宏INTERFACE_USART定义为0,代码无效,hw_config.h */
  102. /* 通过串口获取调试数据,若返回的数据值有效(>=0),则返回获取的数据值,并更新全局变量last_input */
  103. /* NONE:值0,枚举类,定义在bl.h */
  104. /* USB:值2,枚举类,定义在bl.h */
  105. /* bl_type:输入接口类型全局静态变量,被bootloader函数赋值 */
  106. /* last_input:全局变量上次获取串口数据的方式 */
  107. /* usb_cin:从USB模拟串口读入1个字,定义在cdcacm.c */
  108. if (bl_type == NONE || bl_type == USB) {
  109. int usb_in = usb_cin();
  110. if (usb_in >= 0) {
  111. last_input = USB;
  112. return usb_in;
  113. }
  114. }
  115. #endif
  116. #if INTERFACE_USART /* 宏INTERFACE_USART定义为1,代码有效,hw_config.h */
  117. /* 通过串口获取调试数据,若返回的数据值有效(>=0),则返回获取的数据值,并更新全局变量last_input */
  118. /* NONE:值0,枚举类,定义在bl.h */
  119. /* USART:值1,枚举类,定义在bl.h */
  120. /* bl_type:输入接口类型全局静态变量,被bootloader函数赋值 */
  121. /* last_input:全局变量上次获取串口数据的方式 */
  122. /* uart_cin:从串口读入1个字,定义在usart.c */
  123. if (bl_type == NONE || bl_type == USART) {
  124. int uart_in = uart_cin();
  125. if (uart_in >= 0) {
  126. last_input = USART;
  127. return uart_in;
  128. }
  129. }
  130. #endif
  131. return -1; /* 返回错误 */
  132. }
  133. /* 串口或模拟串口输出特定内容的函数 */
  134. inline void cout(uint8_t *buf, unsigned len)
  135. {
  136. #if INTERFACE_USB /* 对主控FMU,宏INTERFACE_USART定义为1,代码有效,hw_config.h */
  137. /* 对IO协处理器,宏INTERFACE_USART定义为0,代码无效,hw_config.h */
  138. /* USB:值2,枚举类,定义在bl.h */
  139. /* bl_type:启动接口类型全局静态变量,被bootloader函数赋值 */
  140. /* usb_cout:USB模拟串口输出特定内容的函数,定义在usart.c */
  141. if (bl_type == USB) {
  142. usb_cout(buf, len);
  143. }
  144. #endif
  145. #if INTERFACE_USART /* 宏INTERFACE_USART定义为1,代码有效,hw_config.h */
  146. /* USART:值1,枚举类,定义在bl.h */
  147. /* bl_type:启动接口类型全局静态变量,被bootloader函数赋值 */
  148. /* uart_cout:USART串口输出特定内容的函数,定义在usart.c */
  149. if (bl_type == USART) {
  150. uart_cout(buf, len);
  151. }
  152. #endif
  153. }
  154. /* 发送同步命令函数,是bootloader函数中命令运行成功后调用的函数 */
  155. static void sync_response(void)
  156. {
  157. /* PROTO_INSYNC:值0x12,同步指令字,定义在bl.c */
  158. /* PROTO_OK:值0x10,指令运行成功回告字,定义在bl.c */
  159. /* cout:串口或模拟串口输出特定内容的函数,定义在bl.c */
  160. uint8_t data[] = {
  161. PROTO_INSYNC,
  162. PROTO_OK
  163. };
  164. cout(data, sizeof(data));
  165. }
  166. /* 当MCU型号错误处理函数,串口或USB模拟串口输出非法响应(PROTO_INSYNC PROTO_BAD_SILICON_REV),是bootloader函数中bad_silicon标号对应的函数 */
  167. #if defined(TARGET_HW_PX4_FMU_V4) /* 对主控FMU,TARGET_HW_PX4_FMU_V4定义为1,代码有效,Makefile.f4 */
  168. /* 对IO协处理器,TARGET_HW_PX4_FMU_V4未定义,代码无效 */
  169. static void bad_silicon_response(void)
  170. {
  171. /* PROTO_INSYNC:值0x12,同步指令字,定义在bl.c */
  172. /* PROTO_BAD_SILICON_REV:值0x14,主控MCU型号错误命令字,定义在bl.c */
  173. /* cout:串口或模拟串口输出特定内容的函数,定义在bl.c */
  174. uint8_t data[] = {
  175. PROTO_INSYNC,
  176. PROTO_BAD_SILICON_REV
  177. };
  178. cout(data, sizeof(data));
  179. }
  180. #endif
  181. /* 当指令非法时的处理函数,串口或USB模拟串口输出非法响应(PROTO_INSYNC PROTO_INVALID),是bootloader函数中cmd_bad标号对应的函数 */
  182. static void invalid_response(void)
  183. {
  184. /* PROTO_INSYNC:值0x12,同步指令字,定义在bl.c */
  185. /* PROTO_INVALID:值0x13,非法指令字,定义在bl.c */
  186. /* cout:串口或模拟串口输出特定内容的函数,定义在bl.c */
  187. uint8_t data[] = {
  188. PROTO_INSYNC,
  189. PROTO_INVALID
  190. };
  191. cout(data, sizeof(data));
  192. }
  193. /* 当指令运行失败时的处理函数,串口或USB模拟串口输出运行失败响应(PROTO_INSYNC PROTO_FAILED),是bootloader函数中cmd_fail标号对应的函数 */
  194. static void failure_response(void)
  195. {
  196. /* PROTO_INSYNC:值0x12,同步指令字,定义在bl.c */
  197. /* PROTO_FAILED:值0x11,运行失败指令字,定义在bl.c */
  198. /* cout:串口或模拟串口输出特定内容的函数,定义在bl.c */
  199. uint8_t data[] = {
  200. PROTO_INSYNC,
  201. PROTO_FAILED
  202. };
  203. cout(data, sizeof(data));
  204. }
  205. /* 在给定的时间timeout内读到串口数据,则返回数据;若超时返回-1 */
  206. static int cin_wait(unsigned timeout)
  207. {
  208. int c = -1;
  209. /* 设置1号定时器(TIMER_CIN)周期为输入值(此处为0) */
  210. /* TIMER_CIN:值1,定义在bl.h */
  211. /* timer:通用计时器数组变量,定义在bl.c */
  212. timer[TIMER_CIN] = timeout;
  213. /* 一直读取串口获得数据。若要返回,或者读取到数据,或者若定时器(TIMER_CIN)到时。 */
  214. /* cin_count:串口和USB虚拟串口接收到的数据总量,定义在bl.c */
  215. /* cin:获取串口数据函数,定义在bl.c */
  216. do {
  217. c = cin();
  218. if (c >= 0) {
  219. cin_count ;
  220. break;
  221. }
  222. } while (timer[TIMER_CIN] > 0);
  223. return c;
  224. }
  225. /* 串口或USB模拟串口输出1个字 */
  226. static void cout_word(uint32_t val)
  227. {
  228. /* cout:串口或模拟串口输出特定内容的函数,定义在bl.c */
  229. cout((uint8_t *)&val, 4);
  230. }
  231. /* 串口或USB模拟串口读入1个字,具有超时检测功能 */
  232. static int cin_word(uint32_t *wp, unsigned timeout)
  233. {
  234. /* 由于USB串口每次只能读入1字节,而结果需要的是1个字,故定义联合体u长度4字节适用于两种操作 */
  235. union {
  236. uint32_t w;
  237. uint8_t b[4];
  238. } u;
  239. /* 连续4此读取串口,并将其拼接成1个字(小端);若读取过程中超时则返回-1 */
  240. /* cin_wait:在给定的时间timeout内读到串口数据,则返回数据;若超时返回-1,定义在bl.c */
  241. for (unsigned i = 0; i < 4; i ) {
  242. int c = cin_wait(timeout);
  243. if (c < 0) {
  244. return c;
  245. }
  246. u.b[i] = c & 0xff;
  247. }
  248. *wp = u.w;
  249. return 0;
  250. }
  251. /* 在给定的等待时间内,下一条获取到的命令是否为终止字PROTO_EOC;若是返回真,不是返回假 */
  252. inline static bool wait_for_eoc(unsigned timeout)
  253. {
  254. /* PROTO_EOC:值0x20,命令终止字,定义在bl.c */
  255. /* cin_wait:在给定的输入时间内读到串口数据,则返回数据;若超时返回-1,bl.c */
  256. return cin_wait(timeout) == PROTO_EOC;
  257. }
  258. /* 给出数据src的crc32校验结果 */
  259. static uint32_t crc32(const uint8_t *src, unsigned len, unsigned state)
  260. {
  261. static uint32_t crctab[256];
  262. /* 检查是否已生成crc32校验表(按照定义肯定是未生成的),若未生成则立刻生成 */
  263. if (crctab[1] == 0) {
  264. for (unsigned i = 0; i < 256; i ) {
  265. uint32_t c = i;
  266. for (unsigned j = 0; j < 8; j ) {
  267. if (c & 1) {
  268. c = 0xedb88320U ^ (c >> 1);
  269. } else {
  270. c = c >> 1;
  271. }
  272. }
  273. crctab[i] = c;
  274. }
  275. }
  276. /* 对于从src开始的len长度字节进行crc32校验 */
  277. for (unsigned i = 0; i < len; i ) {
  278. state = crctab[(state ^ src[i]) & 0xff] ^ (state >> 8);
  279. }
  280. return state;
  281. }
  282. void bootloader(unsigned timeout)
  283. {
  284. /* bl_type:全局接口类型变量。NONE:值0,枚举类,定义在bl.h */
  285. /* 若使bootloader实际上能够起作用,必须设置bl_type的类型,不能按照默认值NONE */
  286. bl_type = NONE;
  287. uint32_t address = board_info.fw_size; /* flash当前编写逻辑地址变量,初始化为board_info.fw_size,确保在不执行擦除条件下无法编写 */
  288. uint32_t first_word = 0xffffffff;
  289. /* 1.(重新)启动systick定时器和中断,周期为1ms */
  290. /* STK_CSR_CLKSOURCE_AHB:值1<<2,选取AHB作为systick的时钟源,libopencm3/include/libopencm3/cm3/systick.h */
  291. /* board_info.systick_mhz:值168(主控FMU,main_f4.c),24(IO协处理器,main_f1.c) */
  292. /* systick_set_clocksource:选取systick的时钟源,libopencm3/lib/cm3/systick.c */
  293. /* systick_set_reload:设置systick时钟倒计时(寄存器STK_VAL),libopencm3/lib/cm3/systick.c */
  294. /* systick_interrupt_enable:开启systick中断,libopencm3/lib/cm3/systick.c */
  295. /* systick_counter_enable:systick倒计时开启,libopencm3/lib/cm3/systick.c */
  296. systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
  297. systick_set_reload(board_info.systick_mhz * 1000); /* 1ms tick, magic number */
  298. systick_interrupt_enable();
  299. systick_counter_enable();
  300. /* 2. 若timeout不为0,启动0号时钟(TIMER_BL_WAIT),周期为timeout,单位ms */
  301. /* timeout为0则不启动时钟,永远停留在本函数中 */
  302. /* TIMER_BL_WAIT:值0,定义在bl.h */
  303. /* timer:unsigned型全局数组,成员有4个,定义在bl.c */
  304. if (timeout) {
  305. timer[TIMER_BL_WAIT] = timeout;
  306. }
  307. /* 3. 设置闪烁B/E LED灯(主控FMU为LED701,IO协处理器为LED703) */
  308. /* B/E LED灯闪烁表示程序处于闲置状态,通过systick时钟的中断处理函数来实现 */
  309. /* LED_BLINK:值0,led_state枚举成员,定义在bl.c */
  310. /* led_set:LED灯状态设置函数,定义在bl.c */
  311. led_set(LED_BLINK);
  312. /* 4. 进入命令处理循环 */
  313. while (true) {
  314. volatile int c;
  315. int arg;
  316. static union {
  317. uint8_t c[256];
  318. uint32_t w[64];
  319. } flash_buffer;
  320. /* 4.1 关闭ACT LED灯(主控FMU没有,IO协处理器为LED705) */
  321. /* LED_ACTIVITY:值1,led_state枚举成员,定义在bl.c */
  322. /* led_off:关闭LED灯,定义在main_f?.c */
  323. led_off(LED_ACTIVITY);
  324. /* 4.2 死循环读取串口数据(1字节),只有读到数据方可跳出循环 */
  325. do {
  326. /* 若timeout非零,0号时钟(TIMER_BL_WAIT)倒计时至0,则timeout时间到返回。 */
  327. /* TIMER_BL_WAIT:值0,定义在bl.h */
  328. /* timeout:bootloader跳转到飞控固件所需等待的时间,bootloader函数输入 */
  329. /* timer:通用计时器数组变量,定义在bl.c */
  330. if (timeout && !timer[TIMER_BL_WAIT]) {
  331. return;
  332. }
  333. /* 不等待,立即读取串口一个数据(cin_wait输入为0) */
  334. /* cin_wait:在给定的输入时间timeout内读到串口数据,则返回数据;若超时返回-1,定义在bl.c */
  335. c = cin_wait(0);
  336. } while (c < 0);
  337. /* 4.3 读到串口数据(命令),开启ACT LED灯 */
  338. /* LED_ACTIVITY:值1,led_state枚举成员,定义在bl.c */
  339. /* led_off:开启LED灯,定义在main_f?.c */
  340. led_on(LED_ACTIVITY);
  341. /* 4.4 处理获取到的命令字 */
  342. switch (c) {
  343. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  344. /* (1) 同步命令PROTO_GET_SYNC(0x21),命令结构PROTO_GET_SYNC PROTO_EOC */
  345. /* * 收到PROTO_GET_SYNC的2ms内未接收到命令终止字,则进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  346. /* * 运行成功,(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  347. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  348. /* 接收到同步命令PROTO_GET_SYNC的2ms内位需收到终止字PROTO_EOC(0x20),否则进行错误指令处理 */
  349. /* PROTO_GET_SYNC:值0x21,同步命令字,定义在bl.c */
  350. /* PROTO_EOC:值0x20,命令终止字,定义在bl.c */
  351. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  352. case PROTO_GET_SYNC:
  353. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为终止字PROTO_EOC;若是返回真,不是返回假 */
  354. /* cmd_bad:标号 */
  355. if (!wait_for_eoc(2)) {
  356. goto cmd_bad;
  357. }
  358. break;
  359. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  360. /* (2)获取设备ID命令PROTO_GET_DEVICE(0x22),它后面通常还会跟随一个参数(用于指示具体获取设备的哪个内容),命令结构:PROTO_GET_DEVICE arg(1byte) PROTO_EOC */
  361. /* * 若为无效参数或参数后2ms内未接收到命令终止字,则进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  362. /* * 若为PROTO_DEVICE_BL_REV(值1),则返回bootloader协议版本号BL_PROTOCOL_VERSION(值5),回告结构:rev(4byte) (PROTO_INSYNC PROTO_OK) */
  363. /* * 若为PROTO_DEVICE_BOARD_ID(值2),则返回板载处理器编号board_info.board_type(BOARD_TYPE,主控FMU值9,IO协处理器值10),回告结构:board_type(4byte) (PROTO_INSYNC PROTO_OK) */
  364. /* * 若为PROTO_DEVICE_BOARD_REV(值3),则返回修订版本board_info.board_rev(值0),回告结构:board_rev(4byte) (PROTO_INSYNC PROTO_OK) */
  365. /* * 若为PROTO_DEVICE_FW_SIZE(值4),则返回飞控固件的最大值(单位KB)board_info.fw_size(主控值2000,IO协处理器APP_SIZE_MAX=0xf000),回告结构:fw_size(4byte) (PROTO_INSYNC PROTO_OK) */
  366. /* * 若为PROTO_DEVICE_VEC_AREA(值5),则返回飞控固件向量表7-10项的函数地址(debug_monitor、sv_call、pend_sv、systick),回告结构:vectors(4*4byte) (PROTO_INSYNC PROTO_OK) */
  367. /* * 若为其他参数,则进行非法指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  368. /* * 运行成功,(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  369. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  370. case PROTO_GET_DEVICE: /* PROTO_GET_DEVICE:值0x22,获取设备ID命令,定义在bl.c */
  371. /* cin_wait:在给定的1s时间内读到串口数据,则返回数据;若超时返回-1,定义在bl.c */
  372. arg = cin_wait(1000);
  373. /* 判定若获取的参数无效(<0)或获取到命令终止,则返回报错 */
  374. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  375. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  376. if (arg < 0) {
  377. goto cmd_bad;
  378. }
  379. if (!wait_for_eoc(2)) {
  380. goto cmd_bad;
  381. }
  382. /* 判定命令PROTO_GET_DEVICE的参数 */
  383. /* PROTO_DEVICE_BL_REV:值1,命令PROTO_GET_DEVICE的参数,表示获取Bootloader协议版本,定义在bl.c */
  384. /* PROTO_DEVICE_BOARD_ID:值2,命令PROTO_GET_DEVICE的参数,表示获取板载处理器编号(对应board_info.board_type=BOARD_TYPE),定义在bl.c */
  385. /* PROTO_DEVICE_BOARD_REV:值3,命令PROTO_GET_DEVICE的参数,表示获取修订版本(对应board_info.board_rev=0),定义在bl.c */
  386. /* PROTO_DEVICE_FW_SIZE:值4,命令PROTO_GET_DEVICE的参数,表示飞控固件的最大值(对应board_info.fw_size),定义在bl.c */
  387. /* PROTO_DEVICE_VEC_AREA:值5,命令PROTO_GET_DEVICE的参数,表示获取飞控固件向量表7-10项的函数地址(debug_monitor、sv_call、pend_sv、systick),定义在bl.c */
  388. /* bl_proto_rev:全局变量,存储Bootloader的版本号(BL_PROTOCOL_VERSION值5),是获取设备ID命令参数PROTO_DEVICE_BL_REV的回告值,定义在bl.c */
  389. /* cout:串口(包括USB虚拟串口)输出特定内容的函数,定义在bl.c */
  390. /* flash_func_read_word:读取flash特定地址1个字的函数,定义在main_f?.c */
  391. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  392. switch (arg) {
  393. case PROTO_DEVICE_BL_REV:
  394. cout((uint8_t *)&bl_proto_rev, sizeof(bl_proto_rev));
  395. break;
  396. case PROTO_DEVICE_BOARD_ID:
  397. cout((uint8_t *)&board_info.board_type, sizeof(board_info.board_type));
  398. break;
  399. case PROTO_DEVICE_BOARD_REV:
  400. cout((uint8_t *)&board_info.board_rev, sizeof(board_info.board_rev));
  401. break;
  402. case PROTO_DEVICE_FW_SIZE:
  403. cout((uint8_t *)&board_info.fw_size, sizeof(board_info.fw_size));
  404. break;
  405. case PROTO_DEVICE_VEC_AREA:
  406. for (unsigned p = 7; p <= 10; p ) {
  407. uint32_t bytes = flash_func_read_word(p * 4);
  408. cout((uint8_t *)&bytes, sizeof(bytes));
  409. }
  410. break;
  411. default:
  412. goto cmd_bad;
  413. }
  414. break;
  415. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  416. /* (3)擦除flash与准备烧写飞控固件指令PROTO_CHIP_ERASE(0x23),命令结构:PROTO_CHIP_ERASE PROTO_EOC */
  417. /* * 收到PROTO_CHIP_ERASE的2ms内未收到命令终止字,则进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */ */
  418. /* * 若为主控FMU芯片(F4),调用check_silicon函数判断MCU版本是否正确(是否为revision 3) */
  419. /* * 若主控FMU版本不正确(revision 3以下),跳转进行bad_silicon处理,回告结构:(PROTO_INSYNC PROTO_BAD_SILICON_REV) */
  420. /* * 点亮B/E灯,指示正在擦除flash,逐段、页(sector)擦除flash */
  421. /* * 关闭B/E灯,指示擦除flash已完成,取每KB的首字检查flash擦除操作是否成功 */
  422. /* * 若flash擦除操作失败,进行指令运行失败处理,回告结构:(PROTO_INSYNC PROTO_FAILED) */
  423. /* * 重置flash编写逻辑地址变量address为0,恢复B/E灯闪烁 */
  424. /* * 运行成功,(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  425. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  426. case PROTO_CHIP_ERASE: /* PROTO_CHIP_ERASE:值0x23,擦除flash与准备烧写飞控固件命令 */
  427. /* 接收到PROTO_CHIP_ERASE命令后2ms内应该接收到终止字PROTO_EOC(0x20),否则进行非法指令处理 */
  428. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  429. /* cmd_bad:指令非法处理函数的标号,在bootloader函数末尾 */
  430. if (!wait_for_eoc(2)) {
  431. goto cmd_bad;
  432. }
  433. #if defined(TARGET_HW_PX4_FMU_V4) /* 对主控FMU,宏TARGET_HW_PX4_FMU_V4定义为1,代码有效,Makefile.F4 */
  434. /* 对UI协处理器,宏TARGET_HW_PX4_FMU_V4未定义,代码无效 */
  435. /* check_silicon:函数自动辨识MCU的版本,默认STM32F427的revision 3返回0,其余返回-1 */
  436. /* bad_silicon:MCU版本错误处理函数的标号,在bootloader函数末尾 */
  437. if (check_silicon()) {
  438. goto bad_silicon;
  439. }
  440. #endif
  441. /* 点亮B/E灯 */
  442. /* LED_ON: 值1,led_state枚举成员,定义在bl.c*/
  443. /* led_set:LED灯状态设置函数,定义在bl.c */
  444. led_set(LED_ON);
  445. /* flash_unlock:解锁flash的库函数,定义在libopencm3/lib/stm32/common/flash_common_f234.c(主控FMU),libopencm3/lib/stm32/common/flash_common_f01.c(IO协处理器) */
  446. flash_unlock();
  447. /* 逐段擦除flash */
  448. /* flash_func_sector_size:返回MCU的flash在某sector的大小值,若无此sector则返回0,定义在main_f?.c */
  449. /* flash_func_erase_sector: */
  450. for (int i = 0; flash_func_sector_size(i) != 0; i ) {
  451. flash_func_erase_sector(i);
  452. }
  453. /* 关闭B/E灯 */
  454. /* LED_OFF: 值2,led_state枚举成员,定义在bl.c*/
  455. /* led_set:LED灯状态设置函数,定义在bl.c */
  456. led_set(LED_OFF);
  457. /* 取每KB的首字检查flash擦除操作是否正确,即是否有擦除成功 */
  458. /* board_info.fw_size:飞控固件的最大值(单位KB) */
  459. /* flash_func_read_word:读取某flash地址的数据,定义在main_f?.c */
  460. /* cmd_fail:指令运行错误处理的标号,在bootloader函数末尾 */
  461. for (address = 0; address < board_info.fw_size; address = 4)
  462. if (flash_func_read_word(address) != 0xffffffff) {
  463. goto cmd_fail;
  464. }
  465. address = 0;
  466. /* B/E灯闪烁 */
  467. /* LED_BLINK: 值0,led_state枚举成员,定义在bl.c*/
  468. /* led_set:LED灯状态设置函数,定义在bl.c */
  469. led_set(LED_BLINK);
  470. break;
  471. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  472. /* (4)flash烧写命令PROTO_PROG_MULTI(0x27),命令结构:PROTO_PROG_MULTI arg(1byte) c(len bytes) PROTO_EOC */
  473. /* * 收到PROTO_PROG_MULTI的50ms内未收到第一个参数(长度arg),按照错误命令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  474. /* * 若收到的第一个参数(长度arg)不是4字节对齐,按照错误命令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  475. /* * 若当前编写地址(address) 第一个参数(长度arg)超出飞控固件最大范围board_info.fw_size,按照错误命令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  476. /* * 若收到的第一个参数(长度arg)超出缓冲区长度,按照错误命令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  477. /* * 根据长度arg逐字节接收数据,并存放在缓冲区flash_buffer中;若其中任何1个字节超时1s,按照错误命令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  478. /* * 接收完有效数据后,若200ms内未接收到命令终止字(PROTO_EOC),按照错误命令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  479. /* * 若当前编写地址address为0,则将烧写缓冲区flash_buffer首个32位保存在变量first_word中,并将缓冲区首改为0xFFFFFFFF */
  480. /* * 对于主控FMU,若MCU版本检查失败,按照MCU版本错误处理,回告结构:(PROTO_INSYNC PROTO_BAD_SILICON_REV) */
  481. /* * 逐字烧写flash地址,若写入与读出不一致,进行命令失败处理,回告结构:(PROTO_INSYNC PROTO_FAILED) */
  482. /* * 运行成功,(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  483. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  484. case PROTO_PROG_MULTI: /* PROTO_PROG_MULTI:值0x27,烧写flash命令 */
  485. /* 50ms内获取第一个参数 */
  486. /* cin_wait:在给定的时间内读到串口数据,则返回数据;若超时返回-1,定义在bl.c */
  487. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  488. /* board_info.fw_size:飞控固件的最大值 */
  489. /* flash_buffer:共同体,代表接收缓冲区(256字节或32字) */
  490. arg = cin_wait(50);
  491. if (arg < 0) {
  492. goto cmd_bad;
  493. }
  494. if (arg % 4) {
  495. goto cmd_bad;
  496. }
  497. if ((address arg) > board_info.fw_size) {
  498. goto cmd_bad;
  499. }
  500. if (arg > sizeof(flash_buffer.c)) {
  501. goto cmd_bad;
  502. }
  503. /* 逐字节接收串口数据并存放在缓冲区内,数据延时不超过1s */
  504. /* cin_wait:在给定的时间内读到串口数据,则返回数据;若超时返回-1,定义在bl.c */
  505. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  506. /* flash_buffer:共同体,代表接收缓冲区(256字节或32字) */
  507. for (int i = 0; i < arg; i ) {
  508. c = cin_wait(1000);
  509. if (c < 0) {
  510. goto cmd_bad;
  511. }
  512. flash_buffer.c[i] = c;
  513. }
  514. /* 接收完有效数据后,200ms内必须接收到命令终止字 */
  515. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  516. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  517. if (!wait_for_eoc(200)) {
  518. goto cmd_bad;
  519. }
  520. /* 若当前编写逻辑地址为0,(对主控FMU,若MCU版本检查失败,跳转到bad_silicon,进行错误MCU版本处理),则将烧写缓冲区flash_buffer首个32位保存在变量first_word中,并将缓冲区首改为0xFFFFFFFF */
  521. /* check_silicon:函数自动辨识MCU的版本,默认STM32F427的revision 3返回0,其余返回-1 */
  522. /* bad_silicon:MCU版本错误处理函数的标号,在bootloader函数末尾 */
  523. /* first_word:首字保存变量,定义在bootloader函数中 */
  524. /* flash_buffer:共同体,代表接收缓冲区(256字节或32字) */
  525. if (address == 0) {
  526. #if defined(TARGET_HW_PX4_FMU_V4) /* 对主控FMU,宏TARGET_HW_PX4_FMU_V4定义为1,代码有效,Makefile.f4 */
  527. /* 对IO协处理器,宏TARGET_HW_PX4_FMU_V4未定义,代码无效 */
  528. if (check_silicon()) {
  529. goto bad_silicon;
  530. }
  531. #endif
  532. first_word = flash_buffer.w[0];
  533. flash_buffer.w[0] = 0xffffffff;
  534. }
  535. /* 逐字烧写flash地址,若写入与读出不一致,跳转到cmd_fail,进行命令失败处理,返回PROTO_INSYNC PROTO_FAILED */
  536. /* flash_func_write_word:烧写flash某地址起始为特定内容,4位烧写,定义在main_f?.c */
  537. /* flash_func_read_word:读取flash某特定地址内容,4位读取,定义在main_f?.c */
  538. /* cmd_fail:指令运行错误处理的标号,在bootloader函数末尾 */
  539. arg /= 4;
  540. for (int i = 0; i < arg; i ) {
  541. flash_func_write_word(address, flash_buffer.w[i]);
  542. if (flash_func_read_word(address) != flash_buffer.w[i]) {
  543. goto cmd_fail;
  544. }
  545. address = 4;
  546. }
  547. break;
  548. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  549. /* (5)CRC32校验命令PROTO_GET_CRC(0x29),命令结构:PROTO_GET_CRC PROTO_EOC */
  550. /* * 收到PROTO_GET_CRC的2ms内未接收到命令终止字,则进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  551. /* * 计算CRC32结果,输出crcsum(4byte),(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  552. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  553. case PROTO_GET_CRC: /* PROTO_GET_CRC:值0x29,CRC32校验命令 */
  554. /* 2ms内必须收到命令终止字PROTO_EOC */
  555. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  556. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  557. if (!wait_for_eoc(2)) {
  558. goto cmd_bad;
  559. }
  560. /* 计算烧写区域的CRC32值,注意飞控固件首地址的处理 */
  561. /* board_info.fw_size:飞控固件的最大值 */
  562. /* flash_func_read_word:读取flash某特定地址内容,4位读取,定义在main_f?.c */
  563. /* crc32:计算给定数据的crc32校验结果 */
  564. uint32_t sum = 0;
  565. for (unsigned p = 0; p < board_info.fw_size; p = 4) {
  566. uint32_t bytes;
  567. if ((p == 0) && (first_word != 0xffffffff)) {
  568. bytes = first_word;
  569. } else {
  570. bytes = flash_func_read_word(p);
  571. }
  572. sum = crc32((uint8_t *)&bytes, sizeof(bytes), sum);
  573. }
  574. /* cout_word:串口或模拟串口输出1个字的函数,定义在bl.c */
  575. cout_word(sum);
  576. break;
  577. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  578. /* (6)读取OTP区域命令PROTO_GET_OTP(0x2a),命令结构:PROTO_GET_OTP index(4byte) PROTO_EOC */
  579. /* * 收到PROTO_GET_OTP后未在允许延时(100ms)内收到有效的地址信息,则进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  580. /* * 收到地址后2ms内未收到命令终止字,则进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  581. /* * 运行成功,输出OTP地址数值val(4byte),(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  582. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  583. case PROTO_GET_OTP: /* PROTO_GET_OTP:值0x2a,读取OTP区域某地址的1字节指令 */
  584. uint32_t index = 0;
  585. /* cin_word:串口或USB模拟串口读入1个字,具有超时检测功能,定义在bl.c */
  586. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  587. if (cin_word(&index, 100)) {
  588. goto cmd_bad;
  589. }
  590. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  591. /* cout_word:串口或模拟串口输出1个字的函数,定义在bl.c */
  592. if (!wait_for_eoc(2)) {
  593. goto cmd_bad;
  594. }
  595. cout_word(flash_func_read_otp(index));
  596. break;
  597. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  598. /* (7)获取MCU的UDID(Unique Device ID,或称为序列号)PROTO_GET_SN(0x2b),命令结构PROTO_GET_SN index(4byte) PROTO_EOC */
  599. /* * 收到PROTO_GET_SN后未在允许延时(100ms)内收到有效的地址信息,则进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  600. /* * 收到地址后2ms内未收到命令终止字,则进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  601. /* * 运行成功,输出UDID数值val(4byte),(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  602. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  603. case PROTO_GET_SN: /* PROTO_GET_SN:值0x2b,获取MCU的UDID命令 */
  604. uint32_t index = 0;
  605. /* cin_word:串口或USB模拟串口读入1个字,具有超时检测功能,定义在bl.c */
  606. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  607. if (cin_word(&index, 100)) {
  608. goto cmd_bad;
  609. }
  610. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  611. /* cout_word:串口或模拟串口输出1个字的函数,定义在bl.c */
  612. if (!wait_for_eoc(2)) {
  613. goto cmd_bad;
  614. }
  615. cout_word(flash_func_read_sn(index));
  616. break;
  617. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  618. /* (8)获取芯片ID和版本信息PROTO_GET_CHIP(0x2c),命令结构PROTO_GET_CHIP PROTO_EOC */
  619. /* * 收到命令PROTO_GET_CHIP后2ms内未收到命令终止字,进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  620. /* * 运行成功,输出芯片ID和版本信息数值val(4byte),(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  621. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  622. case PROTO_GET_CHIP: /* PROTO_GET_CHIP:值0x2c,获取芯片ID和版本信息 */
  623. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  624. /* get_mcu_id:读取寄存器DBGMCU_IDCODE值,表示芯片ID和版本 */
  625. /* cout_word:串口或模拟串口输出1个字的函数,定义在bl.c */
  626. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  627. if (!wait_for_eoc(2)) {
  628. goto cmd_bad;
  629. }
  630. cout_word(get_mcu_id());
  631. break;
  632. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  633. /* (9)获取芯片描述信息PROTO_GET_CHIP_DES(0x2e),命令结构PROTO_GET_CHIP_DES PROTO_EOC */
  634. /* * 收到命令PROTO_GET_CHIP后2ms内未收到命令终止字,进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  635. /* * 运行成功,输出芯片描述信息len(4byte) buffer(len byte),(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  636. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  637. case PROTO_GET_CHIP_DES: /* PROTO_GET_CHIP_DES:值0x2e,获取芯片的描述信息 */
  638. /* MAX_DES_LENGTH:值20,芯片描述信息最大长度,定义在bl.c */
  639. uint8_t buffer[MAX_DES_LENGTH];
  640. unsigned len = MAX_DES_LENGTH;
  641. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  642. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  643. if (!wait_for_eoc(2)) {
  644. goto cmd_bad;
  645. }
  646. /* get_mcu_desc:返回MCU的型号和版本描述信息,定义在main_f?.c */
  647. /* cout_word:串口或模拟串口输出1个字的函数,定义在bl.c */
  648. /* cout:串口(包括USB虚拟串口)输出特定内容的函数,定义在bl.c */
  649. len = get_mcu_desc(len, buffer);
  650. cout_word(len);
  651. cout(buffer, len);
  652. break;
  653. #ifdef BOOT_DELAY_ADDRESS /* 对主控FMU,BOOT_DELAY_ADDRESS定义为0x1a0,代码有效,hw_config.h */
  654. /* 对IO协处理器,BOOT_DELAY_ADDRESS未定义,代码无效 */
  655. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  656. /* (10)设置飞控固件启动延时PROTO_SET_DELAY(0x2d),命令结构:PROTO_SET_DELAY v(4byte) PROTO_EOC */
  657. /* * 收到PROTO_SET_DELAY后100ms内未收到延时v,或延时时间为负数,进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  658. /* * 取延时v的最低字节为时延boot_delay有效信息(单位s),若此值大于允许最大值BOOT_DELAY_MAX,进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  659. /* * 收到延时v的2ms内未收到命令终止字,进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  660. /* * 读取BOOT_DELAY_ADDRESS起始的显示延时信息有效性的2个关键字,通过与预定的关键字对比,若不一致则进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  661. /* * 根据延时boot_delay和关键字BOOT_DELAY_SIGNATURE1确定需存放在地址BOOT_DELAY_ADDRESS的数据,并写入以更新延时参数 */
  662. /* * 再次读取地址BOOT_DELAY_ADDRESS的数据与需写入的数据进行对比,若不一致(写入失败),则进行命令失败处理,回告结构:(PROTO_INSYNC PROTO_FAILED) */
  663. /* * 运行成功,(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  664. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  665. case PROTO_SET_DELAY: /* PROTO_SET_DELAY:值0x2d,设置飞控固件启动延时 */
  666. /* cin_wait:在给定的时间内读到串口数据,则返回数据;若超时返回-1,定义在bl.c */
  667. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  668. int v = cin_wait(100);
  669. if (v < 0) {
  670. goto cmd_bad;
  671. }
  672. /* 取v的低字节作为时延boot_delay的有效信息(时延最大255s) */
  673. /* BOOT_DELAY_MAX:值30,飞控固件启动最大时延,定义在bl.c */
  674. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  675. uint8_t boot_delay = v & 0xFF;
  676. if (boot_delay > BOOT_DELAY_MAX) {
  677. goto cmd_bad;
  678. }
  679. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  680. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  681. if (!wait_for_eoc(2)) {
  682. goto cmd_bad;
  683. }
  684. /* 读取BOOT_DELAY_ADDRESS起始的2个字,提取显示延时信息有效性的2个关键字,通过对比来判断信息是否有效 */
  685. /* BUG?sig1的最低字节是延时参数,不应作为判断依据,这里有问题 */
  686. /* BOOT_DELAY_ADDRESS:值0x000001a0,飞控固件启动延时信息在flash中的存储地址,仅对主控FMU有效,定义在hw_config.h */
  687. /* BOOT_DELAY_SIGNATURE1:值0x92c2ecff,飞控固件启动延时信息有效的关键字1,定义在bl.h */
  688. /* BOOT_DELAY_SIGNATURE2:值0xc5057d5d,飞控固件启动延时信息有效的关键字2,定义在bl.h */
  689. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  690. uint32_t sig1 = flash_func_read_word(BOOT_DELAY_ADDRESS);
  691. uint32_t sig2 = flash_func_read_word(BOOT_DELAY_ADDRESS 4);
  692. if (sig1 != BOOT_DELAY_SIGNATURE1 || sig2 != BOOT_DELAY_SIGNATURE2) {
  693. goto cmd_bad;
  694. }
  695. /* 根据延时boot_delay和关键字BOOT_DELAY_SIGNATURE1确定需存放在地址BOOT_DELAY_ADDRESS的数据,并写入 */
  696. /* BOOT_DELAY_SIGNATURE1:值0x92c2ecff,飞控固件启动延时信息有效的关键字1,定义在bl.h */
  697. /* BOOT_DELAY_ADDRESS:值0x000001a0,飞控固件启动延时信息在flash中的存储地址,仅对主控FMU有效,定义在hw_config.h */
  698. /* flash_func_write_word:烧写flash某地址起始为特定内容,4位烧写,定义在main_f?.c */
  699. uint32_t value = (BOOT_DELAY_SIGNATURE1 & 0xFFFFFF00) | boot_delay;
  700. flash_func_write_word(BOOT_DELAY_ADDRESS, value);
  701. /* 再次读取地址BOOT_DELAY_ADDRESS的数据与需写入的数据进行对比,若不一致则跳转到cmd_fail,运行失败 */
  702. /* BOOT_DELAY_ADDRESS:值0x000001a0,飞控固件启动延时信息在flash中的存储地址,仅对主控FMU有效,定义在hw_config.h */
  703. /* flash_func_read_word:读取flash某特定地址内容,4位读取,定义在main_f?.c */
  704. /* cmd_fail:指令运行错误处理的标号,在bootloader函数末尾 */
  705. if (flash_func_read_word(BOOT_DELAY_ADDRESS) != value) {
  706. goto cmd_fail;
  707. }
  708. break;
  709. #endif
  710. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  711. /* (11)完成烧写并启动飞控固件PROTO_BOOT(0x30),命令结构:PROTO_BOOT PROTO_EOC */
  712. /* * 收到启动命令PROTO_BOOT的1s内未收到命令终止字,进行错误指令处理,回告结构:(PROTO_INSYNC PROTO_INVALID) */
  713. /* * 若变量first_word(飞控固件首字保存变量)中有内容 */
  714. /* * 烧写飞控固件首字first_word */
  715. /* * 读取飞控固件首字并与first_word对比,若不同则进行命令失败处理,回告结构:(PROTO_INSYNC PROTO_FAILED) */
  716. /* * 烧写首字成功,将first_word变量置为无效(0xffffffff) */
  717. /* * 运行成功,发送PROTO_INSYNC PROTO_OK,延时100ms,bootloader函数返回 */
  718. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  719. case PROTO_BOOT: /* PROTO_BOOT:值0x30,完成烧写并启动飞控固件 */
  720. /* wait_for_eoc:在给定的时间内,下一条获取到的命令是否为命令终止字PROTO_EOC;若是返回真,不是返回假,定义在bl.c */
  721. /* cmd_bad:错误命令处理函数的标号,在bootloader函数末尾 */
  722. if (!wait_for_eoc(1000)) {
  723. goto cmd_bad;
  724. }
  725. /* 若变量first_word内容有效,烧写飞控固件首字并判断是否烧写成功 */
  726. /* first_word:飞控固件首字保存变量,定义在bootloader函数中 */
  727. /* flash_func_write_word:烧写flash某地址起始为特定内容,4位烧写,定义在main_f?.c */
  728. /* flash_func_read_word:读取flash某特定地址内容,4位读取,定义在main_f?.c */
  729. /* cmd_fail:指令运行错误处理的标号,在bootloader函数末尾 */
  730. if (first_word != 0xffffffff) {
  731. flash_func_write_word(0, first_word);
  732. /* 读取飞控固件首字并与first_word对比,若不同则跳转到cmd_fail,进行命令失败处理 */
  733. if (flash_func_read_word(0) != first_word) {
  734. goto cmd_fail;
  735. }
  736. /* 将first_word变量置为无效(0xffffffff) */
  737. first_word = 0xffffffff;
  738. }
  739. /* sync_response:发送同步命令函数,表示运行成功,定义在bl.c */
  740. /* delay:延时函数,定义在bl.c */
  741. sync_response();
  742. delay(100);
  743. // quiesce and jump to the app
  744. return;
  745. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  746. /* (12)输出调试信息(命令暂未完成)PROTO_DEBUG(0x31),直接按运行成功处理,(发送PROTO_INSYNC PROTO_OK,变量timeout和bl_type重新赋值,继续新的命令处理) */
  747. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  748. case PROTO_DEBUG: /* PROTO_DEBUG:值0x31,输出调试信息 */
  749. break;
  750. default:
  751. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  752. /* 其他命令,直接进入接收新的命令循环 */
  753. /* ------------------------------------------------------------------------------------------------------------------------------------------------------------ */
  754. continue;
  755. }
  756. /* 4.5 命令处理完毕,运行成功后的处理(变量timeout初始化和bl_type重新赋值,调用sync_response函数发送PROTO_INSYNC PROTO_OK表示运行成功),继续接收下一条指令。 */
  757. /* timeout:bootloader跳转到飞控固件所需等待的时间,bootloader函数输入 */
  758. /* bl_type:静态全局接口类型变量, */
  759. /* NONE:值0,枚举类,定义在bl.h */
  760. /* last_input:静态全局变量,上次获取串口数据的方式 */
  761. /* sync_response:发送同步命令函数,表示运行成功,定义在bl.c */
  762. timeout = 0;
  763. if (bl_type == NONE) {
  764. bl_type = last_input;
  765. }
  766. sync_response();
  767. continue;
  768. cmd_bad: /* cmd_bad:错误指令处理标号,发送PROTO_INSYNC PROTO_INVALID,并准备接收下一条指令 */
  769. /* invalid_response:指令非法的处理函数,发送PROTO_INSYNC PROTO_INVALID,定义在bl.c */
  770. invalid_response();
  771. continue;
  772. cmd_fail: /* cmd_fail:指令运行失败,发送PROTO_INSYNC PROTO_FAILED,并准备接收下一条指令 */
  773. /* failure_response:指令运行失败的处理函数,发送PROTO_INSYNC PROTO_FAILED,定义在bl.c */
  774. failure_response();
  775. continue;
  776. #if defined(TARGET_HW_PX4_FMU_V4) /* 对主控FMU,宏TARGET_HW_PX4_FMU_V4定义为1,代码有效,Makefile.f4 */
  777. /* 对IO协处理器,宏TARGET_HW_PX4_FMU_V4未定义,代码无效 */
  778. bad_silicon: /* bad_silicon:MCU版本错误处理函数的标号,发送PROTO_INSYNC PROTO_BAD_SILICON_REV,并准备接收下一条指令 */
  779. /* bad_silicon_response:MCU型号错误处理函数,发送PROTO_INSYNC PROTO_BAD_SILICON_REV,定义在bl.c */
  780. bad_silicon_response();
  781. continue;
  782. #endif
  783. }
  784. }

8 主控FMU主线程序调用的函数

本节汇总了被主控FMU主线程序(main、jump_to_app和bootloader)调用的函数。

8.1 板载初始化相关函数

板载初始化相关函数包括board_init和反向初始化函数board_deinit,它们分别被main函数和jump_to_app函数调用。

8.1.1 board_init函数

board_init函数主要的功能如下:

  • 赋值board_info.fw_size,确定飞控固件size允许的最大值
  • 初始化USB端口GPIOA9(VBUS)
  • 初始化串口USART2
  • 初始化LED并点亮(B/E,LED701)
  • 使能能耗控制器时钟

main_f4.c

  1. static void board_init(void)
  2. {
  3. /* 1. 赋值board_info.fw_size,确定飞控固件size允许的最大值*/
  4. /* 修改fw_size值为APP_SIZE_MAX,宏APP_SIZE_MAX被main_f4.c定义为 BOARD_FLASH_SIZE-(BOOTLOADER_RESERVATION_SIZE APP_RESERVATION_SIZE) */
  5. /* BOARD_FLASH_SIZE:被定义在hw_config.h中,BOARD_FLASH_SIZE=_FLASH_KBYTES*1024,_FLASH_KBYTES通过读取地址0x1FFF7A22的低16位寄存器得到,此寄存器值代表以KB为单位的芯片的flash大小,属性只读 */
  6. /* BOOTLOADER_RESERVATION_SIZE:被定义在main_f4.c中,BOOTLOADER_RESERVATION_SIZE=16*1024,代表16KB(1个sector) */
  7. /* APP_RESERVATION_SIZE:被定义在hw_config.h中,APP_RESERVATION_SIZE=2*16*1024,代表32KB(2个sector) */
  8. /* 对于STM32F427VIT6,BOARD_FLASH_SIZE=2048KB,APP_SIZE_MAX=2000KB */
  9. board_info.fw_size = APP_SIZE_MAX;
  10. #if defined(TARGET_HW_PX4_FMU_V2) || defined(TARGET_HW_PX4_FMU_V4) /* 定义了宏TARGET_HW_PX4_FMU_V2,因此下列代码有效 */
  11. /* check_silicon函数自动辨识MCU的版本,默认STM32F427的revision 3返回0,其余返回-1 */
  12. /* fw_size的值为APP_SIZE_MAX=2000KB,第二项判断语句肯定为假 */
  13. /* 因此该if语句不为真,fw_size不被重新赋值,其值保持为2000KB */
  14. if (check_silicon() && board_info.fw_size == (2 * 1024 * 1024) - BOOTLOADER_RESERVATION_SIZE) {
  15. board_info.fw_size = (1024 * 1024) - BOOTLOADER_RESERVATION_SIZE;
  16. }
  17. #endif
  18. #if defined(BOARD_POWER_PIN_OUT) /* 宏BOARD_POWER_PIN_OUT未定义,下列语句无效 */
  19. /* Configure the Power pins */
  20. rcc_peripheral_enable_clock(&BOARD_POWER_CLOCK_REGISTER, BOARD_POWER_CLOCK_BIT);
  21. gpio_mode_setup(BOARD_POWER_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, BOARD_POWER_PIN_OUT);
  22. gpio_set_output_options(BOARD_POWER_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, BOARD_POWER_PIN_OUT);
  23. BOARD_POWER_ON(BOARD_POWER_PORT, BOARD_POWER_PIN_OUT);
  24. #endif
  25. /* 2. 初始化USB端口GPIOA9(VBUS) */
  26. #if INTERFACE_USB /* 宏INTERFACE_USB在hw_config.h中被定义为1,下列代码有效 */
  27. /* RCC_AHB1ENR:寄存器(libopencm3/include/libopencm3/stm32/f4/rcc.h,地址0x40023830) */
  28. /* RCC_AHB1ENR_IOPAEN:值为1<<0(libopencm3/include/libopencm3/stm32/f4/rcc.h),bit0 */
  29. /* rcc_peripheral_enable_clock:使能特定的外部时钟,被定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  30. /* 使能GPIOA的时钟,对应原理图中主控FMU连接USB接口芯片(U302,NUF2042VX6)的3个引脚GPIOA9(VBUS/3.1A),GPIOA11(OTG_FS_DM/3.1B),GPIOA12(OTG_FS_DP/3.1A) */
  31. rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_IOPAEN);
  32. #endif
  33. /* 3. 初始化串口USART2 */
  34. #if INTERFACE_USART /* 宏INTERFACE_USART在hw_config.h中被定义为1,下列代码有效 */
  35. /* BOARD_USART_PIN_CLOCK_REGISTER:被hw_config.h定义为寄存器RCC_AHB1ENR(libopencm3/include/libopencm3/stm32/f4/rcc.h,地址0x40023830) */
  36. /* BOARD_USART_PIN_CLOCK_BIT:被hw_config.h定义为RCC_AHB1ENR_IOPDEN(libopencm3/include/libopencm3/stm32/f4/rcc.h),值1<<3,bit3 */
  37. /* rcc_peripheral_enable_clock:使能特定的外部时钟,被定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  38. /* 使能GPIOD的时钟,对应原理图中主控FMU串口USART2的4个引脚GPIOD3(FMU-USART2_CTS/2.1B),GPIOD4(FMU-USART2_RTS/2.1B),GPIOD5(FMU-USART2_TX/2.1A),GPIOD6(FMU-USART2_RX/2.1A) */
  39. rcc_peripheral_enable_clock(&BOARD_USART_PIN_CLOCK_REGISTER, BOARD_USART_PIN_CLOCK_BIT);
  40. /* BOARD_PORT_USART:被hw_config.h定义为GPIOD,对应寄存器GPIOD_MODER(地址0x40020C00) */
  41. /* GPIO_MODE_AF:值为0x2(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  42. /* GPIO_PUPD_PULLUP:值为0x1(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  43. /* BOARD_PIN_TX:被hw_config.h定义为GPIO5,值1<<5(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  44. /* BOARD_PIN_RX:被hw_config.h定义为GPIO6,值1<<6(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  45. /* gpio_mode_setup:定义在libopencm3/lib/stm32/common/gpio_common_f0234.c,功能为设置GPIO引脚的功能 */
  46. /* 设置GPIOD5(USART2-TX)和GPIOD6(USART2-RX)为AF(MODER=GPIO_MODE_AF=10)模式,片内上拉push-pull(PUPDR=GPIO_PUPD_PULLUP=01) */
  47. gpio_mode_setup(BOARD_PORT_USART, GPIO_MODE_AF, GPIO_PUPD_PULLUP, BOARD_PIN_TX | BOARD_PIN_RX);
  48. /* BOARD_PORT_USART:被hw_config.h定义为GPIOD,对应寄存器GPIOD_MODER(地址0x40020C00) */
  49. /* BOARD_PORT_USART_AF:被hw_config.h定义为GPIO_AF7,值0x7 */
  50. /* BOARD_PIN_TX:被hw_config.h定义为GPIO5,值1<<5(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  51. /* BOARD_PIN_RX:被hw_config.h定义为GPIO6,值1<<6(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  52. /* gpio_set_af:定义在libopencm3/lib/stm32/common/gpio_common_f0234.c,功能为设置GPIO的具体AF功能 */
  53. /* 设置GPIOD5(USART2-TX)和GPIOD6(USART2-RX)为USART1~3功能(具体为USART2) */
  54. gpio_set_af(BOARD_PORT_USART, BOARD_PORT_USART_AF, BOARD_PIN_TX);
  55. gpio_set_af(BOARD_PORT_USART, BOARD_PORT_USART_AF, BOARD_PIN_RX);
  56. /* BOARD_USART_CLOCK_REGISTER:被hw_config.h定义为寄存器RCC_APB1ENR(地址0x40023840) */
  57. /* BOARD_USART_CLOCK_BIT:被hw_config.h定义为RCC_APB1ENR_USART2EN,值1<<17(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  58. /* rcc_peripheral_enable_clock:使能特定的外部时钟,被定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  59. /* 使能USART2的时钟,寄存器RCC_APB1ENR的bit17 */
  60. rcc_peripheral_enable_clock(&BOARD_USART_CLOCK_REGISTER, BOARD_USART_CLOCK_BIT);
  61. #endif
  62. #if defined(BOARD_FORCE_BL_PIN_IN) && defined(BOARD_FORCE_BL_PIN_OUT) /* 宏BOARD_FORCE_BL_PIN_IN与BOARD_FORCE_BL_PIN_OUT均未被定义,以下代码无效 */
  63. /* configure the force BL pins */
  64. rcc_peripheral_enable_clock(&BOARD_FORCE_BL_CLOCK_REGISTER, BOARD_FORCE_BL_CLOCK_BIT);
  65. gpio_mode_setup(BOARD_FORCE_BL_PORT, GPIO_MODE_INPUT, BOARD_FORCE_BL_PULL, BOARD_FORCE_BL_PIN_IN);
  66. gpio_mode_setup(BOARD_FORCE_BL_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, BOARD_FORCE_BL_PIN_OUT);
  67. gpio_set_output_options(BOARD_FORCE_BL_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_100MHZ, BOARD_FORCE_BL_PIN_OUT);
  68. #endif
  69. #if defined(BOARD_FORCE_BL_PIN) /* 宏BOARD_FORCE_BL_PIN未定义,下列代码无效 */
  70. /* configure the force BL pins */
  71. rcc_peripheral_enable_clock(&BOARD_FORCE_BL_CLOCK_REGISTER, BOARD_FORCE_BL_CLOCK_BIT);
  72. gpio_mode_setup(BOARD_FORCE_BL_PORT, GPIO_MODE_INPUT, BOARD_FORCE_BL_PULL, BOARD_FORCE_BL_PIN);
  73. #endif
  74. /* 4. 初始化LED并点亮(B/E,LED701) */
  75. /* RCC_AHB1ENR:寄存器(地址0x40023830) */
  76. /* BOARD_CLOCK_LEDS:被hw_config.h定义为RCC_AHB1ENR_IOPEEN,值1<<4,bit4 */
  77. /* rcc_peripheral_enable_clock:使能特定的外部时钟,被定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  78. /* 使能GPIOE的时钟,对应原理图中主控FMU的LED控制引脚GPIOE12(FMU-LED_AMBER/7.5B),低电平点亮发光二极管LED701 */
  79. rcc_peripheral_enable_clock(&RCC_AHB1ENR, BOARD_CLOCK_LEDS);
  80. /* BOARD_PORT_LEDS:被hw_config.h定义为GPIOE,值为GPIOE控制寄存器组起始地址0x40021000 */
  81. /* GPIO_MODE_OUTPUT:值0x1(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  82. /* GPIO_PUPD_NONE:值0x0(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  83. /* BOARD_PIN_LED_BOOTLOADER:被hw_config.h定义为GPIO12,值1<<12(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  84. /* BOARD_PIN_LED_ACTIVITY:被hw_config.h定义为0,代表主控FMU无ACT指示灯 */
  85. /* gpio_mode_setup:定义在libopencm3/lib/stm32/common/gpio_common_f0234.c,功能为设置GPIO引脚的功能 */
  86. /* 设置GPIOE12(FMU-LED_AMBER/7.5B)为输出模式(MODER=GPIO_MODE_OUTPUT=01),push-pull模式(PUPDR=GPIO_PUPD_NONE=00) */
  87. gpio_mode_setup(BOARD_PORT_LEDS, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, BOARD_PIN_LED_BOOTLOADER | BOARD_PIN_LED_ACTIVITY);
  88. /* BOARD_PORT_LEDS:被hw_config.h定义为GPIOE,值为GPIOE控制寄存器组起始地址0x40021000 */
  89. /* GPIO_OTYPE_PP:值0x0(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  90. /* GPIO_OSPEED_2MHZ:值0x0(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  91. /* BOARD_PIN_LED_BOOTLOADER:被hw_config.h定义为GPIO12,值1<<12(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  92. /* BOARD_PIN_LED_ACTIVITY:被hw_config.h定义为0,代表主控FMU无ACT指示灯 */
  93. /* gpio_set_output_options:定义在libopencm3/lib/stm32/common/gpio_common_f0234.c,功能为设置GPIO的输出模式 */
  94. /* 设置GPIOE12(FMU-LED_AMBER/7.5B)的输出模式为push-pull(OTYPER=GPIO_OTYPE_PP=0),GPIO时钟为2MHz(OSPEEDR=GPIO_OSPEED_2MHZ=00) */
  95. gpio_set_output_options(BOARD_PORT_LEDS, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, BOARD_PIN_LED_BOOTLOADER | BOARD_PIN_LED_ACTIVITY);
  96. /* BOARD_PORT_LEDS:被hw_config.h定义为GPIOE,值为GPIOE控制寄存器组起始地址0x40021000 */
  97. /* BOARD_PIN_LED_BOOTLOADER:被hw_config.h定义为GPIO12,值1<<12(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  98. /* BOARD_PIN_LED_ACTIVITY:被hw_config.h定义为0,代表主控FMU无ACT指示灯 */
  99. /* BOARD_LED_ON:被hw_config.h定义为函数gpio_clear(libopencm3/lib/stm32/common/gpio_common_all.c),功能为置某GPIO组中的引脚输出为低电平 */
  100. /* 设置GPIOE12(FMU-LED_AMBER/7.5B)为低电平,点亮B/E(LED701) */
  101. BOARD_LED_ON(BOARD_PORT_LEDS, BOARD_PIN_LED_BOOTLOADER | BOARD_PIN_LED_ACTIVITY);
  102. /* 5. 使能能耗控制器时钟 */
  103. /* RCC_APB1ENR:寄存器(地址0x40023840) */
  104. /* RCC_APB1ENR_PWREN:值1<<28(libopencm3/include/libopencm3/stm32/f4/rcc.h),bit28 */
  105. /* rcc_peripheral_enable_clock:使能特定的外部时钟,被定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  106. /* 使能电源接口时钟 */
  107. rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_PWREN);
  108. }

8.1.2 board_deinit函数

board_deinit函数用于板载设备的反向初始化:主要的功能如下:

  • 无效化串口USART2
  • 无效化LED时钟引脚
  • 关闭电源接口时钟
  • 恢复AHB1外部时钟为默认值

main_f4.c

  1. void board_deinit(void)
  2. {
  3. #if INTERFACE_USART /* 宏INTERFACE_USART定义为1,在hw_config.h,下列代码有效 */
  4. /* 1. 无效化串口USART2 */
  5. /* 设置USART2的输入输出引脚为输入状态 */
  6. /* BOARD_PORT_USART:被hw_config.h定义为GPIOD,对应寄存器GPIOD_MODER(地址0x40020C00) */
  7. /* GPIO_MODE_INPUT:值为0x0(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  8. /* GPIO_PUPD_NONE:值为0x0(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  9. /* BOARD_PIN_TX:被hw_config.h定义为GPIO5,值1<<5(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  10. /* BOARD_PIN_RX:被hw_config.h定义为GPIO6,值1<<6(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  11. /* gpio_mode_setup:定义在libopencm3/lib/stm32/common/gpio_common_f0234.c,功能为设置GPIO引脚的功能 */
  12. /* 设置GPIOD5(USART2-TX)和GPIOD6(USART2-RX)为输入模式(MODER=GPIO_MODE_INPUT=00),浮动状态(PUPDR=GPIO_PUPD_NONE=00) */
  13. gpio_mode_setup(BOARD_PORT_USART, GPIO_MODE_INPUT, GPIO_PUPD_NONE, BOARD_PIN_TX | BOARD_PIN_RX);
  14. /* 关闭USART2的时钟 */
  15. /* BOARD_USART_CLOCK_REGISTER:被hw_config.h定义为寄存器RCC_APB1ENR(地址0x40023840) */
  16. /* BOARD_USART_CLOCK_BIT:被hw_config.h定义为RCC_APB1ENR_USART2EN,值1<<17(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  17. /* rcc_peripheral_disable_clock:关闭特定的外部时钟,被定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  18. rcc_peripheral_disable_clock(&BOARD_USART_CLOCK_REGISTER, BOARD_USART_CLOCK_BIT);
  19. #endif
  20. #if defined(BOARD_FORCE_BL_PIN_IN) && defined(BOARD_FORCE_BL_PIN_OUT) /* 宏BOARD_FORCE_BL_PIN_IN和BOARD_FORCE_BL_PIN_OUT未定义,下列代码无效 */
  21. gpio_mode_setup(BOARD_FORCE_BL_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, BOARD_FORCE_BL_PIN_OUT);
  22. gpio_mode_setup(BOARD_FORCE_BL_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, BOARD_FORCE_BL_PIN_IN);
  23. #endif
  24. #if defined(BOARD_FORCE_BL_PIN) /* 宏BOARD_FORCE_BL_PIN未定义,下列代码无效 */
  25. gpio_mode_setup(BOARD_FORCE_BL_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, BOARD_FORCE_BL_PIN);
  26. #endif
  27. #if defined(BOARD_POWER_PIN_OUT) && defined(BOARD_POWER_PIN_RELEASE) /* 宏BOARD_POWER_PIN_OUT和BOARD_POWER_PIN_RELEASE未定义,下列代码无效 */
  28. gpio_mode_setup(BOARD_POWER_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, BOARD_POWER_PIN);
  29. #endif
  30. /* 2. 无效化LED时钟引脚(设为输入模式) */
  31. /* BOARD_PORT_LEDS:被hw_config.h定义为GPIOE,值为GPIOE控制寄存器组起始地址0x40021000 */
  32. /* GPIO_MODE_INPUT:值为0x0(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  33. /* BOARD_PIN_LED_BOOTLOADER:被hw_config.h定义为GPIO12,值1<<12(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  34. /* BOARD_PIN_LED_ACTIVITY:被hw_config.h定义为0,代表主控FMU无ACT指示灯 */
  35. /* BOARD_LED_ON:被hw_config.h定义为函数gpio_clear(libopencm3/lib/stm32/common/gpio_common_all.c),功能为置某GPIO组中的引脚输出为低电平 */
  36. /* 设置GPIOE12(FMU-LED_AMBER/7.5B)为输入模式(MODER=GPIO_MODE_INPUT=00),浮动模式(PUPDR=GPIO_PUPD_NONE=00) */
  37. gpio_mode_setup(BOARD_PORT_LEDS, GPIO_MODE_INPUT, GPIO_PUPD_NONE, BOARD_PIN_LED_BOOTLOADER | BOARD_PIN_LED_ACTIVITY);
  38. /* 3. 关闭电源接口时钟 */
  39. /* RCC_APB1ENR:寄存器(地址0x40023840) */
  40. /* RCC_APB1ENR_PWREN:值1<<28(libopencm3/include/libopencm3/stm32/f4/rcc.h),bit28 */
  41. /* rcc_peripheral_disable_clock:关闭特定的外部时钟,被定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  42. rcc_peripheral_disable_clock(&RCC_APB1ENR, RCC_APB1ENR_PWREN);
  43. /* 4. 恢复AHB1外部时钟为默认值 */
  44. /* RCC_AHB1ENR:AHB1外部时钟使能寄存器 */
  45. RCC_AHB1ENR = 0x00100000; // XXX Magic reset number from STM32F4x reference manual
  46. }

8.2 时钟初始化相关函数

板载初始化相关函数包括clock_init和反向初始化函数clock_deinit,它们分别被main函数和jump_to_app函数调用。

8.2.1 clock_init函数

clock_init函数用到了一个数据结构rcc_clock_scale,它被定义在libopencm3/include/libopencm3/stm32/f4/rcc.h中。


libopencm3/include/libopencm3/stm32/f4/rcc.h

  1. truct rcc_clock_scale {
  2. uint8_t pllm; /* 寄存器RCC_PLLCFGR中的PLLM信息(5:0) */
  3. uint16_t plln; /* 寄存器RCC_PLLCFGR中的PLLN信息(14:6) */
  4. uint8_t pllp; /* 寄存器RCC_PLLCFGR中的PLLP信息(17:16) */
  5. uint8_t pllq; /* 寄存器RCC_PLLCFGR中的PLLQ信息(27:24) */
  6. uint8_t pllr; /* 仅STM32F446/469包含此项 */
  7. uint32_t flash_config; /* flash访问控制,对应寄存器FLASH_ACR */
  8. uint8_t hpre; /* 寄存器RCC_CFGR中的HPRE信息(7:4) */
  9. uint8_t ppre1; /* 寄存器RCC_CFGR中的PPRE1信息(12:10) */
  10. uint8_t ppre2; /* 寄存器RCC_CFGR中的PPRE2信息(15:13) */
  11. uint8_t power_save; /* 0:高能耗模式(scale 1,11),1:节能模式(scale 3,01);对应寄存器PWR_CR的VOS信息(15:14) */
  12. uint32_t ahb_frequency; /* AHB时钟频率 */
  13. uint32_t apb1_frequency; /* APB1时钟频率 */
  14. uint32_t apb2_frequency; /* APB2时钟频率 */
  15. };

通过设置数据结构rcc_clock_scale中域的值,可实现如下功能:

  • PLL时钟设置,并选择main PLL作为系统时钟sysclk,fsysclk=fPLL=168MHz,fUSBSDRNG=48MHz
  • AHB、APB1、APB2时钟设置,fAHB=fsysclk=168MHz,fAPB1=42MHz,fAPB2=84MHz
  • 选择高能耗模式(Scale 2 mode)
  • flash访问控制设置,启动Icache和Dcache,并设置等待时间5周期
  • 更新库libopencm3中AHB,APB1,APB2对应的全局变量

main_f4.c

  1. static const struct rcc_clock_scale clock_setup = {
  2. /* PLL时钟设置 */
  3. .pllm = OSC_FREQ, /* PLLM=OSC_FREQ=24(hw_config.h) */
  4. .plln = 336, /* PLLN=336 */
  5. .pllp = 2, /* PLLP=2 */
  6. .pllq = 7, /* PLLQ=7 */
  7. #if defined(STM32F446) || defined(STM32F469) /* 未定义宏STM32F446和STM32F469,pllr域不初始化 */
  8. .pllr = 2,
  9. #endif
  10. /* AHB、APB1、APB2时钟设置 */
  11. .hpre = RCC_CFGR_HPRE_DIV_NONE, /* HPRE=RCC_CFGR_HPRE_DIV_NONE=0(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  12. .ppre1 = RCC_CFGR_PPRE_DIV_4, /* PPRE1=RCC_CFGR_PPRE_DIV_4=0x5(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  13. .ppre2 = RCC_CFGR_PPRE_DIV_2, /* PPRE1=RCC_CFGR_PPRE_DIV_2=0x4(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  14. /* 能耗模式 */
  15. .power_save = 0, /* VOS=10,默认为11 */
  16. /* flash访问控制 */
  17. .flash_config = FLASH_ACR_ICE | FLASH_ACR_DCE | FLASH_ACR_LATENCY_5WS, /* 寄存器FLASH_ACR的ICEN(9),DCEN(10),LATENCY(3:0) */
  18. /* 更新APB1、APB2时钟全局变量值 */
  19. .apb1_frequency = 42000000, /* */
  20. .apb2_frequency = 84000000,
  21. };
  22. /* rcc_clock_setup_hse_3v3:被定义在libopencm3/lib/stm32/f4/rcc.c,用于启动后各系统时钟的初始化、能耗模式配置、flash访问控制 */
  23. /* 1. PLL时钟设置(寄存器RCC_PLLCFGR) */
  24. /* fVCO=fPLLin*(PLLN/PLLM)=24*(336/24)=336MHz,(根据原理图,主控FMU的外部晶振频率为24MHz) */
  25. /* fPLL=fVCO/PLLP=168MHz */
  26. /* fUSBSDRNG=fVCO/PLLQ=48MHz */
  27. /* 2. AHB、APB1、APB2时钟设置(寄存器RCC_CFGR) */
  28. /* fAHB=fsysclk=fPLL=168MHz,域HPRE=0,不分频,系统时钟由调用的rcc_set_sysclk_source函数设置为fPLL */
  29. /* fAPB1=fAHB/4=42MHz,域PPRE1=101,分频倍率为4 */
  30. /* fAPB2=fAHB/2=84MHz,域PPRE2=100,分频倍率为2 */
  31. /* 3. 能耗模式 */
  32. /* 寄存器PWR_CR的域VOS=10,对应Scale 2 mode */
  33. /* 4. flash访问控制 */
  34. /* ICEN=FLASH_ACR_ICE=1,bit9,使能I cache */
  35. /* DCEN=FLASH_ACR_DCE=1,bit10,使能D cache */
  36. /* LATENCY=FLASH_ACR_LATENCY_5WS=0x5,访问等待5周期 */
  37. /* 5. 更新库libopencm3中定义的全局变量rcc_apb1_frequency=apb1_frequency=42000000,rcc_apb2_frequency=apb2_frequency=84000000 */
  38. static inline void clock_init(void)
  39. {
  40. rcc_clock_setup_hse_3v3(&clock_setup);
  41. }

8.2.2 clock_deinit函数

clock_deinit函数关闭时钟源,将所有内部时钟设置为重启后的初始状态,为飞控固件的运行提供良好的初始环境,主要操作如下:

  • 开启内部高速时钟并等待其稳定
  • 清除所有时钟配置
  • 关闭时钟HSE、PLL、PLLI2S、PLLSAI并关闭时钟安全系统(CSS),此时切换为HSI
  • 恢复PLL配置寄存器为默认值
  • 关闭HSE旁路功能
  • 清除所有时钟中断

  1. void clock_deinit(void)
  2. {
  3. /* 1. 开启内部高速时钟并等待其稳定 */
  4. /* RCC_HSI:值4,rcc_osc枚举变量,定义在libopencm3/include/libopencm3/stm32/f4/rcc.h */
  5. /* rcc_osc_on:开启某内部时钟,操作寄存器RCC_CR。rcc_osc_on(RCC_HSI)操作HSION域(bit0),定义在libopencm3/lib/stm32/f4/rcc.c */
  6. /* rcc_wait_for_osc_ready:等待某内部时钟稳定,读取寄存器RCC_CR。rcc_wait_for_osc_ready(RCC_HSI)读取HSIRDY(bit1),定义在libopencm3/lib/stm32/f4/rcc.c */
  7. rcc_osc_on(RCC_HSI);
  8. rcc_wait_for_osc_ready(RCC_HSI);
  9. /* 2. 清除所有时钟配置 */
  10. /* RCC_CFGR:时钟配置寄存器寄存器,libopencm3/include/libopencm3/stm32/f4/rcc.h */
  11. RCC_CFGR = 0x000000;
  12. /* 3. 关闭时钟HSE、PLL、PLLI2S、PLLSAI并关闭时钟安全系统(CSS),此时切换为HSI */
  13. /* RCC_HSE:值3,rcc_osc枚举变量,定义在libopencm3/include/libopencm3/stm32/f4/rcc.h */
  14. /* RCC_PLL:值0,rcc_osc枚举变量,定义在libopencm3/include/libopencm3/stm32/f4/rcc.h */
  15. /* rcc_osc_off:关闭某内部时钟,操作寄存器RCC_CR。定义在libopencm3/lib/stm32/f4/rcc.c */
  16. /* rcc_css_disable:关闭时钟安全系统,操作寄存器RCC_CR的域CSSON(bit19)。定义在libopencm3/lib/stm32/f4/rcc.c */
  17. /* rcc_osc_off(RCC_HSE)操作HSEON域(bit16),rcc_osc_off(RCC_PLL)操作PLLON域(bit24)。定义在libopencm3/lib/stm32/f4/rcc.c */
  18. rcc_osc_off(RCC_HSE);
  19. rcc_osc_off(RCC_PLL);
  20. rcc_css_disable();
  21. /* 4. 恢复PLL配置寄存器为默认值 */
  22. /* RCC_PLLCFGR:RCC PLL配置寄存器 */
  23. RCC_PLLCFGR = 0x24003010;
  24. /* 5. 关闭HSE旁路功能 */
  25. /* RCC_HSE:值3,rcc_osc枚举变量,定义在libopencm3/include/libopencm3/stm32/f4/rcc.h */
  26. /* rcc_osc_bypass_disable:关闭HSE的旁路功能,操作寄存器RCC_CR的HSEBYP域(bit18) */
  27. rcc_osc_bypass_disable(RCC_HSE);
  28. /* 6. 清除所有时钟中断 */
  29. /* RCC_CIR:RCC时钟中断寄存器 */
  30. RCC_CIR = 0x000000;
  31. }

8.3 RTC备份寄存器操作函数

RTC备份寄存器操作函数board_get_rtc_signature和board_set_rtc_signature这两个函数都被定义在main_f4.c中。这两个函数分别用于获取和设置RTC_BKPxR的第一个32位寄存器(被定义为BOOT_RTC_REG)的值。

这里的源代码中有BUG,未选择时钟源,正确的操作应当是选择HSE并且分频系数为24,确保RTC时钟为1MHz。根据STM32f427芯片手册,访问RTC寄存器(含16个RTC_BKPxR寄存器组)的操作如下:

  • 使能电源接口时钟(此步骤已在board_init函数中操作,这里可以省略):寄存器RCC_APB1ENR的PWREN位(bit28)置1
  • 取消写保护:寄存器PWR_CR的DBP位(bit8)置1,使寄存器RCC_BDCR、RTC寄存器(含RTC_BKPxR)和寄存器PWR_CSR的BRE位可写
  • 选择RTC时钟源(BUG关键点,未选择时钟源):设置寄存器RCC_BDCR的域RTCSEL(9:8)来选择RTC的时钟源(默认00:无,01:LSE,10:LSI,11:HSE RTCPRE);若选择HSE RTCPRE,还需设置寄存器RCC_CFGR的域RTCPRE(20:16)来确定分频系数(默认00000:无,00001:无,00010:HSE/2,00011:HSE/3,……,11111:HSE/31)
  • 使能RTC时钟源:寄存器RCC_BDCR的RTCEN位(bit15)置1

经过上述操作就可以访问RTC寄存器了。访问完毕之后,一般需反向操作关闭访问权限。

8.3.1 board_get_rtc_signature函数


main_f4.c

  1. /* BOOT_RTC_REG:定义强制bootloader寄存器,地址为0x40002850,对应16个RTC_BKPxR的第一个32位寄存器,它们不受芯片重启reset影响 */
  2. #define BOOT_RTC_REG MMIO32(RTC_BASE 0x50)
  3. static uint32_t board_get_rtc_signature()
  4. {
  5. /* 1. 修改相关寄存器选项,使BOOT_RTC_REG可以被访问 */
  6. /* PWR_CR:能耗控制寄存器 */
  7. /* PWR_CR_DBP:值1<<8(libopencm3/include/libopencm3/stm32/common/pwr_common_all.h) */
  8. /* 芯片重启后,寄存器RCC_BDCR、RTC备份寄存器(如BOOT_RTC_REG)和寄存器PWR_CSR的BRE位都处于写保护状态,寄存器PWR_CR的bit8置1解除写保护 */
  9. PWR_CR |= PWR_CR_DBP;
  10. /* RCC_BDCR:备份域控制寄存器 */
  11. /* RCC_BDCR_RTCEN:值1<<15(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  12. /* 使能RTC时钟,BOOT_RTC_REG可以访问,还未选择时钟源?BUG? */
  13. RCC_BDCR |= RCC_BDCR_RTCEN;
  14. /* 2. 读取BOOT_RTC_REG寄存器值 */
  15. uint32_t result = BOOT_RTC_REG;
  16. /* 3. 修改寄存器选项,禁止访问BOOT_RTC_REG */
  17. /* RCC_BDCR:备份域控制寄存器 */
  18. /* RCC_BDCR_RTCEN:值1<<15(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  19. /* 这里应反向关闭RTC时钟,正确代码应该如下。源代码不但没有关闭RTC时钟,还会,BUG? */
  20. /* RCC_BDCR &= ~RCC_BDCR_RTCEN */
  21. RCC_BDCR &= RCC_BDCR_RTCEN;
  22. /* PWR_CR:能耗控制寄存器 */
  23. /* PWR_CR_DBP:值1<<8(libopencm3/include/libopencm3/stm32/common/pwr_common_all.h) */
  24. /* 此句为RTC寄存器加上写保护 */
  25. PWR_CR &= ~PWR_CR_DBP;
  26. return result;
  27. }

8.3.2 board_set_rtc_signature函数


main_f4.c

  1. static void board_set_rtc_signature(uint32_t sig)
  2. {
  3. /* 1. 修改相关寄存器选项,使BOOT_RTC_REG可以被访问 */
  4. /* PWR_CR:能耗控制寄存器 */
  5. /* PWR_CR_DBP:值1<<8(libopencm3/include/libopencm3/stm32/common/pwr_common_all.h) */
  6. /* 芯片重启后,寄存器RCC_BDCR、RTC备份寄存器(如BOOT_RTC_REG)和寄存器PWR_CSR的BRE位都处于写保护状态,寄存器PWR_CR的bit81解除写保护 */
  7. PWR_CR |= PWR_CR_DBP;
  8. /* RCC_BDCR:备份域控制寄存器 */
  9. /* RCC_BDCR_RTCEN:值1<<15(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  10. /* 使能RTC时钟,BOOT_RTC_REG可以访问,还未选择时钟源?BUG? */
  11. RCC_BDCR |= RCC_BDCR_RTCEN;
  12. /* 2. 设置BOOT_RTC_REG寄存器值 */
  13. BOOT_RTC_REG = sig;
  14. /* 3. 修改寄存器选项,禁止访问BOOT_RTC_REG */
  15. /* RCC_BDCR:备份域控制寄存器 */
  16. /* RCC_BDCR_RTCEN:值1<<15(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  17. /* 这里应反向关闭RTC时钟,正确代码应该如下。源代码不但没有关闭RTC时钟,还会,BUG? */
  18. /* RCC_BDCR &= ~RCC_BDCR_RTCEN */
  19. RCC_BDCR &= RCC_BDCR_RTCEN;
  20. /* PWR_CR:能耗控制寄存器 */
  21. /* PWR_CR_DBP:值1<<8(libopencm3/include/libopencm3/stm32/common/pwr_common_all.h) */
  22. /* 此句为RTC寄存器加上写保护 */
  23. PWR_CR &= ~PWR_CR_DBP;
  24. }

8.4 flash操作函数

这里的flash操作是针对飞控固件部分flash的。

8.4.1 flash_func_read_word函数

flash_func_read_word函数返回飞控固件flash域内某地址的值,输入的地址值自动加上APP_LOAD_ADDRESS(0x08004000)。


main_f4.c

  1. uint32_t flash_func_read_word(uint32_t address)
  2. {
  3. /* 若地址非4字节对齐,返回0 */
  4. if (address & 3) {
  5. return 0;
  6. }
  7. /* 否则返回地址为(address APP_LOAD_ADDRESS)对应的值,这个地址在飞控固件内 */
  8. /* APP_LOAD_ADDRESS:值0x08004000,定义在hw_config.h */
  9. return *(uint32_t *)(address APP_LOAD_ADDRESS);
  10. }

8.4.2 flash_func_sector_size函数

flash_func_sector_size函数返回MCU的flash在某sector的大小值,若无此sector则返回0。


main_f4.c

  1. /* BOARD_FLASH_SECTORS:STM32F4芯片若片内flash为1M,则Sector数量为11;若不为1M(2M),则Sector数量为23,定义在hw_config.h */
  2. /* flash_sectors:结构体全局变量,表示flash内部各sector的size分布,定义在main_f4.c */
  3. uint32_t flash_func_sector_size(unsigned sector)
  4. {
  5. if (sector < BOARD_FLASH_SECTORS) {
  6. return flash_sectors[sector].size;
  7. }
  8. return 0;
  9. }

8.4.3 flash_func_erase_sector函数

flash_func_erase_sector函数用于擦除指定的sector。根据当前段中有无有效数据而被擦写,若有就擦写;若无有效数据(全为0xFFFFFFFF),则不用擦写。


  1. void flash_func_erase_sector(unsigned sector)
  2. {
  3. /* 判定输入参数sector是否合法,若不合法直接返回 */
  4. /* BOARD_FLASH_SECTORS:STM32F4芯片若片内flash为1M,则Sector数量为11;若不为1M(2M),则Sector数量为23,定义在hw_config.h */
  5. /* BOARD_FIRST_FLASH_SECTOR_TO_ERASE:值0,定义在hw_config.h */
  6. if (sector >= BOARD_FLASH_SECTORS || sector < BOARD_FIRST_FLASH_SECTOR_TO_ERASE) {
  7. return;
  8. }
  9. /* 计算当前sector相对于APP_LOAD_ADDRESS(0x08004000)的相对地址(即逻辑地址) */
  10. /* BOARD_FIRST_FLASH_SECTOR_TO_ERASE:值0,定义在hw_config.h */
  11. /* flash_func_sector_size:函数返回MCU的flash在某sector的大小值,若无此sector则返回0,定义在main_f4.c */
  12. uint32_t address = 0;
  13. for (unsigned i = BOARD_FIRST_FLASH_SECTOR_TO_ERASE; i < sector; i ) {
  14. address = flash_func_sector_size(i);
  15. }
  16. /* 获取当前段的大小,扫描当前段的内容,若存在不为0xffffffff的非空字节,说明本段内容不为空;置blank变量为假,表示本段写入过数据,应被擦写。 */
  17. /* flash_func_sector_size:函数返回MCU的flash在某sector的大小值,若无此sector则返回0,定义在main_f4.c */
  18. /* blank:当前段是否为空的标记 */
  19. unsigned size = flash_func_sector_size(sector);
  20. bool blank = true;
  21. /* flash_func_read_word:函数返回飞控固件flash域内某地址的值,输入的地址值自动加上APP_LOAD_ADDRESS(0x08004000),定义在main_f4.c */
  22. for (unsigned i = 0; i < size; i = sizeof(uint32_t)) {
  23. if (flash_func_read_word(address i) != 0xffffffff) {
  24. blank = false;
  25. break;
  26. }
  27. }
  28. /* 根据blank的取值进行标记确定是否擦除当前段 */
  29. /* blank:当前段是否为空的标记 */
  30. /* FLASH_CR_PROGRAM_X32:值2,表示flash写入的并行度为32位,定义在libopencm3/include/libopencm3/stm32/common/flash_common_f24.h */
  31. /* flash_erase_sector:擦除某段的库函数,定义在libopencm3/lib/stm32/common/flash_common_f24.c */
  32. if (!blank) {
  33. flash_erase_sector(flash_sectors[sector].sector_number, FLASH_CR_PROGRAM_X32);
  34. }
  35. }

8.4.4 flash_func_write_word函数

flash_func_write_word函数用于编写特定地址的flash,以4字节编写,输入地址自动加上APP_LOAD_ADDRESS(0x08004000)。


main_f4.c

  1. void flash_func_write_word(uint32_t address, uint32_t word)
  2. {
  3. /* flash_program_word:在某地址编写flash字的库函数,定义在libopencm3/lib/stm32/common/flash_common_f24.c */
  4. flash_program_word(address APP_LOAD_ADDRESS, word);
  5. }

8.4.5 flash_func_read_otp函数

flash_func_read_otp函数读取OTP区的一个字,输入地址为相对地址(相对于OTP区基地址)。


main_f4.c

  1. #define OTP_BASE 0x1fff7800 /* OTP区基地址 */
  2. #define OTP_SIZE 512 /* OTP区大小 */
  3. uint32_t flash_func_read_otp(uint32_t address)
  4. {
  5. /* 若给定地址不是4字节对齐或者超出OTP区的范围,返回0 */
  6. /* OTP_SIZE:值512,OTP区的大小(byte),定义在main_f4.c */
  7. /* OTP_BASE:值0x1fff7800,OTP区基地址,定义在main_f4.c */
  8. if (address & 3) {
  9. return 0;
  10. }
  11. if (address > OTP_SIZE) {
  12. return 0;
  13. }
  14. return *(uint32_t *)(address OTP_BASE);
  15. }

8.4.6 flash_func_read_sn函数

flash_func_read_sn函数读取MCU的UDID(Unique Device ID)某字(共12byte,3字)。


main_f4.c

  1. #define UDID_START 0x1FFF7A10 /* UDID_START:UDID寄存器基地址,值0x1FFF7A10,定义在main_f4.c */
  2. uint32_t flash_func_read_sn(uint32_t address)
  3. {
  4. return *(uint32_t *)(address UDID_START);
  5. }

8.5 board_test_usart_receiving_break函数

board_test_usart_receiving_break没有使用USART控制器来接收信息,而是使用systick时钟来软件模拟串口的接收。程序看起来复杂,实质非常简单。它的功能就是:连续接收3个字节的内容,如果收到一个0字节则返回真,否则返回假。break字符为字节0。


main_f4.c

  1. static bool board_test_usart_receiving_break()
  2. {
  3. #if !defined(SERIAL_BREAK_DETECT_DISABLED) /* 宏SERIAL_BREAK_DETECT_DISABLED未定义,下列代码有效 */
  4. /* 启动systick定时器 */
  5. systick_interrupt_disable(); /* 关闭systick中断(libopencm3/lib/cm3/systick.c) */
  6. systick_counter_disable(); /* systick倒计时关闭(libopencm3/lib/cm3/systick.c) */
  7. systick_set_clocksource(STK_CSR_CLKSOURCE_AHB); /* 选取AHB作为systick的时钟源,STK_CSR_CLKSOURCE_AHB=1<<2(libopencm3/include/libopencm3/cm3/systick.h) */
  8. /* board_info.systick_mhz:值168,定义在main_f4.c */
  9. /* USART_BAUDRATE:值115200,定义在hw_config.h */
  10. /* 设置systick时钟倒计时(寄存器STK_VAL)约为729,为串口波特率的一半 */
  11. systick_set_reload(((board_info.systick_mhz * 1000000) / USART_BAUDRATE) >> 1);
  12. systick_counter_enable(); /* systick倒计时开启(libopencm3/lib/cm3/systick.c) */
  13. uint8_t cnt_consecutive_low = 0;
  14. uint8_t cnt = 0;
  15. /* 检测连续3个字节,若有1个字节值为0,则跳出循环。每个传播周期包含10位(1起始 8数据 1停止)。 */
  16. while (cnt < 60) {
  17. /* BOARD_PORT_USART:值GPIOD,定义在hw_config.h */
  18. /* BOARD_PIN_RX:值GPIO6,定义在hw_config.h */
  19. /* systick_get_countflag:定义在libopencm3/lib/cm3/systick.c,当systick倒计时为0时返回1,否则返回0。库中定义的寄存器STK_CSR即为寄存器STK_CTRL */
  20. /* gpio_get:定义在libopencm3/lib/stm32/common/gpio_common_all.c,功能为获取某GPIO组的值 */
  21. if (systick_get_countflag() == 1) {
  22. if (gpio_get(BOARD_PORT_USART, BOARD_PIN_RX) == 0) {
  23. cnt_consecutive_low ; // Increment the consecutive low counter
  24. } else {
  25. cnt_consecutive_low = 0; // Reset the consecutive low counter
  26. }
  27. cnt ;
  28. }
  29. // 若连续收到9个低电平(1起始 8数据),即一个字节0,则跳出循环
  30. if (cnt_consecutive_low >= 18) {
  31. break;
  32. }
  33. }
  34. systick_counter_disable(); /* systick倒计时关闭(libopencm3/lib/cm3/systick.c) */
  35. /* 如果检测到9个低电平(1起始 8数据),则返回真,否则返回假 */
  36. if (cnt_consecutive_low >= 18) {
  37. return true;
  38. }
  39. #endif // !defined(SERIAL_BREAK_DETECT_DISABLED)
  40. return false;
  41. }

8.6 LED操作函数

LED操作函数包含点亮led_on、熄灭led_off和反向led_toggle三个函数。

8.6.1 led_on函数


main_f4.c

  1. void led_on(unsigned led)
  2. {
  3. /* LED_ACTIVITY:值1,定义在bl.h */
  4. /* LED_BOOTLOADER:值2,定义在bl.h */
  5. /* BOARD_PORT_LEDS:值GPIOE(指向GPIOE首寄存器),定义在hw_config.h */
  6. /* BOARD_PIN_LED_ACTIVITY:值0,无此LED,定义在hw_config.h */
  7. /* BOARD_PIN_LED_BOOTLOADER:值GPIO12(1<<12),定义在hw_config.h */
  8. /* BOARD_LED_ON:被hw_config.h定义为gpio_clear函数,功能为置某GPIO组中的引脚输出为低电平(libopencm3/lib/stm32/common/gpio_common_all.c) */
  9. switch (led) {
  10. case LED_ACTIVITY:
  11. BOARD_LED_ON(BOARD_PORT_LEDS, BOARD_PIN_LED_ACTIVITY);
  12. break;
  13. case LED_BOOTLOADER:
  14. BOARD_LED_ON(BOARD_PORT_LEDS, BOARD_PIN_LED_BOOTLOADER);
  15. break;
  16. }
  17. }

8.6.2 led_off函数


main_f4.c

  1. void led_off(unsigned led)
  2. {
  3. /* LED_ACTIVITY:值1,定义在bl.h */
  4. /* LED_BOOTLOADER:值2,定义在bl.h */
  5. /* BOARD_PORT_LEDS:值GPIOE(指向GPIOE首寄存器),定义在hw_config.h */
  6. /* BOARD_PIN_LED_ACTIVITY:值0,无此LED,定义在hw_config.h */
  7. /* BOARD_PIN_LED_BOOTLOADER:值GPIO12(1<<12),定义在hw_config.h */
  8. /* BOARD_LED_OFF:被hw_config.h定义为gpio_set函数,功能为置某GPIO组中的引脚输出为高电平(libopencm3/lib/stm32/common/gpio_common_all.c) */
  9. switch (led) {
  10. case LED_ACTIVITY:
  11. BOARD_LED_OFF(BOARD_PORT_LEDS, BOARD_PIN_LED_ACTIVITY);
  12. break;
  13. case LED_BOOTLOADER:
  14. BOARD_LED_OFF(BOARD_PORT_LEDS, BOARD_PIN_LED_BOOTLOADER);
  15. break;
  16. }
  17. }

8.6.3 led_toggle函数


main_f4.c

  1. /* LED_ACTIVITY:值1,定义在bl.h */
  2. /* LED_BOOTLOADER:值2,定义在bl.h */
  3. /* BOARD_PORT_LEDS:值GPIOE(指向GPIOE首寄存器),定义在hw_config.h */
  4. /* BOARD_PIN_LED_ACTIVITY:值0,无此LED,定义在hw_config.h */
  5. /* BOARD_PIN_LED_BOOTLOADER:值GPIO12(1<<12),定义在hw_config.h */
  6. /* gpio_toggle:反向函数,定义在libopencm3/lib/stm32/common/gpio_common_all.c */
  7. void led_toggle(unsigned led)
  8. {
  9. switch (led) {
  10. case LED_ACTIVITY:
  11. gpio_toggle(BOARD_PORT_LEDS, BOARD_PIN_LED_ACTIVITY);
  12. break;
  13. case LED_BOOTLOADER:
  14. gpio_toggle(BOARD_PORT_LEDS, BOARD_PIN_LED_BOOTLOADER);
  15. break;
  16. }
  17. }

8.7 获取芯片信息函数

获取芯片信息函数包括:get_mcu_id函数取包含芯片ID和版本的寄存器DBGMCU_IDCODE值,get_mcu_desc函数返回MCU的型号和版本描述信息。

8.7.1 get_mcu_id函数

get_mcu_id函数读取包含芯片ID和版本的寄存器DBGMCU_IDCODE值。


main_f4.c

  1. #define DBGMCU_IDCODE 0xE0042000 /* DBGMCU_IDCODE,寄存器,存放芯片ID和版本信息 */
  2. uint32_t get_mcu_id(void)
  3. {
  4. return *(uint32_t *)DBGMCU_IDCODE;
  5. }

8.7.2 get_mcu_desc函数

get_mcu_desc函数返回MCU的型号和版本描述信息。


main_f4.c

  1. #define DBGMCU_IDCODE 0xE0042000 /* DBGMCU_IDCODE,寄存器,存放芯片ID和版本信息 */
  2. #define DEVID_MASK 0xFFF /* MCU的ID存放在DBGMCU_IDCODE寄存器的低12位 */
  3. #define STM32_UNKNOWN 0 /* 未知芯片描述的index */
  4. int get_mcu_desc(int max, uint8_t *revstr)
  5. {
  6. /* 获取MCU的ID和版本,分别存放在变量mcuid和revid中 */
  7. uint32_t idcode = (*(uint32_t *)DBGMCU_IDCODE);
  8. int32_t mcuid = idcode & DEVID_MASK;
  9. mcu_rev_e revid = (idcode & REVID_MASK) >> 16;
  10. /* 初始化MCU描述信息为未知 */
  11. mcu_des_t des = mcu_descriptions[STM32_UNKNOWN];
  12. /* 根据mcuid依次查找数组mcu_descriptions中匹配项,若匹配则找到该MCU的型号描述 */
  13. /* mcu_descriptions:mcu_des_t型数组,用于存放MCU的型号描述信息,定义在main_f4.c */
  14. /* arraySize:以宏形式定义的函数,用于计算数组的成员个数,定义在bl.h */
  15. for (int i = 0; i < arraySize(mcu_descriptions); i ) {
  16. if (mcuid == mcu_descriptions[i].mcuid) {
  17. des = mcu_descriptions[i];
  18. break;
  19. }
  20. }
  21. /* 根据revid依次查找数组mcu_descriptions中匹配项,若匹配则找到该MCU的版本 */
  22. /* silicon_revs:mcu_rev_t型数组,用于存放MCU版本信息,定义在main_f4.c */
  23. /* arraySize:以宏形式定义的函数,用于计算数组的成员个数,定义在bl.h */
  24. for (int i = 0; i < arraySize(silicon_revs); i ) {
  25. if (silicon_revs[i].revid == revid) {
  26. des.rev = silicon_revs[i].rev;
  27. }
  28. }
  29. /* 按位字符操作,将MCU的描述和版本信息存储到缓冲区,中间用“,”隔开 */
  30. uint8_t *endp = &revstr[max - 1];
  31. uint8_t *strp = revstr;
  32. while (strp < endp && *des.desc) {
  33. *strp = *des.desc ;
  34. }
  35. if (strp < endp) {
  36. *strp = ',';
  37. }
  38. if (strp < endp) {
  39. *strp = des.rev;
  40. }
  41. return strp - revstr;
  42. }

8.8 check_silicon函数

check_silicon函数自动辨识运新的MCU版本是否为FIRST_BAD_SILICON_OFFSET序号以后的silicon_revs(mcu_rev_t型结构体数组)成员。在不改变代码的情况下,STM32F427型芯片REV_ID为0x2001时(Revision 3)返回0,其余均返回-1。


main_f4.c

  1. int check_silicon(void)
  2. {
  3. #if defined(TARGET_HW_PX4_FMU_V2) || defined(TARGET_HW_PX4_FMU_V4) /* 定义了宏TARGET_HW_PX4_FMU_V2,因此下列代码有效,将被编译。 */
  4. /* 宏DBGMCU_IDCODE被定义在main_f4.c中,代表地址为0xE0042000的寄存器。变量idcode包含信息REV_ID(31:16)和DEV_ID(11:0) */
  5. uint32_t idcode = (*(uint32_t *)DBGMCU_IDCODE);
  6. /* 宏REVID_MASK被定义在main_f4.c中,值为0xFFFF0000。变量revid为REV_ID的值 */
  7. mcu_rev_e revid = (idcode & REVID_MASK) >> 16;
  8. /* 宏FIRST_BAD_SILICON_OFFSET被main_f4.c定义为1,代表第1块sector(从0开始编号) */
  9. /* 变量silicon_revs为mcu_rev_t型结构体数组,存储MCU版本号与其代码的对应信息 */
  10. /* 以宏形式定义的函数arraySize()在bl.h中,其功能为求解数组的成员个数 */
  11. /* 此循环的功能是,若使用自FIRST_BAD_SILICON_OFFSET序号以后的MCU版本(REV_ID),则函数返回-1,否则返回0 */
  12. for (int i = FIRST_BAD_SILICON_OFFSET; i < arraySize(silicon_revs); i ) {
  13. if (silicon_revs[i].revid == revid) {
  14. return -1;
  15. }
  16. }
  17. #endif
  18. return 0;
  19. }

9 IO协处理器主线流程调用的函数

本节汇总了被主控FMU主线程序(main、jump_to_app和bootloader)调用的函数。

9.1 板载初始化相关函数

板载初始化相关函数包括board_init和反向初始化函数board_deinit,它们分别被main函数和jump_to_app函数调用。

9.1.1 board_init函数

board_init函数主要的功能如下:

  • 初始化LED控制并点亮LED灯
  • 设置强制Bootloader引脚GPIOB5为浮动输入模式,方便采集安全开关的状态
  • 使能能源接口时钟和备份接口时钟,准备备份寄存器
  • 设置USART2-TX引脚GPIOA2,对应原理图SERIAL_IO_TO_FMU,用于IO协处理器发送信息到主控FMU

main_f1.c

  1. static void board_init(void)
  2. {
  3. /* 1. 初始化LED控制并点亮LED灯 */
  4. /* 根据原理图,GPIOB14(IO-LED_BLUE)和GPIOB15(IO-LED_AMBER)分别控制IO协处理器的B/E(LED703)和ACT(LED705),低电平有效 */
  5. /* BOARD_CLOCK_LEDS_REGISTER:被定义为寄存器RCC_APB2ENR,hw_config.h */
  6. /* BOARD_CLOCK_LEDS:被hw_config.h定义为RCC_APB2ENR_IOPBEN,值1<<3(bit3),定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  7. /* BOARD_PORT_LEDS:被定义为GPIOB(GPIOB寄存器首地址0x40010C00),hw_config.h */
  8. /* GPIO_MODE_OUTPUT_50_MHZ:值0x03,定义在libopencm3/include/libopencm3/stm32/f1/gpio.h */
  9. /* GPIO_CNF_OUTPUT_PUSHPULL:值0x00,定义在libopencm3/include/libopencm3/stm32/f1/gpio.h */
  10. /* BOARD_PIN_LED_BOOTLOADER:被定义为GPIO15,值1<<15,定义在libopencm3/include/libopencm3/stm32/common/gpio_common_all.h */
  11. /* BOARD_PIN_LED_ACTIVITY:被定义为GPIO14,值1<<14,定义在libopencm3/include/libopencm3/stm32/common/gpio_common_all.h */
  12. /* rcc_peripheral_enable_clock:使能外部特定时钟,定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  13. /* gpio_set_mode:设置GPIO引脚工作模式,定义在libopencm3/lib/stm32/f1/gpio.c */
  14. /* BOARD_LED_ON:被hw_config.h定义为gpio_clear(libopencm3/lib/stm32/common/gpio_common_all.c) */
  15. /* 第一行,启动GPIOB时钟,通过操作RCC_APB2ENR寄存器的域IOPBEN(bit3) */
  16. /* 第二行,设置GPIOB14和GPIOB15为最大频率50MHz输出(MODE=GPIO_MODE_OUTPUT_50_MHZ=11),push-pull模式(CNF=GPIO_CNF_OUTPUT_PUSHPULL=00) */
  17. /* 第三行,设置GPIOB13和GPIOB15为低电平,点亮两个LED灯 */
  18. rcc_peripheral_enable_clock(&BOARD_CLOCK_LEDS_REGISTER, BOARD_CLOCK_LEDS);
  19. gpio_set_mode(BOARD_PORT_LEDS, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, BOARD_PIN_LED_BOOTLOADER | BOARD_PIN_LED_ACTIVITY);
  20. BOARD_LED_ON(BOARD_PORT_LEDS, BOARD_PIN_LED_BOOTLOADER | BOARD_PIN_LED_ACTIVITY);
  21. /* if we have one, enable the force-bootloader pin */
  22. #ifdef BOARD_FORCE_BL_PIN /* 宏BOARD_FORCE_BL_PIN有定义,代码有效 */
  23. /* 2. 设置强制Bootloader引脚GPIOB5为浮动输入模式,方便采集安全开关的状态 */
  24. /* 根据原理图,GPIOB5(IO-LED_SAFETY)连接安全开关,程序默认设置为高电平。采集到安全开关闭合(电平拉低)有效 */
  25. /* BOARD_FORCE_BL_CLOCK_REGISTER:被定义为寄存器RCC_APB2ENR,hw_config.h */
  26. /* BOARD_FORCE_BL_CLOCK_BIT:被hw_config.h定义为RCC_APB2ENR_IOPBEN,值1<<3(bit3),定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  27. /* BOARD_FORCE_BL_PORT:被定义为GPIOB(GPIOB寄存器首地址0x40010C00),hw_config.h */
  28. /* BOARD_FORCE_BL_PIN:被定义为GPIO5,值1<<5,定义在libopencm3/include/libopencm3/stm32/common/gpio_common_all.h */
  29. /* GPIO_MODE_INPUT:值0x00,libopencm3/include/libopencm3/stm32/f1/gpio.h */
  30. /* BOARD_FORCE_BL_PULL:被hw_config.h定义为GPIO_CNF_INPUT_FLOAT,值0x01,定义在libopencm3/include/libopencm3/stm32/f1/gpio.h */
  31. /* rcc_peripheral_enable_clock:使能外部特定时钟,定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  32. /* gpio_set:设置GPIO引脚电平,置1(set位),定义在libopencm3/lib/stm32/common/gpio_common_all.c */
  33. /* gpio_set_mode:设置GPIO引脚工作模式,libopencm3/lib/stm32/f1/gpio.c */
  34. /* 第一行,启动GPIOB时钟,通过操作RCC_APB2ENR寄存器的域IOPBEN(bit3) */
  35. /* 第二行,设置GPIOB5(IO-LED_SAFETY)为高电平 */
  36. /* 第三行,设置GPIOB5(IO-LED_SAFETY)为输入模式(MODE=GPIO_MODE_INPUT=00),浮动模式(CNF=BOARD_FORCE_BL_PULL=01) */
  37. rcc_peripheral_enable_clock(&BOARD_FORCE_BL_CLOCK_REGISTER, BOARD_FORCE_BL_CLOCK_BIT);
  38. gpio_set(BOARD_FORCE_BL_PORT, BOARD_FORCE_BL_PIN);
  39. gpio_set_mode(BOARD_FORCE_BL_PORT, GPIO_MODE_INPUT, BOARD_FORCE_BL_PULL, BOARD_FORCE_BL_PIN);
  40. #endif
  41. /* 3. 使能能源接口时钟和备份接口时钟,准备备份寄存器 */
  42. /* RCC_APB1ENR:APB1寄存器,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  43. /* RCC_APB1ENR_PWREN:值1<<28,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  44. /* RCC_APB1ENR_BKPEN:值1<<27,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  45. /* rcc_peripheral_enable_clock:使能外部特定时钟,定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  46. rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN);
  47. #ifdef INTERFACE_USART /* 宏INTERFACE_USART定义为USART2,hw_config.h,代码有效 */
  48. /* 4. 设置USART2-TX引脚GPIOA2,对应原理图SERIAL_IO_TO_FMU,用于IO协处理器发送信息到主控FMU */
  49. /* BOARD_USART_PIN_CLOCK_REGISTER:被定义为寄存器RCC_APB2ENR,hw_config.h */
  50. /* BOARD_USART_PIN_CLOCK_BIT:被hw_config.h定义为RCC_APB2ENR_IOPAEN,值1<<2(bit2,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h) */
  51. /* BOARD_PORT_USART:被定义为GPIOA(GPIOA寄存器首地址0x40010800),hw_config.h */
  52. /* GPIO_MODE_OUTPUT_50_MHZ:值0x03,定义在libopencm3/include/libopencm3/stm32/f1/gpio.h */
  53. /* GPIO_CNF_OUTPUT_ALTFN_PUSHPULL:值0x02,定义在libopencm3/include/libopencm3/stm32/f1/gpio.h */
  54. /* BOARD_PIN_TX:被hw_config.h定义为GPIO_USART2_TX,值GPIO2(1<<2),libopencm3/include/libopencm3/stm32/f1/gpio.h */
  55. /* BOARD_USART_CLOCK_REGISTER:被定义为寄存器RCC_APB1ENR,hw_config.h */
  56. /* BOARD_USART_CLOCK_BIT:被hw_config.h定义为RCC_APB1ENR_USART2EN,值1<<17,libopencm3/include/libopencm3/stm32/f1/rcc.h */
  57. /* rcc_peripheral_enable_clock:使能外部特定时钟,定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  58. /* gpio_set_mode:设置GPIO引脚工作模式,libopencm3/lib/stm32/f1/gpio.c */
  59. /* 第一行,使能USART2所在的GPIOA时钟 */
  60. /* 第二行,设置USART2的发送引脚GPIOA2为50MHz输出模式(MODE=GPIO_MODE_OUTPUT_50_MHZ=11),特殊功能push-pull模式(CNF=GPIO_CNF_OUTPUT_ALTFN_PUSHPULL=10) */
  61. /* 第三行,重置USART2时钟 */
  62. rcc_peripheral_enable_clock(&BOARD_USART_PIN_CLOCK_REGISTER, BOARD_USART_PIN_CLOCK_BIT);
  63. gpio_set_mode(BOARD_PORT_USART, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, BOARD_PIN_TX);
  64. rcc_peripheral_enable_clock(&BOARD_USART_CLOCK_REGISTER, BOARD_USART_CLOCK_BIT);
  65. #endif
  66. #ifdef INTERFACE_I2C /* 宏INTERFACE_I2C未定义,代码无效 */
  67. # error I2C GPIO config not handled yet
  68. #endif
  69. }

9.1.2 board_deinit函数

board_deinit函数用于板载设备的反向初始化,主要的功能如下:

  • 无效化LED控制引脚GPIOB14和GPIOB15
  • 无效化强制Bootloader引脚GPIOB5
  • 关闭能源接口时钟和备份接口时钟,不可访问备份寄存器
  • 无效化USART2-TX引脚GPIOA2
  • 设置APB2寄存器为默认值,关闭所有外部时钟

main_f1.c

  1. void board_deinit(void)
  2. {
  3. /* 1. 无效化LED控制引脚GPIOB14和GPIOB15 */
  4. /* 根据原理图,GPIOB14(IO-LED_BLUE)和GPIOB15(IO-LED_AMBER)分别控制IO协处理器的B/E(LED703)和ACT(LED705),低电平有效 */
  5. /* BOARD_PORT_LEDS:被定义为GPIOB(GPIOB寄存器首地址0x40010C00),hw_config.h */
  6. /* GPIO_MODE_INPUT:值0x00,libopencm3/include/libopencm3/stm32/f1/gpio.h */
  7. /* GPIO_CNF_INPUT_FLOAT:值0x01,libopencm3/include/libopencm3/stm32/f1/gpio.h */
  8. /* BOARD_PIN_LED_BOOTLOADER:被定义为GPIO15,值1<<15,定义在libopencm3/include/libopencm3/stm32/common/gpio_common_all.h */
  9. /* BOARD_PIN_LED_ACTIVITY:被定义为GPIO14,值1<<14,定义在libopencm3/include/libopencm3/stm32/common/gpio_common_all.h */
  10. /* gpio_set_mode:设置GPIO引脚工作模式,定义在libopencm3/lib/stm32/f1/gpio.c */
  11. /* 设置GPIOB14和GPIOB15为输入模式(MODE=GPIO_MODE_INPUT=00),浮动模式(CNF=GPIO_CNF_INPUT_FLOAT=01) */
  12. gpio_set_mode(BOARD_PORT_LEDS, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, BOARD_PIN_LED_BOOTLOADER | BOARD_PIN_LED_ACTIVITY);
  13. #ifdef BOARD_FORCE_BL_PIN /* 宏BOARD_FORCE_BL_PIN定义为GPIO5,代码有效,hw_config.h */
  14. /* 2. 无效化强制Bootloader引脚GPIOB5 */
  15. /* BOARD_FORCE_BL_PORT:被定义为GPIOB(GPIOB寄存器首地址0x40010C00),hw_config.h */
  16. /* GPIO_MODE_INPUT:值0x00,libopencm3/include/libopencm3/stm32/f1/gpio.h */
  17. /* GPIO_CNF_INPUT_FLOAT:值0x01,libopencm3/include/libopencm3/stm32/f1/gpio.h */
  18. /* BOARD_FORCE_BL_PIN:被定义为GPIO5,值1<<5,定义在libopencm3/include/libopencm3/stm32/common/gpio_common_all.h */
  19. /* gpio_set_mode:设置GPIO引脚工作模式,libopencm3/lib/stm32/f1/gpio.c */
  20. /* gpio_clear:设置GPIO引脚电平,清0(reset位),定义在libopencm3/lib/stm32/common/gpio_common_all.c */
  21. /* 第一行,设置GPIOB5(IO-LED_SAFETY)为输入模式(MODE=GPIO_MODE_INPUT=00),浮动模式(CNF=BOARD_FORCE_BL_PULL=01) */
  22. /* 第二行,设置GPIOB5(IO-LED_SAFETY)为低电平 */
  23. gpio_set_mode(BOARD_FORCE_BL_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, BOARD_FORCE_BL_PIN);
  24. gpio_clear(BOARD_FORCE_BL_PORT, BOARD_FORCE_BL_PIN);
  25. #endif
  26. /* 3. 关闭能源接口时钟和备份接口时钟,不可访问备份寄存器 */
  27. /* RCC_APB1ENR:APB1寄存器,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  28. /* RCC_APB1ENR_PWREN:值1<<28,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  29. /* RCC_APB1ENR_BKPEN:值1<<27,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  30. /* rcc_peripheral_enable_clock:使能外部特定时钟,定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  31. rcc_peripheral_disable_clock(&RCC_APB1ENR, RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN);
  32. #ifdef INTERFACE_USART /* 宏INTERFACE_USART定义为1,代码有效 */
  33. /* 4. 无效化USART2-TX引脚GPIOA2 */
  34. /* BOARD_PORT_USART:被定义为GPIOA(GPIOA寄存器首地址0x40010800),hw_config.h */
  35. /* GPIO_MODE_INPUT:值0x00,libopencm3/include/libopencm3/stm32/f1/gpio.h */
  36. /* GPIO_CNF_INPUT_FLOAT:值0x01,libopencm3/include/libopencm3/stm32/f1/gpio.h */
  37. /* BOARD_PIN_TX:被hw_config.h定义为GPIO_USART2_TX,值GPIO2(1<<2),libopencm3/include/libopencm3/stm32/f1/gpio.h */
  38. /* BOARD_USART_CLOCK_REGISTER:被定义为寄存器RCC_APB1ENR,hw_config.h */
  39. /* BOARD_USART_CLOCK_BIT:被hw_config.h定义为RCC_APB1ENR_USART2EN,值1<<17,libopencm3/include/libopencm3/stm32/f1/rcc.h */
  40. /* rcc_peripheral_enable_clock:使能外部特定时钟,定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  41. /* gpio_set_mode:设置GPIO引脚工作模式,libopencm3/lib/stm32/f1/gpio.c */
  42. /* 第一行,设置USART2的发送引脚GPIOA2为输入(MODE=GPIO_MODE_INPUT=00),浮动模式(CNF=GPIO_CNF_INPUT_FLOAT=01) */
  43. /* 第二行,关闭USART2时钟 */
  44. gpio_set_mode(BOARD_PORT_USART, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, BOARD_PIN_TX);
  45. rcc_peripheral_disable_clock(&BOARD_USART_CLOCK_REGISTER, BOARD_USART_CLOCK_BIT);
  46. #endif
  47. #ifdef INTERFACE_I2C /* 宏INTERFACE_I2C未定义,代码无效 */
  48. # error I2C GPIO config not handled yet
  49. #endif
  50. /* 5. 设置APB2寄存器为默认值,关闭所有外部时钟 */
  51. /* RCC_APB2ENR:寄存器,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  52. RCC_APB2ENR = 0x00000000; // XXX Magic reset number from STM32F1x reference manual
  53. }

9.2 时钟初始化相关函数

板载初始化相关函数包括clock_init和反向初始化函数clock_deinit,它们分别被main函数和jump_to_app函数调用。

9.2.1 clock_init函数

clock_init函数调用库函数,使用HSI将MCU的系统时钟为PLL,频率48MHz(BUG?应该设置为24MHz,对应board_info.systick_mhz)。


main_f1.c

  1. static inline void clock_init(void)
  2. {
  3. #if defined(INTERFACE_USB) /* 宏INTERFACE_USB定义为0,代码有效 */
  4. /* rcc_clock_setup_in_hsi_out_48mhz:使用HSI设置MCU的系统时钟为PLL,48MHz,定义在libopencm3/lib/stm32/f1/rcc.c */
  5. rcc_clock_setup_in_hsi_out_48mhz();
  6. #else
  7. rcc_clock_setup_in_hsi_out_24mhz();
  8. #endif
  9. }

9.2.2 clock_deinit函数

clock_deinit函数关闭时钟源,将所有内部时钟设置为重启后的初始状态,为飞控固件的运行提供良好的初始环境,主要操作如下:

  • 使能HSI时钟
  • 重置寄存器RCC_CFGR,清空时钟配置
  • 关闭时钟HSE、PLL、PLLI2S、PLLSAI并关闭时钟安全系统(CSS),此时切换为HSI
  • 关闭HSE旁路功能
  • 清除所有时钟中断

main_f1.c

  1. void clock_deinit(void)
  2. {
  3. /* 1. 使能HSI时钟 */
  4. /* RCC_HSI:值4,rcc_osc枚举型,libopencm3/include/libopencm3/stm32/f1/rcc.h */
  5. /* rcc_osc_on:开启某内部时钟,操作寄存器RCC_CR。rcc_osc_on(RCC_HSI)操作HSION域(bit0),定义在libopencm3/lib/stm32/f1/rcc.c */
  6. /* rcc_wait_for_osc_ready:等待某内部时钟稳定,读取寄存器RCC_CR。rcc_wait_for_osc_ready(RCC_HSI)读取HSIRDY(bit1),定义在libopencm3/lib/stm32/f1/rcc.c */
  7. rcc_osc_on(RCC_HSI);
  8. rcc_wait_for_osc_ready(RCC_HSI);
  9. /* 2. 重置寄存器RCC_CFGR,清空时钟配置 */
  10. /* RCC_CFGR:寄存器,libopencm3/include/libopencm3/stm32/f1/rcc.h */
  11. RCC_CFGR = 0x000000;
  12. /* 3. 关闭时钟HSE、PLL、PLLI2S、PLLSAI并关闭时钟安全系统(CSS),此时切换为HSI */
  13. /* RCC_HSE:值3,rcc_osc枚举变量,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  14. /* RCC_PLL:值0,rcc_osc枚举变量,定义在libopencm3/include/libopencm3/stm32/f1/rcc.h */
  15. /* rcc_osc_off:关闭某内部时钟,操作寄存器RCC_CR。定义在libopencm3/lib/stm32/f1/rcc.c */
  16. /* rcc_css_disable:关闭时钟安全系统,操作寄存器RCC_CR的域CSSON(bit19)。定义在libopencm3/lib/stm32/f1/rcc.c */
  17. /* rcc_osc_off(RCC_HSE)操作HSEON域(bit16),rcc_osc_off(RCC_PLL)操作PLLON域(bit24)。定义在libopencm3/lib/stm32/f1/rcc.c */
  18. /* Stop the HSE, CSS, PLL, PLLI2S, PLLSAI */
  19. rcc_osc_off(RCC_HSE);
  20. rcc_osc_off(RCC_PLL);
  21. rcc_css_disable();
  22. /* 4. 关闭HSE旁路功能 */
  23. /* RCC_HSE:值3,rcc_osc枚举变量,定义在libopencm3/include/libopencm3/stm32/f4/rcc.h */
  24. /* rcc_osc_bypass_disable:关闭HSE的旁路功能,操作寄存器RCC_CR的HSEBYP域(bit18) */
  25. rcc_osc_bypass_disable(RCC_HSE);
  26. /* 5. 清除所有时钟中断 */
  27. /* RCC_CIR:RCC时钟中断寄存器 */
  28. RCC_CIR = 0x000000;
  29. }

9.3 LED操作函数

LED操作函数包含点亮led_on、熄灭led_off和反向led_toggle三个函数。

9.3.1 led_on函数


main_f1.c

  1. void led_on(unsigned led)
  2. {
  3. /* LED_ACTIVITY:值1,定义在bl.h */
  4. /* LED_BOOTLOADER:值2,定义在bl.h */
  5. /* BOARD_PORT_LEDS:值GPIOB(指向GPIOB首寄存器),定义在hw_config.h */
  6. /* BOARD_PIN_LED_ACTIVITY:值GPIO14(1<<14),定义在hw_config.h */
  7. /* BOARD_PIN_LED_BOOTLOADER:值GPIO12(1<<15),定义在hw_config.h */
  8. /* BOARD_LED_ON:被hw_config.h定义为gpio_clear函数,功能为置某GPIO组中的引脚输出为低电平(libopencm3/lib/stm32/common/gpio_common_all.c) */
  9. switch (led) {
  10. case LED_ACTIVITY:
  11. BOARD_LED_ON(BOARD_PORT_LEDS, BOARD_PIN_LED_ACTIVITY);
  12. break;
  13. case LED_BOOTLOADER:
  14. BOARD_LED_ON(BOARD_PORT_LEDS, BOARD_PIN_LED_BOOTLOADER);
  15. break;
  16. }
  17. }

9.3.2 led_off函数


main_f1.c

  1. void led_off(unsigned led)
  2. {
  3. /* LED_ACTIVITY:值1,定义在bl.h */
  4. /* LED_BOOTLOADER:值2,定义在bl.h */
  5. /* BOARD_PORT_LEDS:值GPIOB(指向GPIOB首寄存器),定义在hw_config.h */
  6. /* BOARD_PIN_LED_ACTIVITY:值GPIO14(1<<14),定义在hw_config.h */
  7. /* BOARD_PIN_LED_BOOTLOADER:值GPIO12(1<<15),定义在hw_config.h */
  8. /* BOARD_LED_OFF:被hw_config.h定义为gpio_set函数,功能为置某GPIO组中的引脚输出为高电平(libopencm3/lib/stm32/common/gpio_common_all.c) */
  9. switch (led) {
  10. case LED_ACTIVITY:
  11. BOARD_LED_OFF(BOARD_PORT_LEDS, BOARD_PIN_LED_ACTIVITY);
  12. break;
  13. case LED_BOOTLOADER:
  14. BOARD_LED_OFF(BOARD_PORT_LEDS, BOARD_PIN_LED_BOOTLOADER);
  15. break;
  16. }
  17. }

9.3.3 led_off函数


  1. void led_toggle(unsigned led)
  2. {
  3. /* LED_ACTIVITY:值1,定义在bl.h */
  4. /* LED_BOOTLOADER:值2,定义在bl.h */
  5. /* BOARD_PORT_LEDS:值GPIOB(指向GPIOB首寄存器),定义在hw_config.h */
  6. /* BOARD_PIN_LED_ACTIVITY:值GPIO14(1<<14),定义在hw_config.h */
  7. /* BOARD_PIN_LED_BOOTLOADER:值GPIO12(1<<15),定义在hw_config.h */
  8. /* gpio_toggle:反向函数,定义在libopencm3/lib/sam/common/gpio_common_all.c */
  9. switch (led) {
  10. case LED_ACTIVITY:
  11. gpio_toggle(BOARD_PORT_LEDS, BOARD_PIN_LED_ACTIVITY);
  12. break;
  13. case LED_BOOTLOADER:
  14. gpio_toggle(BOARD_PORT_LEDS, BOARD_PIN_LED_BOOTLOADER);
  15. break;
  16. }
  17. }

9.4 flash操作函数

这里的flash操作是针对飞控固件部分flash的。

9.4.1 flash_func_read_word函数

flash_func_read_word函数返回飞控固件flash域内某地址的值,输入的地址值需加上APP_LOAD_ADDRESS(0x08001000)。


main_f1.c

  1. uint32_t flash_func_read_word(uint32_t address)
  2. {
  3. /* APP_LOAD_ADDRESS:值0x08001000,定义在hw_config.h */
  4. return *(uint32_t *)(address APP_LOAD_ADDRESS);
  5. }

9.4.2 flash_func_sector_size函数

flash_func_sector_size函数返回MCU的flash在某sector的大小值,若无此sector,则返回0。对于STM32F1芯片,片内以页计算,无sector概念,故这里的sector表示页。


main_f1.c

  1. /* BOARD_FLASH_SECTORS:STM32F1片内flash页数,Bootloader认为片内flash的前60页有效,定义在hw_config.h */
  2. /* FLASH_SECTOR_SIZE:STM32F1片内flash每页大小为1KB,定义在hw_config.h */
  3. uint32_t flash_func_sector_size(unsigned sector)
  4. {
  5. if (sector < BOARD_FLASH_SECTORS) {
  6. return FLASH_SECTOR_SIZE;
  7. }
  8. return 0;
  9. }

9.4.3 flash_func_erase_sector函数

flash_func_erase_sector函数擦除当前页。


main_f1.c

  1. void flash_func_erase_sector(unsigned sector)
  2. {
  3. /* BOARD_FLASH_SECTORS:值60,表示MCU中flash的页数,定义在hw_config.h */
  4. /* APP_LOAD_ADDRESS:值0x08001000,表示飞控固件起始地址,定义在hw_config.h */
  5. /* FLASH_SECTOR_SIZE:值0x400,表示MCU中每页flash的大小均为1KB,定义在hw_config.h */
  6. /* flash_erase_page:库函数擦写当前地址所在的页,定义在libopencm3/lib/stm32/f1/flash.c */
  7. if (sector < BOARD_FLASH_SECTORS) {
  8. flash_erase_page(APP_LOAD_ADDRESS (sector * FLASH_SECTOR_SIZE));
  9. }
  10. }

9.4.4 flash_func_write_word函数

flash_func_write_word函数用于编写特定地址的flash,以4字节编写,输入地址自动加上APP_LOAD_ADDRESS(0x08004000)。


main_f1.c

  1. void flash_func_write_word(uint32_t address, uint32_t word)
  2. {
  3. /* flash_program_word:在某地址编写flash字的库函数,定义在libopencm3/lib/stm32/common/flash_common_f01.c */
  4. flash_program_word(address APP_LOAD_ADDRESS, word);
  5. }

9.4.5 flash_func_read_otp函数

STM32F1芯片无OTP区,函数直接返回0。


main_f1.c

  1. uint32_t flash_func_read_otp(uint32_t address)
  2. {
  3. return 0;
  4. }

9.4.6 flash_func_read_sn函数

flash_func_read_sn函数读取MCU的UDID(Unique Device ID)某字(共12byte,3字)。


main_f1.c

  1. #define UDID_START 0x1FFFF7E8 /* UDID_START:UDID寄存器基地址,值0x1FFFF7E8,定义在main_f1.c */
  2. uint32_t flash_func_read_sn(uint32_t address)
  3. {
  4. return *(uint32_t *)(address UDID_START);
  5. }

9.5 获取芯片信息函数

9.5.1 get_mcu_id函数

get_mcu_id函数读取包含芯片ID和版本的寄存器DBGMCU_IDCODE值。


main_f1.c

  1. #define DBGMCU_IDCODE 0xE0042000 /* DBGMCU_IDCODE:寄存器,存放芯片ID和版本信息 */
  2. uint32_t get_mcu_id(void)
  3. {
  4. return *(uint32_t *)DBGMCU_IDCODE;
  5. }

9.5.2 get_mcu_desc函数

get_mcu_desc函数直接返回MCU型号描述和版本信息,为固定值'STM32F1xxx,?'


main_f1.c

  1. int get_mcu_desc(int max, uint8_t *revstr)
  2. {
  3. const char none[] = 'STM32F1xxx,?';
  4. int i;
  5. for (i = 0; none[i] && i < max - 1; i ) {
  6. revstr[i] = none[i];
  7. }
  8. return i;
  9. }

9.6 should_wait函数

should_wait函数被main函数调用,判定RTC备份寄存器BKP_DR1中是否存储了预定值,如果存储了就返回真,否则返回假。


main_f1.c

  1. static bool should_wait(void)
  2. {
  3. bool result = false; /* 默认返回假 */
  4. /* 打开RTC备份寄存器访问 */
  5. /* PWR_CR:功率控制寄存器,定义在libopencm3/include/libopencm3/stm32/common/pwr_common_all.h */
  6. /* PWR_CR_DBP:值1<<8(bit8),定义在libopencm3/include/libopencm3/stm32/common/pwr_common_all.h */
  7. PWR_CR |= PWR_CR_DBP;
  8. /* 若RTC备份寄存器BKP_DR1的值等于预定值0x19710317,则返回真且清零寄存器 */
  9. /* BKP_DR1:RTC备份寄存器1,定义在libopencm3/include/libopencm3/stm32/f1/bkp.h */
  10. /* BL_WAIT_MAGIC:值0x19710317,定义在bl.h */
  11. if (BKP_DR1 == BL_WAIT_MAGIC) {
  12. result = true;
  13. BKP_DR1 = 0;
  14. }
  15. /* 关闭RTC备份寄存器访问 */
  16. /* PWR_CR:功率控制寄存器,定义在libopencm3/include/libopencm3/stm32/common/pwr_common_all.h */
  17. /* PWR_CR_DBP:值1<<8(bit8),定义在libopencm3/include/libopencm3/stm32/common/pwr_common_all.h */
  18. PWR_CR &= ~PWR_CR_DBP;
  19. return result;
  20. }

10 串口和USB虚拟串口函数

串口和USB虚拟串口用于PX4 Bootloader与上位机的通信,可用于固件烧写和程序调试。除此之外,本节还包括对应的反初始化函数,它们被jump_to_app函数调用,使Bootloader放弃串口的控制权。

10.1 串口和USB虚拟串口初始化相关函数

串口和USB虚拟串口初始化包括cinit、uart_cinit和usb_cinit共3个函数

10.1.1 cinit函数

cinit函数是串口和USB虚拟串口初始化的主要函数,初始化后Bootloader可与上位机的通信接口,根据输入命令对Bootloader进行调试。cinit函数根据输入interface的值决定具体调用的初始化函数;uart_cinit用于初始化串口,usb_cinit用于初始化USB接口。


bl.c

  1. /* USART:值为1,枚举类,定义在bl.h */
  2. /* USB:值为2,枚举类,定义在bl.h */
  3. inline void cinit(void *config, uint8_t interface)
  4. {
  5. #if INTERFACE_USB /* 主控FMU:宏INTERFACE_USB值为1,代码有效,定义在hw_config.h */
  6. /* IO协处理器:宏INTERFACE_USB值为0,代码无效,定义在hw_config.h */
  7. if (interface == USB) {
  8. return usb_cinit();
  9. }
  10. #endif
  11. #if INTERFACE_USART /* 宏INTERFACE_USART值为1,代码有效,定义在hw_config.h */
  12. if (interface == USART) {
  13. return uart_cinit(config);
  14. }
  15. #endif
  16. }

10.1.2 uart_cinit函数

usart初始化函数uart_cinit,输入参数config为void*型指针,值为USART寄存器基地址。此函数使用SOC上的串口寄存器进行设置,功能为115200@8N1,收发通信,无硬件流控制。在pixahwk V2硬件和PX4代码使用USART作为上位机通信串口,config值USART2的寄存器基地址(0x40004400)。


usart.c

  1. uint32_t usart; /* 全局变量,uart_cinit函数中被赋值为USART2 */
  2. void uart_cinit(void *config)
  3. {
  4. /* 全局变量usart指向串口寄存器基地址 */
  5. usart = (uint32_t)config;
  6. /* 引脚和时钟初始化 */
  7. /* USART_CR1:根据给定的USART寄存器首地址移位0x0c来获取寄存器USART_CR1。 */
  8. /* 寄存器USART_CR1的bit15为OVER8域,控制串口的过采样模式。默认值0:16次过采样,值1:8次过采样。 */
  9. //USART_CR1(usart) |= (1 << 15); /* libopencm3不支持USART_CR1寄存器的OVER8位操作 */
  10. /* USART_BAUDRATE:值115200,定义在hw_config.h */
  11. /* usart_set_baudrate:用于设置串口波特率,操作寄存器USART_BRR,定义在libopencm3/lib/stm32/common/usart_common_all.c */
  12. /* 库函数usart_set_baudrate用到了库全局变量rcc_apb1_frequency作为时钟输入。 */
  13. usart_set_baudrate(usart, USART_BAUDRATE);
  14. /* usart_set_databits:用于设置串口数据位数(8或9),操作寄存器USART_CR1域M(bit12),定义在libopencm3/lib/stm32/common/usart_common_all.c */
  15. usart_set_databits(usart, 8);
  16. /* USART_STOPBITS_1:值0x00<<12,表示停止位1(libopencm3/include/libopencm3/stm32/common/usart_common_all.h) */
  17. /* usart_set_stopbits:用于设置串口停止位数,操作寄存器USART_CR2域STOP(bit13:12),定义在libopencm3/lib/stm32/common/usart_common_all.c */
  18. usart_set_stopbits(usart, USART_STOPBITS_1);
  19. /* USART_MODE_TX_RX:值USART_CR1_RE|USART_CR1_TE,表示收 发(libopencm3/include/libopencm3/stm32/common/usart_common_all.h) */
  20. /* USART_CR1_RE:值1<<2(libopencm3/include/libopencm3/stm32/common/usart_common_f124.h) */
  21. /* USART_CR1_TE:值1<<3(libopencm3/include/libopencm3/stm32/common/usart_common_f124.h) */
  22. /* usart_set_mode:用于设置串口传输模式(单收,单发,收 发),操作寄存器USART_CR1域TE(bit3)和域RE(bit2),定义在libopencm3/lib/stm32/common/usart_common_all.c */
  23. usart_set_mode(usart, USART_MODE_TX_RX);
  24. /* USART_PARITY_NONE:值0x00,表示无校验(libopencm3/include/libopencm3/stm32/common/usart_common_all.h) */
  25. /* usart_set_parity:用于设置串口校验模式(无、奇、偶),操作寄存器USART_CR1域PCE(bit10)和域PS(bit9),定义在libopencm3/lib/stm32/common/usart_common_all.c */
  26. usart_set_parity(usart, USART_PARITY_NONE);
  27. /* USART_FLOWCONTROL_NONE:值0x00,表示无硬件流控制(libopencm3/include/libopencm3/stm32/common/usart_common_all.h) */
  28. /* usart_set_flow_control:用于设置硬件流控制(无、RTS、CTS、RTS CTS),操作寄存器USART_CR3的CTSE(bit9)和RTSE(bit8) */
  29. usart_set_flow_control(usart, USART_FLOWCONTROL_NONE);
  30. /* usart_enable:用于使能串口,操作寄存器USART_CR1的域UE(bit13),定义在libopencm3/lib/stm32/common/usart_common_all.c */
  31. usart_enable(usart);
  32. }

10.1.3 usb_cinit函数

USB虚拟串口初始化函数usb_cinit,根据CDC ACM虚拟串口协议对板载USB OTG FS进行设备初始化,115200@8N1,收发通信。本函数使用了USB的设备描述符(usb_device_descriptor)、配置描述符(usb_config_descriptor)、接口描述符(usb_interface_descriptor)、端点描述符(usb_endpoint_descriptor),这些描述符的定义都在libopencm3/include/libopencm3/usb/usbstd.h中。这些描述符支撑了一个全局使用的_usbd_device型结构体指针usbd_dev,它包含了USB的所有配置信息和操作函数,对它成员的配置就是本函数的核心。配置完成后USB接口的操作就非常方便了。


cdcacm.c

  1. #define USB_CDC_REQ_GET_LINE_CODING 0x21
  2. #define OTG_CID_HAS_VBDEN 0x00002000
  3. #define OTG_GCCFG_VBDEN (1 << 21)
  4. /* USB设备字符串列表,下标从1开始 */
  5. static const char *usb_strings[] = {
  6. USBMFGSTRING, /* USBMFGSTRING:值'3D Robotics',生产厂家名称,INDEX 1。(hw_config.h) */
  7. USBDEVICESTRING, /* USBDEVICESTRING:'PX4 BL FMU v2.x',USB设备名称,INDEX2。(hw_config.h) */
  8. '0', /* 设备序号,INDEX 3 */
  9. };
  10. #define NUM_USB_STRINGS (sizeof(usb_strings)/sizeof(usb_strings[0])) /* 上面定义的字符串数组usb_strings的成员个数 */
  11. static usbd_device *usbd_dev; /* _usbd_device型结构体全局指针变量,包含一系列USB信息与操作函数(libopencm3/lib/usb/usb_private.h) */
  12. /* Buffer to be used for control requests. */
  13. static uint8_t usbd_control_buffer[128];
  14. /* 标准的USB设备描述符结构体变量dev,属性packed,定义在libopencm3/include/libopencm3/usb/usbstd.h */
  15. static const struct usb_device_descriptor dev = {
  16. .bLength = USB_DT_DEVICE_SIZE, /* USB_DT_DEVICE_SIZE:值18,本结构体的大小(libopencm3/include/libopencm3/usb/usbstd.h) */
  17. .bDescriptorType = USB_DT_DEVICE, /* USB_DT_DEVICE:值1,本结构体为USB设备描述符(libopencm3/include/libopencm3/usb/usbstd.h) */
  18. .bcdUSB = 0x0200, /* USB接口版本,0x0200表示符合USB2.0规范 */
  19. .bDeviceClass = USB_CLASS_CDC, /* USB_CLASS_CDC:值0x2,设备群组码(libopencm3/include/libopencm3/usb/cdc.h) */
  20. .bDeviceSubClass = 0, /* 设备次群组码 */
  21. .bDeviceProtocol = 0, /* 通信设备协议,0表示无群组特定协议 */
  22. .bMaxPacketSize0 = 64, /* 设备最大信息包大小为64字节(只能为8、16、32、64) */
  23. .idVendor = USBVENDORID, /* USBVENDORID:值0x26AC,USB设备厂家ID,这里是默认值(hw_config.h) */
  24. .idProduct = USBPRODUCTID, /* USBPRODUCTID:值0x0011,USB产品ID(hw_config.h) */
  25. .bcdDevice = 0x0101, /* USB设备发行序号,0x0101表示1.01版,为了兼容NuttX */
  26. .iManufacturer = 1, /* USB设备厂家字符串下标,1代表USBMFGSTRING('3D Robotics'),数组usb_strings */
  27. .iProduct = 2, /* USB产品字符串下标,2代表USBDEVICESTRING('PX4 BL FMU v2.x'),数组usb_strings */
  28. .iSerialNumber = 3, /* USB设备序号下标,3代表空('0') */
  29. .bNumConfigurations = 1, /* USB设备配置的数目为1个(config) */
  30. };
  31. /* USB通信端点描述符结构体数组变量comm_endp,属性packed,定义在libopencm3/include/libopencm3/usb/usbstd.h */
  32. /* 这里仅对端点描述符的内容进行了赋值,结构体规定的额外内容在UNIX环境中被默认为0 */
  33. static const struct usb_endpoint_descriptor comm_endp[] = {{
  34. .bLength = USB_DT_ENDPOINT_SIZE, /* USB_DT_ENDPOINT_SIZE:值7,端点描述符大小(libopencm3/include/libopencm3/usb/usbstd.h) */
  35. .bDescriptorType = USB_DT_ENDPOINT, /* USB_DT_ENDPOINT:值5,表示本结构体为USB端点描述符(libopencm3/include/libopencm3/usb/usbstd.h) */
  36. .bEndpointAddress = 0x83, /* 通信端点地址为0x83 */
  37. .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, /* USB_ENDPOINT_ATTR_INTERRUPT:值0x03,本通信端点为中断传输(libopencm3/include/libopencm3/usb/usbstd.h) */
  38. .wMaxPacketSize = 16, /* 数据包最大16字节 */
  39. .bInterval = 255, /* 轮询间隔255ms */
  40. }
  41. };
  42. /* USB数据端点描述符结构体数组变量data_endp(2个,一般称为DATA0和DATA1),属性packed,定义在libopencm3/include/libopencm3/usb/usbstd.h */
  43. /* 这里仅对端点描述符的内容进行了赋值,结构体规定的额外内容在UNIX环境中被默认为0 */
  44. static const struct usb_endpoint_descriptor data_endp[] = {{
  45. .bLength = USB_DT_ENDPOINT_SIZE, /* USB_DT_ENDPOINT_SIZE:值7,端点描述符大小(libopencm3/include/libopencm3/usb/usbstd.h) */
  46. .bDescriptorType = USB_DT_ENDPOINT, /* USB_DT_ENDPOINT:值5,表示本结构体为USB端点描述符(libopencm3/include/libopencm3/usb/usbstd.h) */
  47. .bEndpointAddress = 0x01, /* 此数据端点的地址为0x01 */
  48. .bmAttributes = USB_ENDPOINT_ATTR_BULK, /* USB_ENDPOINT_ATTR_BULK:值0x2,本数据端点为批量传输(libopencm3/include/libopencm3/usb/usbstd.h) */
  49. .wMaxPacketSize = 64, /* 数据包最大64字节 */
  50. .bInterval = 1, /* 轮询间隔1ms */
  51. }, {
  52. .bLength = USB_DT_ENDPOINT_SIZE, /* USB_DT_ENDPOINT_SIZE:值7,端点描述符大小(libopencm3/include/libopencm3/usb/usbstd.h) */
  53. .bDescriptorType = USB_DT_ENDPOINT, /* USB_DT_ENDPOINT:值5,表示本结构体为USB端点描述符(libopencm3/include/libopencm3/usb/usbstd.h) */
  54. .bEndpointAddress = 0x82, /* 此数据端点的地址为0x82 */
  55. .bmAttributes = USB_ENDPOINT_ATTR_BULK, /* USB_ENDPOINT_ATTR_BULK:值0x2,本数据端点为批量传输(libopencm3/include/libopencm3/usb/usbstd.h) */
  56. .wMaxPacketSize = 64, /* 数据包最大64字节 */
  57. .bInterval = 1, /* 轮询间隔1ms */
  58. }
  59. };
  60. /* 对USB CDC通信需要额外提供的信息进行汇总,用于到关联通信接口描述符comm_iface */
  61. static const struct {
  62. struct usb_cdc_header_descriptor header;
  63. struct usb_cdc_call_management_descriptor call_mgmt;
  64. struct usb_cdc_acm_descriptor acm;
  65. struct usb_cdc_union_descriptor cdc_union;
  66. } __attribute__((packed)) cdcacm_functional_descriptors = {
  67. .header = {
  68. .bFunctionLength = sizeof(struct usb_cdc_header_descriptor),
  69. .bDescriptorType = CS_INTERFACE,
  70. .bDescriptorSubtype = USB_CDC_TYPE_HEADER,
  71. .bcdCDC = 0x0110,
  72. },
  73. .call_mgmt = {
  74. .bFunctionLength = sizeof(struct usb_cdc_call_management_descriptor),
  75. .bDescriptorType = CS_INTERFACE,
  76. .bDescriptorSubtype = USB_CDC_TYPE_CALL_MANAGEMENT,
  77. .bmCapabilities = 0,
  78. .bDataInterface = 1,
  79. },
  80. .acm = {
  81. .bFunctionLength = sizeof(struct usb_cdc_acm_descriptor),
  82. .bDescriptorType = CS_INTERFACE,
  83. .bDescriptorSubtype = USB_CDC_TYPE_ACM,
  84. .bmCapabilities = 0,
  85. },
  86. .cdc_union = {
  87. .bFunctionLength = sizeof(struct usb_cdc_union_descriptor),
  88. .bDescriptorType = CS_INTERFACE,
  89. .bDescriptorSubtype = USB_CDC_TYPE_UNION,
  90. .bControlInterface = 0,
  91. .bSubordinateInterface0 = 1,
  92. }
  93. };
  94. /* USB通信接口描述符结构体变量comm_iface,属性packed,定义在libopencm3/include/libopencm3/usb/usbstd.h */
  95. static const struct usb_interface_descriptor comm_iface[] = {{
  96. .bLength = USB_DT_INTERFACE_SIZE, /* USB_DT_INTERFACE_SIZE:值9,接口描述符大小(libopencm3/include/libopencm3/usb/usbstd.h) */
  97. .bDescriptorType = USB_DT_INTERFACE, /* USB_DT_INTERFACE:值4,表示本结构体为USB接口描述符(libopencm3/include/libopencm3/usb/usbstd.h) */
  98. .bInterfaceNumber = 0, /* 接口数目1个,以0为基准开始计数 */
  99. .bAlternateSetting = 0, /* 交互设置值为0 */
  100. .bNumEndpoints = 1, /* 端点数目1个(comm_endp) */
  101. .bInterfaceClass = USB_CLASS_CDC, /* USB_CLASS_CDC:值0x02,接口群组(libopencm3/include/libopencm3/usb/cdc.h) */
  102. .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, /* USB_CDC_SUBCLASS_ACM:值0x02,接口次群组(libopencm3/include/libopencm3/usb/cdc.h) */
  103. .bInterfaceProtocol = USB_CDC_PROTOCOL_AT, /* USB_CDC_PROTOCOL_AT:值0x01,接口协议(libopencm3/include/libopencm3/usb/cdc.h) */
  104. .iInterface = 0, /* 接口的字符串描述符索引为0 */
  105. /* 以下内容不再属于USB接口描述符信息,为程序内部使用的变量 */
  106. .endpoint = comm_endp, /* 此接口对应的端点为comm_endp */
  107. .extra = &cdcacm_functional_descriptors, /* 通信接口额外信息域为cdcacm_functional_descriptors */
  108. .extralen = sizeof(cdcacm_functional_descriptors) /* 额外信息域长度 */
  109. }
  110. };
  111. /* USB数据接口描述符结构体变量data_iface,属性packed,定义在libopencm3/include/libopencm3/usb/usbstd.h */
  112. /* 这里仅进行了部分赋值,结构体规定的额外内容在UNIX环境中被默认为0 */
  113. static const struct usb_interface_descriptor data_iface[] = {{
  114. .bLength = USB_DT_INTERFACE_SIZE, /* USB_DT_INTERFACE_SIZE:值9,接口描述符大小(libopencm3/include/libopencm3/usb/usbstd.h) */
  115. .bDescriptorType = USB_DT_INTERFACE, /* USB_DT_INTERFACE:值4,表示本结构体为USB接口描述符(libopencm3/include/libopencm3/usb/usbstd.h) */
  116. .bInterfaceNumber = 1, /* 接口数目2个,以0为基准开始计数 */
  117. .bAlternateSetting = 0, /* 交互设置值为0 */
  118. .bNumEndpoints = 2, /* 端点数目2个(data_endp) */
  119. .bInterfaceClass = USB_CLASS_DATA, /* USB_CLASS_DATA:值0x0a,接口群组(libopencm3/include/libopencm3/usb/cdc.h) */
  120. .bInterfaceSubClass = 0, /* 接口次群组为0 */
  121. .bInterfaceProtocol = 0, /* 接口协议,0表示无特定协议 */
  122. .iInterface = 0, /* 接口的字符串描述符索引为0,无效 */
  123. /* 以下内容不再属于USB接口描述符信息,为程序内部使用的变量 */
  124. .endpoint = data_endp, /* 此接口对应的端点为data_endp */
  125. }
  126. };
  127. /* 变量ifaces对接口描述符进行了统计(共2个),用于关联到配置描述符中 */
  128. static const struct usb_interface ifaces[] = {{
  129. .num_altsetting = 1,
  130. .altsetting = comm_iface,
  131. }, {
  132. .num_altsetting = 1,
  133. .altsetting = data_iface,
  134. }
  135. };
  136. /* USB配置描述符结构体变量config,属性packed,定义在libopencm3/include/libopencm3/usb/usbstd.h */
  137. static const struct usb_config_descriptor config = {
  138. .bLength = USB_DT_CONFIGURATION_SIZE, /* USB_DT_CONFIGURATION_SIZE:值9,配置描述符大小(libopencm3/include/libopencm3/usb/usbstd.h) */
  139. .bDescriptorType = USB_DT_CONFIGURATION, /* USB_DT_CONFIGURATION:值2,表示本结构体为USB配置描述符(libopencm3/include/libopencm3/usb/usbstd.h) */
  140. .wTotalLength = 0, /* 描述符的总长度为0(是否应该为32字节?接口描述符9 配置描述符9 端点描述符7*2) */
  141. .bNumInterfaces = 2, /* 该配置支持的接口数目2个(comm_iface和data_iface) */
  142. .bConfigurationValue = 1, /* 配置值为1,作为set configuration请求的配置值 */
  143. .iConfiguration = 0, /* 配置字符串描述符索引为0,无效 */
  144. .bmAttributes = 0x80, /* 配置的属性为0x80 */
  145. .bMaxPower = 0xFA, /* 当USB设备操作时,它从总线上获得的最大电源(单位2mA,0xFA=250,500mA) */
  146. /* 以下内容不再属于USB配置描述符信息,为程序内部使用的变量 */
  147. .interface = ifaces, /* 此配置对应的接口 */
  148. };
  149. /* USB虚拟串口属性结构体变量line_coding,属性packed,定义在libopencm3/include/libopencm3/usb/cdc.h */
  150. static const struct usb_cdc_line_coding line_coding = {
  151. .dwDTERate = 115200, /* 波特率115200 */
  152. .bCharFormat = USB_CDC_1_STOP_BITS, /* USB_CDC_1_STOP_BITS:值0,1位停止位(libopencm3/include/libopencm3/usb/cdc.h) */
  153. .bParityType = USB_CDC_NO_PARITY, /* USB_CDC_NO_PARITY:值0,无校验(libopencm3/include/libopencm3/usb/cdc.h) */
  154. .bDataBits = 0x08 /* 数据8位 */
  155. };
  156. /* USB虚拟串口请求控制函数,被配置函数cdcacm_set_config函数使用 */
  157. static int cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len,
  158. void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req))
  159. {
  160. (void)complete;
  161. (void)buf;
  162. (void)usbd_dev;
  163. switch (req->bRequest) { /* 请求类型 */
  164. case USB_CDC_REQ_SET_CONTROL_LINE_STATE: { /* USB_CDC_REQ_SET_CONTROL_LINE_STATE:值0x22,请求设置命令行状态,定义在libopencm3/include/libopencm3/usb/cdc.h */
  165. return 1; /* 不操作,直接返回1 */
  166. }
  167. case USB_CDC_REQ_SET_LINE_CODING: /* USB_CDC_REQ_SET_LINE_CODING:值0x20,请求设置虚拟串口属性,定义在libopencm3/include/libopencm3/usb/cdc.h */
  168. if (*len < sizeof(struct usb_cdc_line_coding)) { /* 若配置的参数长度小于usb_cdc_line_coding的大小,返回0 */
  169. return 0;
  170. }
  171. return 1; /* 不操作,返回1 */
  172. case USB_CDC_REQ_GET_LINE_CODING: /* USB_CDC_REQ_GET_LINE_CODING:值0x21,请求读取虚拟串口属性,定义在cdcacm.c中 */
  173. *buf = (uint8_t *)&line_coding; /* buf指向虚拟串口属性变量line_coding(115200@8N1),定义在cdcacm.c中 */
  174. return 1;
  175. }
  176. return 0;
  177. }
  178. /* USB虚拟串口数据接收函数,被配置函数cdcacm_set_config函数使用 */
  179. static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
  180. {
  181. (void)ep;
  182. char buf[64];
  183. unsigned i;
  184. /* usbd_ep_read_packet:去读USB设备usbd_dev在端点地址0x01的数据到缓冲区buf中,并返回读取到数据的个数。定义在libopencm3/lib/usb/usb.c */
  185. unsigned len = usbd_ep_read_packet(usbd_dev, 0x01, buf, sizeof(buf));
  186. /* 将数据赋值给接收数据缓冲区rx_buf */
  187. /* buf_put:将数据写入到缓冲区的函数,定义在bl.c */
  188. for (i = 0; i < len; i ) {
  189. buf_put(buf[i]);
  190. }
  191. }
  192. /* USB虚拟串口配置函数,被usb_cinit中的库函数usbd_register_set_config_callback引用 */
  193. static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue)
  194. {
  195. (void)wValue;
  196. /* USB_ENDPOINT_ATTR_BULK:值0x2,批量传输(libopencm3/include/libopencm3/usb/usbstd.h) */
  197. /* USB_ENDPOINT_ATTR_INTERRUPT:值0x03,中断传输(libopencm3/include/libopencm3/usb/usbstd.h) */
  198. /* cdcacm_data_rx_cb:USB虚拟串口数据接收函数,将数据赋值给接收数据缓冲区rx_buf(bl.c),定义在cdcacm.c */
  199. /* usbd_ep_setup:设置USB端点地址、类型、FIFO存储空间和回调函数 */
  200. /* 第一行:地址为0x01的USB端点类型为批量传输模式(USB_ENDPOINT_ATTR_BULK),缓冲区长度64字节,回调函数为cdcacm_data_rx_cb,用于接收USB数据 */
  201. /* 第二行:地址为0x82的USB端点类型为批量传输模式(USB_ENDPOINT_ATTR_BULK),缓冲区长度64字节,无回调函数,用于发送USB数据 */
  202. /* 第三行:地址为0x83的USB端点类型为中断传输模式(USB_ENDPOINT_ATTR_INTERRUPT),缓冲区长度16字节,无回调函数,用于USB传输控制 */
  203. usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, cdcacm_data_rx_cb);
  204. usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_BULK, 64, NULL);
  205. usbd_ep_setup(usbd_dev, 0x83, USB_ENDPOINT_ATTR_INTERRUPT, 16, NULL);
  206. /* USB_REQ_TYPE_CLASS:值0x20(libopencm3/include/libopencm3/usb/usbstd.h) */
  207. /* USB_REQ_TYPE_INTERFACE:值0x01(libopencm3/include/libopencm3/usb/usbstd.h) */
  208. /* USB_REQ_TYPE_TYPE:值0x60(libopencm3/include/libopencm3/usb/usbstd.h) */
  209. /* USB_REQ_TYPE_RECIPIENT:值0x1F(libopencm3/include/libopencm3/usb/usbstd.h) */
  210. /* cdcacm_control_request:USB虚拟串口请求控制函数,功能为处理各种USB请求信息,定义在cdcacm.c */
  211. /* usbd_register_control_callback:USB寄存器控制请求回调函数,定义在libopencm3/lib/usb/usb_control.c */
  212. usbd_register_control_callback(usbd_dev, USB_REQ_TYPE_CLASS|USB_REQ_TYPE_INTERFACE, USB_REQ_TYPE_TYPE|USB_REQ_TYPE_RECIPIENT, cdcacm_control_request);
  213. }
  214. /* USB OTG FS中断处理函数 */
  215. void otg_fs_isr(void)
  216. {
  217. if (usbd_dev) {
  218. /* usbd_poll:USB设备的POLL操作;定义在libopencm3/lib/usb/usb.c */
  219. usbd_poll(usbd_dev);
  220. }
  221. }
  222. /* USB虚拟串口通信初始化函数 */
  223. void usb_cinit(void)
  224. {
  225. #if defined(STM32F4) /* 宏STM32F4值为1,定义在Makefile.f4中,下列代码有效 */
  226. /* 1. 启动USB接口时钟,对应原理图中GPIOA9(VBUS/3.1A),GPIOA11(OTG_FS_DM/3.1B),GPIOA12(OTG_FS_DP/3.1A) */
  227. /* RCC_AHB1ENR:寄存器(地址0x40023830),定义在libopencm3/include/libopencm3/stm32/f4/rcc.h */
  228. /* RCC_AHB2ENR:寄存器(地址0x40023834),定义在libopencm3/include/libopencm3/stm32/f4/rcc.h */
  229. /* RCC_AHB1ENR_IOPAEN:值1<<0(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  230. /* RCC_AHB2ENR_OTGFSEN:值1<<7(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  231. /* rcc_peripheral_enable_clock:使能特定的外部时钟,被定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  232. /* 第一行代码使能GPIOA的时钟,操作寄存器RCC_AHB1ENR的域GPIOAEN(bit0) */
  233. /* 第二行代码使能USB OTG FS时钟,操作寄存器RCC_AHB2ENR的域OTGFSEN(bit7) */
  234. rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_IOPAEN);
  235. rcc_peripheral_enable_clock(&RCC_AHB2ENR, RCC_AHB2ENR_OTGFSEN);
  236. #if defined(USB_FORCE_DISCONNECT) /* 宏USB_FORCE_DISCONNECT未定义,下列代码无效 */
  237. gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_OTYPE_OD, GPIO12);
  238. gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO12);
  239. gpio_clear(GPIOA, GPIO12);
  240. systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
  241. systick_set_reload(board_info.systick_mhz * 1000); /* 1ms tick, magic number */
  242. systick_interrupt_enable();
  243. systick_counter_enable();
  244. /* Spec is 2-2.5 uS */
  245. delay(1);
  246. systick_interrupt_disable();
  247. systick_counter_disable(); // Stop the timer
  248. #endif
  249. /* 2. 配置GPIOA11和GPIOA12分别USB数据通信功能 */
  250. /* GPIOA:地址为0x40020000的寄存器(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  251. /* GPIO_MODE_AF:值0x2(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  252. /* GPIO_PUPD_NONE:值0x0(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  253. /* GPIO11:值1<<11(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  254. /* GPIO12:值1<<12(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  255. /* GPIO_AF10:值0xa(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  256. /* gpio_mode_setup:设置引脚工作模式,定义在libopencm3/lib/stm32/common/gpio_common_f0234.c */
  257. /* gpio_set_af:设置引脚具体的AF功能,定义在libopencm3/lib/stm32/common/gpio_common_f0234.c */
  258. /* 第一行代码设置GPIOA11(OTG_FS_DM/3.1B)和GPIOA12(OTG_FS_DP/3.1A)为AF功能(MODER=GPIO_MODE_AF=10),push-pull模式(PUPDR=GPIO_PUPD_NONE=0x0) */
  259. /* 第二行代码设置GPIOA11和GPIOA12的具体AF功能为OTGFS/OTGHS(AFRH10=AFRH9=GPIO_AF10=0xa) */
  260. gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO11 | GPIO12);
  261. gpio_set_af(GPIOA, GPIO_AF10, GPIO11 | GPIO12);
  262. #if defined(BOARD_USB_VBUS_SENSE_DISABLED) /* 宏BOARD_USB_VBUS_SENSE_DISABLED未定义,下列代码无效 */
  263. OTG_FS_GCCFG |= OTG_GCCFG_NOVBUSSENS;
  264. #endif
  265. /* 3. USB设备的初始化配置 */
  266. /* usbd_dev:_usbd_device型结构体全局指针变量,包含一系列USB信息与操作函数(libopencm3/lib/usb/usb_private.h) */
  267. /* otgfs_usb_driver:_usbd_driver型结构体变量stm32f107_usb_driver,包含一系列USBFS驱动函数(libopencm3/lib/usb/usb_f107.c) */
  268. /* dev:USB设备描述符usb_device_descriptor,包含USB设备的基本信息(libopencm3/include/libopencm3/usb/usbstd.h),初始化在cdcacm.c */
  269. /* config:USB配置描述符usb_config_descriptor,包含USB设备的配置信息(libopencm3/include/libopencm3/usb/usbstd.h),初始化在cdcacm.c */
  270. /* usb_strings:USB设备字符串列表,定义并被初始化在cdcacm.c */
  271. /* NUM_USB_STRINGS:值3,usb_strings的数量,宏定义在cdcacm.c */
  272. /* usbd_control_buffer:控制操作请求缓冲区,128字节,定义在cdcacm.c */
  273. /* usbd_init:usb接口初始化函数,目的是填写结构体变量usbd_dev,定义在libopencm3/lib/usb/usb.c */
  274. usbd_dev = usbd_init(&otgfs_usb_driver, &dev, &config, usb_strings, NUM_USB_STRINGS, usbd_control_buffer, sizeof(usbd_control_buffer));
  275. #elif defined(STM32F1) /* 宏STM32F1未定义(IO协处理器不执行usb_cinit),下列代码无效 */
  276. rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPAEN);
  277. gpio_set(GPIOA, GPIO8);
  278. gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO8);
  279. usbd_dev = usbd_init(&st_usbfs_v1_usb_driver, &dev, &config, usb_strings, NUM_USB_STRINGS, usbd_control_buffer, sizeof(usbd_control_buffer));
  280. #endif
  281. /* 4. USB传输回调函数设置 */
  282. /* usbd_dev:_usbd_device型结构体全局指针变量,包含一系列USB信息与操作函数(libopencm3/lib/usb/usb_private.h) */
  283. /* cdcacm_set_config:USB虚拟串口配置函数,配置USB端点的操作函数和USB寄存器的请求操作函数,定义在cdcmca.c */
  284. /* usbd_register_set_config_callback:追加回调函数cdcacm_set_config到设备描述符usbd_dev中 */
  285. usbd_register_set_config_callback(usbd_dev, cdcacm_set_config);
  286. #if defined(STM32F4) /* 宏STM32F4值为1,定义在Makefile.f4中,下列代码有效 */
  287. /* 5. 若USB产品的ID号为0x00002000,启动USB连接功能 */
  288. /* OTG_FS_CID:寄存器,用于存储USB产品ID,软件可更改(libopencm3/include/libopencm3/stm32/otg_fs.h) */
  289. /* OTG_CID_HAS_VBDEN:值0x00002000,定义在cdcacm.c */
  290. /* OTG_FS_GCCFG:寄存器(libopencm3/include/libopencm3/stm32/otg_fs.h) */
  291. /* OTG_GCCFG_VBDEN:值1<<21,置该位认为VBUS一直有效,不再受外围电路影响(cdcacm.c) */
  292. /* OTG_GCCFG_PWRDWN:值1<<16,置该位开启收发功能(libopencm3/include/libopencm3/stm32/otg_common.h) */
  293. /* OTG_FS_DCTL:寄存器(libopencm3/include/libopencm3/stm32/otg_fs.h) */
  294. /* OTG_DCTL_SDIS:值1<<1,该位由软件控制USB核心开启软件连接功能(libopencm3/include/libopencm3/stm32/otg_common.h) */
  295. if (OTG_FS_CID == OTG_CID_HAS_VBDEN) {
  296. OTG_FS_GCCFG |= OTG_GCCFG_VBDEN | OTG_GCCFG_PWRDWN;
  297. /* 对于STM32F446/469,启动后需要进行软件连接 */
  298. OTG_FS_DCTL &= ~OTG_DCTL_SDIS;
  299. }
  300. /* 6. 使能USB OTG FS中断 */
  301. /* NVIC_OTG_FS_IRQ:值67,对应USB OTG FS的全局中断向量号,libopencm3/include/libopencm3/stm32/f4/nvic.h(编译时自动生成文件) */
  302. /* nvic_enable_irq:使能中断向量,定义在libopencm3/lib/cm3/nvic.c */
  303. nvic_enable_irq(NVIC_OTG_FS_IRQ);
  304. #endif
  305. }

USB串口读取数据后存放到缓冲区的函数,被cdcacm_data_rx_cb函数调用


bl.c

  1. static unsigned head, tail; /* 头、尾指针 */
  2. static uint8_t rx_buf[256]; /* 缓冲区长度 */
  3. void buf_put(uint8_t b)
  4. {
  5. unsigned next = (head 1) % sizeof(rx_buf);
  6. if (next != tail) {
  7. rx_buf[head] = b;
  8. head = next;
  9. }
  10. }

10.2 串口和USB虚拟串口反向初始化相关函数

串口和USB虚拟串口反向初始化包括cfini、uart_cfini和usb_cfini共3个函数。

10.2.1 cfini函数

cfini函数是串口和USB虚拟串口初始化的主要函数,使Bootloader主动放弃通信串口的控制权。cfini函数根据输入interface的值决定具体调用的初始化函数;uart_cfini用于反向初始化串口,usb_cfini用于反向初始化USB接口。


bl.c

  1. inline void cfini(void)
  2. {
  3. #if INTERFACE_USB /* 主控FMU:宏INTERFACE_USB定义为1,代码有效,hw_config.h */
  4. /* IO协处理器:宏INTERFACE_USB定义为0,代码无效,hw_config.h */
  5. usb_cfini();
  6. #endif
  7. #if INTERFACE_USART /* 宏INTERFACE_USART定义为1,hw_config.h */
  8. uart_cfini();
  9. #endif
  10. }

10.2.2 uart_cfini函数

uart_cfini函数的草组很简单,直接调用库函数usart_disable关闭串口,USART2串口的配置并不改变。


usart.c

  1. uint32_t usart; /* 全局变量,uart_cinit函数中被赋值为USART2 */
  2. void uart_cfini(void)
  3. {
  4. /* usart_disable:关闭串口,操作寄存器USART_CR1的域UE(bit13)(libopencm3/lib/stm32/common/usart_common_all.c) */
  5. usart_disable(usart);
  6. }

10.2.3 usb_cfini函数

USB虚拟串口反向初始化函数usb_cfini,并未改变USB的接口配置,主要操作如下:

  • 关闭USB OTG FS中断
  • 关闭USB软连接,并清空usbd_dev指针
  • 配置USB数据引脚GPIOA11和GPIOA12的工作模式
  • 关闭USB OTG FS时钟

  1. void usb_cfini(void)
  2. {
  3. #if defined(STM32F4) /* 宏STM32F4值为1,定义在Makefile.f4中,下列代码有效 */
  4. /* 1. 关闭USB OTG FS中断 */
  5. /* NVIC_OTG_FS_IRQ:值67,对应USB OTG FS的全局中断向量号,libopencm3/include/libopencm3/stm32/f4/nvic.h(编译时自动生成文件) */
  6. /* nvic_disable_irq:使能中断向量,定义在libopencm3/lib/cm3/nvic.c */
  7. nvic_disable_irq(NVIC_OTG_FS_IRQ);
  8. #endif
  9. /* 2.关闭USB软连接,并清空usbd_dev指针 */
  10. /* usbd_dev:usbd_device型全局变量指针,指向USB的配置信息和操作函数,被usbd_init库函数初始化,cdcacm.c */
  11. /* usbd_disconnect:断开USB软连接(不是所有的MCU都支持)。定义在libopencm3/lib/usb/usb.c */
  12. if (usbd_dev) {
  13. usbd_disconnect(usbd_dev, true);
  14. usbd_dev = NULL;
  15. }
  16. #if defined(STM32F4) /* 宏STM32F4值为1,定义在Makefile.f4中,下列代码有效 */
  17. /* 3. 配置USB数据引脚GPIOA11和GPIOA12为输入 */
  18. /* GPIOA:地址为0x40020000的寄存器(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  19. /* GPIO_MODE_INPUT:值0x0(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  20. /* GPIO_PUPD_NONE:值0x0(libopencm3/include/libopencm3/stm32/common/gpio_common_f234.h) */
  21. /* GPIO11:值1<<11(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  22. /* GPIO12:值1<<12(libopencm3/include/libopencm3/stm32/common/gpio_common_all.h) */
  23. /* gpio_mode_setup:设置引脚工作模式,定义在libopencm3/lib/stm32/common/gpio_common_f0234.c */
  24. /* 设置GPIOA11(OTG_FS_DM/3.1B)和GPIOA12(OTG_FS_DP/3.1A)为输入功能(MODER=GPIO_MODE_AF=00),浮空模式(PUPDR=GPIO_PUPD_NONE=0x0) */
  25. gpio_mode_setup(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO11 | GPIO12);
  26. /* 4. 关闭USB OTG FS时钟 */
  27. /* RCC_AHB2ENR:寄存器(地址0x40023834),定义在libopencm3/include/libopencm3/stm32/f4/rcc.h */
  28. /* RCC_AHB2ENR_OTGFSEN:值1<<7(libopencm3/include/libopencm3/stm32/f4/rcc.h) */
  29. /* rcc_peripheral_disable_clock:关闭特定的外部时钟,被定义在libopencm3/lib/stm32/common/rcc_common_all.c */
  30. /* 关闭USB OTG FS时钟,操作寄存器RCC_AHB2ENR的域OTGFSEN(bit7) */
  31. rcc_peripheral_disable_clock(&RCC_AHB2ENR, RCC_AHB2ENR_OTGFSEN);
  32. #elif defined(STM32F1) /* 宏STM32F1未定义(IO协处理器不执行usb_cfini),下列代码无效 */
  33. /* Reset the USB pins to being floating inputs */
  34. gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO8);
  35. gpio_clear(GPIOA, GPIO8);
  36. #endif
  37. }

10.3 读取串口和USB虚拟串口内容函数

读取串口和USB虚拟串口内容函数包括uart_cin和usb_cin两个,分别对应USART读取和USB模拟串口读取函数,这两个函数均被cin函数调用。

10.3.1 uart_cin函数

uart_cin函数用于读取USART串口的1个字。


usart.c

  1. uint32_t usart; /* 全局变量,uart_cinit函数中被赋值为USART2 */
  2. int uart_cin(void)
  3. {
  4. int c = -1; /* 默认返回值-1 */
  5. /* 若串口数据寄存器不为空,调用库函数读取接收到的数据 */
  6. /* USART_SR:USART状态寄存器,定义在libopencm3/include/libopencm3/stm32/common/usart_common_f124.h */
  7. /* USART_SR_RXNE:值1<<5,定义在libopencm3/include/libopencm3/stm32/common/usart_common_f124.h */
  8. /* usart:值USART2(USART2基地址),定义在usart.c */
  9. /* usart_recv:接收串口数据的库函数,定义在libopencm3/lib/stm32/common/usart_common_f124.c */
  10. if (USART_SR(usart) & USART_SR_RXNE) {
  11. c = usart_recv(usart);
  12. }
  13. return c;
  14. }

10.3.2 usb_cin函数

usb_cin函数用于读取USB模拟串口的1个字,该函数仅对主控FMU有效。


cdcacm.c

  1. int usb_cin(void)
  2. {
  3. /* 若usbd_dev为空指针,则USB设备未被初始化,返回错误 */
  4. /* usbd_dev:_usbd_device型结构体全局指针变量,包含一系列USB信息与操作函数,定义在cdcacm.c */
  5. if (usbd_dev == NULL) { return -1; }
  6. #if defined(STM32F1) /* 主控FMU未定义,代码无效 */
  7. usbd_poll(usbd_dev);
  8. #endif
  9. /* buf_get:从串口缓冲区内读取1个字的内容,定义在bl.c */
  10. return buf_get();
  11. }


bl.c

  1. static unsigned head, tail; /* 头、尾指针 */
  2. static uint8_t rx_buf[256]; /* 串口串口缓冲区 */
  3. int buf_get(void)
  4. {
  5. int ret = -1;
  6. if (tail != head) {
  7. ret = rx_buf[tail];
  8. tail = (tail 1) % sizeof(rx_buf);
  9. }
  10. return ret;
  11. }

10.4 串口和USB虚拟串口内容输出函数

串口和USB虚拟串口输出函数包括uart_cout和usb_cout两个,分别对应USART读取和USB模拟串口输出函数,这两个函数均被cout函数调用。

10.4.1 uart_cout函数


usart.c

  1. uint32_t usart; /* 全局变量,uart_cinit函数中被赋值为USART2 */
  2. void uart_cout(uint8_t *buf, unsigned len)
  3. {
  4. /* usart_send_blocking:USART串口发送库函数,仅当串口数据缓存为空时再发送下一个数据,定义在libopencm3/lib/stm32/common/usart_common_all.c */
  5. while (len--) {
  6. usart_send_blocking(usart, *buf );
  7. }
  8. }

10.4.2 usb_cout函数


cdcacm.c

  1. void usb_cout(uint8_t *buf, unsigned count)
  2. {
  3. if (usbd_dev) { /* 若USB设备有效 */
  4. /* 将需要写入的数据分成64字节1包,逐包写入到地址为0x82的端点中 */
  5. while (count) {
  6. unsigned len = (count > 64) ? 64 : count;
  7. unsigned sent;
  8. /* usbd_ep_write_packet:向USB模拟串口写入数据的库函数,返回写入的字节数,定义在libopencm3/lib/usb/usb.c */
  9. sent = usbd_ep_write_packet(usbd_dev, 0x82, buf, len);
  10. count -= sent;
  11. buf = sent;
  12. }
  13. }
  14. }

11 结论

不知不觉,就写了这么多,我最怕写结尾了,因为不知道写什么好。写博客的时候发现,github上bootloader的原始代码也更新了。幸好只是改变了Makefile的结构,对软件结构影响不大,因此上述内容依然有参考价值。博客写完之后没有进行详细地推敲,希望各位看官多多批评指正,大家共同进步。

希望详细讨论的看官,可以发邮件至我的邮箱:

zhaowei19841211@gmail.com

只要我知道的,有求必应。

开源万岁!!!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多