PX4 SOC 与 BSP 层平台抽象

一、三层结构概览

Board 层、BSP 层、SOC 层构成 PX4 硬件抽象的三级结构,职责各异:

1
2
3
4
5
6
7
8
9
10
flowchart TD
Board["Board 层<br/>boards/px4/fmu-v5/<br/>提供 board_config.h 宏定义<br/>不直接操作寄存器"]
BSP["BSP 层<br/>platforms/nuttx/src/px4/stm32_common/<br/>实现 GPIO/ADC/TIM/SPI/UART 等外设<br/>暴露 px4_arch_* hrt_* io_timer_* 接口"]
SOC["SOC 层<br/>platforms/nuttx/src/px4/stm32f7/<br/>聚合 stm32_common 通用能力<br/>实现 F7 特定功能(DMA Cache IDLE 中断)"]
NuttX["NuttX STM32 arch/HAL<br/>stm32_* 寄存器级实现"]

Board --> BSP
Board --> SOC
BSP --> NuttX
SOC --> BSP

相关前置知识:Board 层硬件配置


二、构建关系

platforms/nuttx/src/px4/stm32f7/CMakeLists.txt 负责将 BSP 与 SOC 组件聚合到同一编译目标:

  • BSP 通用模块(stm32_common):ADC、HRT、SPI、io_timer 等,被所有 STM32 系列复用
  • SOC 专属(stm32f7)
    • px4io_serial:F7 特定的 DMA + DCache + IDLE 中断处理
    • watchdog:F7 具体的硬件看门狗实现

Board 层在更高层的 Board CMake 中被加入同一构建图谱,最终与 BSP/SOC 一起链接成单一的 .elf 固件镜像。


三、运行时调用链路

3.1 stm32_boardinitialize()

NuttX 启动序列最早调用的板级入口,在 init.c 中实现:

1
2
3
4
5
6
7
8
stm32_boardinitialize()
-> board_on_reset():危险引脚/PWM 切到安全态
-> board_autoled_initialize():不依赖 NuttX 系统的 LED 初始化
-> px4_gpio_init(PX4_GPIO_INIT_LIST)
Board 层提供 GPIO_xxx 宏(Pinset 位域)
BSP 层 micro_hal.h 将 px4_arch_configgpio() 映射到 stm32_configgpio()
NuttX STM32 arch 层做实际寄存器写入
-> USB 相关初始化

3.2 board_app_initialize()

应用层初始化入口,PX4 平台起来前的最后板级准备:

1
2
3
4
5
6
7
board_app_initialize()
-> 上电策略:调用 board_config.h 中的 True-logic 宏
-> px4_platform_init():明确要求 HRT 先跑起来
-> board_determine_hw_info():读取 ADC 电阻梯,生成 Ver/Rev/type_name
-> stm32_spiinitialize():在确定 HW 版本后配置 SPI
-> 裁剪不存在的 CAN2/CAN3:把 RX 引脚从外设复用切换成下拉输入
-> board_dma_alloc_init()、串口 DMA Polling、LED 驱动、SDIO 等

四、BSP 层核心文件

4.1 micro_hal.h

BSP 层将 Board 层定义的宏落地到 NuttX STM32 HAL 的关键桥接文件,统一将 px4_arch_* 映射到 NuttX STM32 的 stm32_* 实现:

1
2
3
4
px4_arch_configgpio(pinset)  ->  stm32_configgpio(pinset)
px4_arch_gpioread(pinset) -> stm32_gpioread(pinset)
px4_arch_gpiowrite(pinset, value) -> stm32_gpiowrite(pinset, value)
px4_arch_gpiosetevent(...) -> stm32_gpiosetevent(...)

Board 层产出 Pinset 位域(端口/引脚/模式/上下拉/AF/默认电平),BSP 层把 Pinset 写进 STM32 GPIO 寄存器。Board 层不允许直接操作寄存器。

4.2 board_hw_rev_ver.c

硬件版本识别:将 ADC 电阻梯码值转为 V5xxxx 类型信息。

Board 层在 board_config.h 定义版本识别的引脚宏与 ADC 通道,BSP 层 determine_hw_info()read_id_dn() 做两次测量(先测 revision,再测 version),将 ADC 码值 dn 转成 ordinal(离散编号),最终生成 hw_info 字符串。

4.3 board_reset.cpp

复位到 Bootloader 或应用程序,核心机制:

  • 写 RTC 备份寄存器(如 STM32_RTC_BK0R)存放模式魔数
  • 魔数类型:boot to BL、boot to app、CAN BL 等
  • 调用 up_systemreset() 触发复位
  • 备份域掉电保持,适合跨复位传递启动模式意图

4.4 hrt.c

HRT 高精度时钟与 PPM 解码绑定在同一硬件定时器上。直接占用一个 TIM,不走 NuttX 通用定时器驱动:

  • 需要稳定 1 MHz free-running 计数作微秒级时间基准
  • 需要比较中断,安排未来某时刻触发回调
  • 需要最小抖动与最小开销

hrt_init() 核心流程:

1
2
3
4
5
6
7
8
9
// 打开定时器时钟 RCC
// 配置 TIM 为 1MHz free-run
PSC = (HRT_TIMER_CLOCK / 1000000) - 1;
ARR = 0xffff; // 自由运行
CR1 = CEN; // 开始计数

