pixhawk PX4FMU和PX4IO最底层启动过程分析
1.1 主处理器和协处理器的固件烧写和运行流程
首先,大体了解PX4IO 与PX4FMU各自的任务。
PX4IO(STM32F100)为PIXHAWK 中专用于处理输入输出的部分,输入为支持的各类遥控器(PPM,SPKT/DSM,SBUS), 输出为电调的PWM 驱动信号, 它与PX4FMU(STM32F427)通过串口进行通信。
PX4FMU :各种传感器数据读取、姿态解算、PWM控制量的计算、与PX4IO通信。负责飞控最主要的工作。
其中这两处理器的固件烧写运行流程如下(也可以搜索相应的文章):
主处理器和协处理器,固件下载到固件运行的流程图。值得说明的一点是分别给两个处理器下载blootloader以后,主处理器等待下载飞控固件,用地面站通过USB向飞控固件下载完以后,主处理器的飞控固件启动,这时候通过串口给协处理器下载固件(固件只是下载一次)。这个工作完成之后,主处理器和协处理器同时开始工作。
详细可以看到如下代码
#if defined(CONFIG_ARCH_BOARD_PX4FMU_V1)
fn[0] = "/etc/extras/px4io-v1.bin";
fn[1] ="/fs/microsd/px4io1.bin";
fn[2] ="/fs/microsd/px4io.bin";
fn[3] =nullptr;
#elif defined(CONFIG_ARCH_BOARD_PX4FMU_V2)
fn[0] = "/etc/extras/px4io-v2.bin";
fn[1] ="/fs/microsd/px4io2.bin";
fn[2] ="/fs/microsd/px4io.bin";
fn[3] =nullptr;
#else
#error "unknown board"
#endif
}
up = new PX4IO_Uploader;
int ret = up->upload(//向协处理器下载固件
delete up;
这段代码在px4io.cpp中,随着px4io.cpp进程启动而启动。功能就是由主处理器向协处理器烧写代码。
其中协处理器的固件源代码在:
E:\Firmware-master\Firmware\src\modules\px4iofirmware 这里面主要是是一些,IO口的操作和安全开关的操作。他会在编译飞控固件的时候,一起被编译成为px4io-v2.bin,放在NUTTX文件系统中,在主处理器运行的时候把这个固件烧写进入协处理器。具体的烧写到单片机的FLASH那个位置可以在px4io.cpp相关代码找到。
在软件层面的详细流程图如下:
1.2 Bootloader和NUTTX启动详解
在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。Bootloader是嵌入式系统在加电后执行的第一段代码,在它完成CPU和相关硬件的初始化之后,再将操作系统映像或固化的嵌入式应用程序装在到内存中然后跳转到操作系统所在的空间,启动操作系统运行.
进入px4/Bootloader可以看到main_f1、main_f4,分别对应着PX4IO、PX4FMU的Bootloader.
值得注意的是单片机的二进制代码在单片机的启动位置
我们知道Pixhawk硬件使用STM32的芯片,
Cortex M3的内核有三种启动方式,其分别是:
A.通过boot引脚设置可以将中断向量表定位于SRAM区,即起始地址为0x2000000,同时复位后PC指针位于0x2000000处; B.通过boot引脚设置可以将中断向量表定位于FLASH区,即起始地址为0x8000000,同时复位后PC指针位于0x8000000处; C.通过boot引脚设置可以将中断向量表定位于内置Bootloader区,M3单片机复位后,从0x00000000取栈指针(SP),从0x00000004取复位向量(PC),有了栈指针和复位向量后,单片机就按照正常流程运行了,单片机启动默认先运行BootLoader,所以默认的中断向量表位置是BootLoader的中断向量表。我们知道Bootloader是上电执行的第一个代码,Pixhawk的启动是通过B的方式来启动的,所以我们在下Bootloader的时候也是向FLASH这0x8000000地址烧写的固件(具体可以看固件烧写这篇文章),Bootloader烧写成功以后每次上电就会启动,启动的起始地址就是FLASH的0x8000000。Bootloader启动执行到最后,会用汇编语句,自动跳转到FLASH中固件下载的位置,开始执行固件代码。NUTTX启动(也就是上面的固件代码,汇编语句就是跳转到这个位置来执行stm32_start.c):
此部分摘自于pixhawk自学笔记之px4程序启动顺序
代码位置:Firmware/build_px4fmu-v2_default/px4fmu-v2/Nuttx/nuttx/arch/arm/src/stm32/stm32_start.c
__start-- #处理器执行的第一条指令(px4使用的是stm32,入口在stm32_start.c中)
stm32_clockconfig() #初始化时钟
rcc_reset() #复位rcc
stm32_stdclockconfig() #初始化标准时钟
rcc_enableperipherals()#使能外设时钟
------------------------------------------------------------------
stm32_fpuconfig() #配置fpu
stm32_lowsetup() #基本初始化串口,之后可以使用up_lowputc()
stm32_gpioinit() #初始化gpio,只是调用stm32_gpioremap()设置重映射
up_earlyserialinit() #初始化串口,之后可以使用up_putc()
stm32_boardinitialize()
stm32_spiinitialize() #初始化spi,只是调用stm32_configgpio()设置gpio
stm32_usbinitialize() #初始化usb,只是调用stm32_configgpio()设置gpio
up_ledinit(); #初始化led,只是调用stm32_configgpio()设置gpio
----------------------------------------------------------------------------------------------
在stm32_start.c文件中我们会看到这么一句话:
/* Then start NuttX */
showprogress('\r');
showprogress('\n');
os_start();系统开始启动>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
一下是 os_start()内容:
dq_init() #初始化各种状态的任务列表(置为null)
g_pidhash[i]= #初始化唯一可以确定的元素--进程ID
g_pidhash[PIDHASH(0)]= #分配空闲任务的进程ID为0
g_idletcb= #初始化空闲任务的任务控制块
sem_initialize()-- #初始化信号量
dq_init() #将信号量队列置为null
sem_initholders() #初始化持有者结构以支持优先级继承
up_allocate_heap() #分配用户模式的堆(设置堆的起点和大小)
kumm_initialize() #初始化用户模式的堆
up_allocate_kheap() #分配内核模式的堆
kmm_initialize() #初始化内核模式的堆
task_initialize() #初始化任务数据结构
irq_initialize() #将所有中断向量都指向同一个异常中断处理程序
wd_initialize() #初始化看门狗数据结构
clock_initialize() #初始化rtc
timer_initialize() #配置POSIX定时器
sig_initialize() #初始化信号
mq_initialize() #初始化命名消息队列
pthread_initialize() #初始化线程特定的数据,空函数
fs_initialize()--- #初始化文件系统
sem_init() #初始化节点信号量为1
files_initialize() #初始化文件数组,空函数
net_initialize()-- #初始化网络
uip_initialize() #初始化uIP层
net_initroute() #初始化路由表
netdev_seminit() #初始化网络设备信号量
arptimer_init() #初始化ARP定时器
up_initialize()--- #处理器特定的初始化
up_calibratedelay() #校准定时器
up_addregion() #增加额外的内存段
up_irqinitialize() #设置中断优先级,关联硬件异常处理函数
up_pminitialize() #初始化电源管理
up_dmainitialize() #初始化DMA
up_timerinit() #初始化定时器中断
devnull_register() #注册/dev/null
devzero_register() #注册/dev/zero
up_serialinit() #注册串口控制台/dev/console和串口/dev/ttyS0
up_rnginitialize() #初始化并注册随机数生成器
up_netinitialize() #初始化网络,是arch/arm/src/chip/stm32_eth.c中的
up_usbinitialize() #初始化usb驱动
board_led_on() #打开中断使能led,但很快会被其它地方的led操作改变状态
lib_initialize() #初始化c库,空函数
group_allocate() #分配空闲组
group_setupidlefiles() #在空闲任务上创建stdout、stderr、stdin
group_initialize() #完全初始化空闲组
os_bringup()------ #创建初始任务
KEKERNEL_THREAD() #启动内核工作者线程
board_initialize() #最后一刻的板级初始化
TASK_CREATE() #启动默认应用程序
forup_idle() #空闲任务循环
for(;;) #不应该到达这里
__start 负责STM32 芯片的底层初始化,包括时钟,FPU,GPIO 等单元的初始化
os_start 负责OS 的底层初始化,包括各种队列和进程结构的初始化
os_bringup 负责OS 基本进程的启动以及用户进程的启动,用户启动入口由(CONFIG_USER_ENTRYPOINT)宏进行指定是执行nsh_main还是user_start。
启动脚本分析