PX4 固件编译与烧写流程

一、编译链总览

make px4_fmu-v5_default 启动后经过七个阶段,最终将固件写入 MCU Flash:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
选择 default.px4board
|
v
Kconfig 依赖求解
|-- 输出 .config
`-- 生成 autoconf.h
|
v
CMake 配置阶段
`-- 生成 build.ninja(或 Makefile)
|
v
Ninja 编译
|-- *.c/*.cpp -> *.o
`-- *.o + *.a -> firmware.elf(linker script 决定地址布局)
|
v
objcopy / 封装
|-- firmware.elf -> firmware.bin / firmware.hex
`-- -> firmware.px4(含元信息)
|
v
Bootloader 接收固件
|-- 擦除应用区 flash
|-- 写入固件
|-- 校验
`-- 重启跳转运行

相关前置知识:SOC 与 BSP 层平台抽象


二、各阶段详解

2.1 目标解析

顶层 Makefile 包装器解析 px4_fmu-v5_default,得到:

  • board = px4/fmu-v5
  • label = default
  • generator = Ninja(优先选择)

随后管理 CMake 配置缓存,调用 CMake/Ninja 执行真正的编译。

2.2 Kconfig 阶段

Kconfig 将 default.px4board 变成 .configautoconf.h

Kconfig 两类输入:

  • 规则输入:大量 Kconfig 文件,定义可选项、依赖关系与默认值
  • 默认选择集defconfig,给出初始配置快照

核心计算规则:

  • depends on:不满足则强制关闭
  • select:联动打开依赖项
  • choice:互斥选择,保留一个

注意:PX4 的 Kconfig 与 NuttX 的 Kconfig 是两条独立链路,分别控制 PX4 模块与 NuttX 内核的编译选项。

输出:

  • .configCONFIG_* 开关事实表
  • autoconf.h:将事实表变成 C 宏 #define CONFIG_XXX 1

CMake 根据 CONFIG_* 决定编译哪些目录,代码按 #ifdef CONFIG_* 裁剪。

2.3 CMake 配置阶段

输入:.config + 工具链 + 平台/板目录

输出:build.ninja + CMakeCache.txt

2.4 编译阶段

Ninja 调用 arm-none-eabi-gcc/g++ 将每个 .c/.cpp 编译为 .o,关键编译参数:

1
-mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard

2.5 链接阶段

arm-none-eabi-ld 使用链接脚本(来自 NuttX/板级配置的 nuttx-config 与 Board ldscript)将所有 .o 与静态库 .a 组合成 px4_fmu-v5_default.elf(带符号与段信息)。

2.6 镜像封装

1
2
objcopy    firmware.elf -> firmware.bin / firmware.hex
PX4 封装工具 -> firmware.px4(含版本、校验等元信息)

2.7 Bootloader 烧写

通过以下方式将镜像写入 MCU 内部 Flash 的固定地址区间:

  • USB DFU
  • MAVLink Bootloader(QGroundControl 固件更新)
  • JLINK 调试器

流程:擦除应用区 Flash → 写入固件 → CRC 校验 → 重启跳转执行。


三、各阶段输入输出汇总

阶段 执行者 输入 输出 被谁使用
目标解析 PX4 顶层 Makefile px4_fmu-v5_default board、label、generator 后续 Kconfig/CMake
Kconfig 求解 kconfig 工具 Kconfig 规则 + default.px4board .config + autoconf.h CMake 与编译器
CMake 配置 cmake .config + toolchain + 平台/板目录 build.ninja + CMakeCache.txt Ninja
编译/归档 ninja → arm-none-eabi-gcc/g++ 源码 + 头文件 + autoconf.h .olib*.a 链接器
链接 arm-none-eabi-ld .o/.a + 链接脚本 firmware.elf objcopy/封装/调试
提取/封装 objcopy + PX4 封装工具 firmware.elf .bin/.hex/.px4 bootloader/QGC
擦写/启动 bootloader + QGC .px4 或等价数据流 Flash 应用区固件 MCU 复位后执行

四、上电执行路径

固件写入 Flash 后,上电复位时 MCU 的执行路径:

1
2
3
4
5
6
7
8
9
10
11
12
MCU 从 Flash 取向量表
-> 取 Reset_Handler 地址
-> 设置 MSP(主栈指针)
-> 启动代码初始化 C 运行时:
.data 从 Flash 拷贝到 RAM
.bss 清零
调用 main()(或 NuttX 的入口)

进入 NuttX 内核启动序列
-> 调用 Board 的两个关键入口(init.c):
stm32_boardinitialize() // 最早的板级硬件初始化
board_app_initialize() // 应用层初始化入口

stm32_boardinitialize()

将 Board 层的 GPIO 初始化清单写进寄存器:

1
2
3
4
5
6
Board 层提供 PX4_GPIO_INIT_LIST(GPIO_xxx 宏/Pinset 位域)
BSP 层 micro_hal.h: px4_arch_configgpio(pinset) -> stm32_configgpio(pinset)
NuttX STM32 arch 层寄存器写入:
-> 开 GPIO 端口时钟(写 RCC)
-> 写 GPIO->MODER/OTYPER/OSPEEDR/PUPDR/AFR
-> 写 GPIO->BSRR 设置默认电平

board_app_initialize()

点亮 HRT/ADC/HW_INFO/SPI/IO_TIMER 功能,为 PX4 主系统启动做最后准备:

1
2
3
4
5
-> HRT 初始化(要求先跑起来,后续 ADC/SPI 初始化依赖时间戳)
-> ADC 初始化,读取硬件版本
-> SPI 初始化(依赖 HW 版本结果)
-> IO_TIMER 初始化
-> 进入 PX4 主系统(modules 逐一 start)

上一篇:SOC 与 BSP 层平台抽象
下一篇:交叉编译顺序详解