// 使能中断向量
// 同时打开 DIER_HRT | DIER_PPM
// 配置 CCMR/CCER 与 PPM 相关通道寄存器

同一颗 TIM 同时承担:

  • HRT:系统微秒时基 + callout 调度(比较中断)
  • PPM:输入捕获/边沿时间戳 → PPM 状态机解码

4.5 adc.cpp

ADC 采样抽象,单次转换、寄存器级操作:

1
2
3
4
5
6
// 初始化接口
px4_arch_adc_init(base_address); // 对 ADC 实例做一次性初始化、校准、上电

// 采样接口
px4_arch_adc_sample(base_address, channel);
// 配置 SQR3=channel -> 启动 SWSTART -> 等待 EOC -> 读 DR 返回原始码值

Board 层定义引脚与通道映射,board_hw_rev_ver.cboard_adc 驱动只关心通道编号,不关心寄存器细节。

4.6 io_timer.c

PWM/捕获/Oneshot/DShot 的统一定时器框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
初始化流程:
io_timer_init_timer(timer, mode)
-> 开 RCC 时钟
-> 清零 CR1/CR2/SMCR/DIER/CCER/CCMR 等寄存器到可控初态
-> 安装 IRQ handler
-> 配置定时器频率与分频

通道初始化:
io_timer_channel_init(channel, mode, handler, context)
-> 从 timer_io_channels[](Board 层 timer_config.cpp 生成的映射表)获取:
- 该 channel 属于哪个 timer
- 该 channel 是 timer 的 CH1/2/3/4
- 对应的 GPIO pinset(AF 复用)
-> 按 mode 设置 CCMR(输入/输出比较模式)、CCER(极性、使能)、CCR(占空比/比较值)
-> 配置 GPIO 为 AF 推挽或 AF 开漏

Board 层 timer_config.cpp 决定”哪些 TIM 实例可用、每个通道映射到哪个引脚”,BSP io_timer.c 只负责把”映射表 + 模式”变成寄存器配置。同一套框架覆盖 PWM/捕获/OneShot/DShot,差异只在 mode 与寄存器位。


五、SOC 层:stm32f7 特定实现

stm32f7/px4io_serial/px4io_serial.cpp 是 SOC 层的典型代表。

ArchPX4IOSerial::init()

通过 PX4IO_SERIAL_* 宏(来自 Board 层 board_config.h)拿到 UART base/vector/DMAMAP/RCC 等硬件细节:

1
2
3
-> 申请 TX/RX DMA 通道:stm32_dmachannel(PX4IO_SERIAL_TX_DMAMAP)
-> 配置 UART:启用 RE/TE/UE,启用 IDLEIE(空闲线中断)与错误中断
-> 设置 DMA 缓冲区对齐(F7 DCache 行对齐,避免 cache 污染)

ArchPX4IOSerial::_bus_exchange()

每次事务流程:

1
2
3
4
准备 RX DMA -> TX DMA 发包 -> 等待 RX DMA 完成或超时
-> CRC 校验 -> 失败则重试或报错
-> 超时则 _abort_dma() 清理 DMA 状态
-> 处理 IDLE 中断触发的"帧结束"语义

F7 有 DCache,DMA 必须处理 cache line 对齐与 invalidate/clean。这也是 SOC 层独立于 BSP 层的原因:不同 STM32 系列的 UART/DMA/中断细节差异显著,通用化会引入大量条件编译。

驱动层(src/drivers/px4io/)通过 PX4IO_serial_interface() 工厂函数拿到 ArchPX4IOSerial 实例,px4io 驱动本身不直接写寄存器,实现了驱动层与 SOC 层的解耦。


六、Board/BSP/SOC 层宏到行为的对应关系

GPIO 链路

1
2
3
4
Board -> 定义 GPIO_*、PX4_GPIO_INIT_LIST
BSP -> micro_hal.h: px4_arch_configgpio() -> stm32_configgpio()
bringup -> init.c 调用 px4_gpio_init(PX4_GPIO_INIT_LIST)
最终效果: 上电早期每个焊盘进入确定状态

ADC/硬件版本链路

1
2
3
4
Board -> 定义 ADC_HW_VER_SENSE_CHANNEL 等
BSP -> board_hw_rev_ver.c 调用 read_id_dn() 读码值转 ordinal
BSP -> adc.cpp 完成寄存器配置与 EOC 等待,返回 raw dn
最终效果: 固件启动时得到 hw_version/hw_revision/hw_type_name

HRT 链路

1
2
3
4
Board -> 选择 HRT_TIMER=8、HRT_TIMER_CHANNEL=3、HRT_PPM_CHANNEL=1
BSP -> hrt.c 用这些宏选择 TIM base/clock/IRQ vector
设 prescaler 让 CNT 以 1MHz free-run
最终效果: hrt_absolute_time() 提供统一微秒时间戳;PPM 输入捕获与系统时基同域

PX4IO 串口链路

1
2
3
4
Board -> 给出 /dev/ttyS6、UART base/vector、DMA map、RCC enable、TX/RX GPIO
SOC -> ArchPX4IOSerial::init() 用这些宏配置 UART+DMA+IRQ
Driver-> 通过 PX4IO_serial_interface() 拿到实例,执行 _bus_exchange()
最终效果: PX4IO 通信的"协议/事务"在驱动层稳定,"寄存器/DMA/Cache"在 F7 SOC 层实现

上一篇:Board 层硬件配置
下一篇:固件编译与烧写流程