2. ArduPilot系统架构概览

理解 ArduPilot 的外设驱动与设备接入,不能只盯着某一个驱动文件,而要先看清它在整个飞控软件中的位置。ArduPilot 采用分层架构:上层是飞行控制栈(飞行模式、姿态控制、导航、任务逻辑),中间是硬件抽象层(AP_HAL),下层是具体硬件平台(ChibiOS、Linux、ESP32 等)以及挂载其上的传感器驱动和外设总线。外设接入的本质,就是让一个物理设备经过总线、驱动、抽象层,最终以统一的数据接口暴露给飞行控制栈。

核心思想:飞行控制栈只关心“我要姿态、位置、高度、遥控输入”,不关心这些数据来自 I2C 还是 SPI、来自哪家厂商的芯片。硬件抽象层与驱动层负责把千差万别的硬件统一成标准接口。

一、五层结构:从飞行逻辑到物理引脚

ArduPilot 的整体关系可以概括为五个层次,自顶向下依次是:

  • 飞行控制栈(Vehicle/Flight Stack):包含 ArduCopter、ArduPlane、ArduRover 等机型代码,负责姿态控制、位置控制、飞行模式切换、任务执行。它通过 scheduler 周期调用,读取传感器数据并输出电机/舵机控制量。
  • 传感器与估计层(Sensors / EKF):AP_InertialSensor、AP_Baro、AP_Compass、AP_GPS、AP_RangeFinder 等库,把原始数据融合成姿态、位置、速度估计,供控制栈使用。
  • 硬件抽象层(AP_HAL):定义 I2C、SPI、UART、GPIO、RCInput、RCOutput、Storage、Scheduler 等统一接口,屏蔽底层 RTOS 与芯片差异。
  • 平台实现层(HAL 实现):如 AP_HAL_ChibiOS、AP_HAL_Linux、AP_HAL_ESP32,把 AP_HAL 接口映射到具体外设控制器、DMA、中断和引脚。
  • 物理设备与总线:IMU、磁罗盘、气压计、GPS、光流、测距仪、电调、接收机等,通过 I2C、SPI、UART、CAN、PWM 等总线连接。

这五层不是简单的“上下级”,而是“接口契约 + 实现替换”的关系。飞行控制栈调用 AP_HAL 接口,AP_HAL 调用平台实现,平台实现操作总线,总线连接设备。任何一层替换,只要接口不变,上层代码无需修改。

ArduPilot 分层架构与外设接入路径 飞行控制栈(ArduCopter / ArduPlane / ArduRover) 姿态控制 · 位置控制 · 飞行模式 · 任务调度 · 电机/舵机输出 传感器与估计层(AP_InertialSensor / AP_Baro / AP_Compass / AP_GPS / EKF) 原始数据校准 · 融合估计 · 姿态/位置/速度输出 硬件抽象层 AP_HAL(统一接口契约) I2C · SPI · UART · GPIO · RCInput · RCOutput · Storage · Scheduler 平台实现层(AP_HAL_ChibiOS / AP_HAL_Linux / AP_HAL_ESP32) 外设控制器 · DMA · 中断 · 引脚映射 · RTOS 任务 物理设备与总线(IMU · 磁罗盘 · 气压计 · GPS · 光流 · 测距 · 电调 · 接收机) I2C · SPI · UART · CAN · PWM/DShot · S.Bus · CRSF 调用 调用 调用 调用 数据自下而上流动:设备 → 总线 → 平台实现 → AP_HAL → 传感器库 → 控制栈 控制自下而上输出:控制栈 → AP_HAL → 平台实现 → 总线 → 电调/舵机

二、外设接入的完整数据流

以最常见的 IMU 为例,一次数据采集要经过如下路径:

  1. IMU 芯片通过 SPI 或 I2C 总线连接飞控主控;
  2. 平台实现层(如 AP_HAL_ChibiOS)配置 SPI 时钟、DMA 通道和中断;
  3. AP_InertialSensor 驱动通过 AP_HAL::SPIDevice 发起寄存器读取;
  4. 原始加速度、角速度数据经过校准、滤波、温度补偿;
  5. 数据进入 EKF 融合,输出姿态估计;
  6. 飞行控制栈读取姿态估计,计算控制量并输出到电机。

这条链路中,任何一环出问题都会表现为“传感器不健康”或“姿态漂移”。排障时要沿着链路逐段确认:设备是否被探测到、总线是否通信正常、驱动是否注册成功、数据是否进入 EKF。

三、关键接口与参数对照

下表列出外设接入中最常打交道的接口与对应参数,便于快速定位配置位置:

层次 典型接口/对象 关键参数示例 作用
飞行控制栈 AP_Vehicle、模式切换 FLTMODE_CH、ARMING_CHECK 决定何时解锁、使用哪些传感器
传感器库 AP_InertialSensor INS_USE、INS_ACCEL_FILTER 选择 IMU 实例、滤波与校准
硬件抽象层 AP_HAL::I2CDevice HAL_I2C_DEVICE 统一 I2C 读写接口
平台实现 SPIDevice、UARTDriver SERIALx_PROTOCOL、SERIALx_BAUD 串口协议与波特率映射
物理总线 I2C/SPI/UART/CAN CAN_P1_DRIVER、BRD_CAN_ENABLE 总线使能与设备发现

四、典型场景:接入一个 I2C 测距仪

假设要接入一个 I2C 接口的激光测距仪,接入流程如下:

1. 确认总线:I2C 地址 0x29,挂载在 I2C1
2. 在 hwdef.dat 中声明设备:
   I2C_ORDER I2C1
   define HAL_RANGEFINDER_ENABLE 1
3. 驱动注册:AP_RangeFinder 探测到设备并创建实例
4. 参数配置:
   RNGFND1_TYPE = 20      # 对应驱动类型
   RNGFND1_ADDR = 0x29
   RNGFND1_MAX_CM = 4000
   RNGFND1_MIN_CM = 20
5. 验证:地面站查看 rangefinder 距离是否稳定

这个过程中,hwdef.dat 属于平台实现层,RNGFND1_* 属于传感器库参数,而实际读写发生在 AP_HAL 的 I2C 接口上。理解这个映射关系,就能明白为什么有些配置要改硬件定义、有些只需改参数。

调试外设时,优先确认三件事:设备是否出现在总线扫描结果中、驱动是否被编译进固件、参数是否指向正确的实例编号。三者缺一,设备都不会出现在地面站列表中。

五、常见误区与复盘

  • 误区一:把驱动当成独立程序。ArduPilot 的驱动是编译进固件的库,通过探测和注册机制接入,不是运行时加载的插件。
  • 误区二:忽略平台实现层。同一个驱动在不同 HAL 平台上可能行为不同,因为底层 SPI/I2C 时序、DMA 和中断实现有差异。
  • 误区三:只改参数不改硬件定义。如果总线未在 hwdef 中使能,参数再正确也无法通信。
  • 误区四:不区分“探测失败”和“数据异常”。探测失败通常是总线或地址问题;数据异常通常是校准、滤波或供电问题。

复盘时建议按“总线 → 平台实现 → AP_HAL → 驱动 → 传感器库 → 控制栈”的顺序逐层验证,而不是一上来就改控制参数。分层架构的价值就在于:每一层都有明确的输入输出,问题可以被隔离在某一层内定位。

外设接入不是“接上线就能用”。从物理连接到数据可用,中间要经过总线使能、驱动探测、参数配置、校准与健康检查多个环节。任何一环缺失,都会表现为设备不可见或数据不可信。

掌握这套分层关系后,后续学习具体驱动时就能快速定位代码位置:飞行控制栈在 ArduCopter/,传感器库在 libraries/AP_*,HAL 接口在 libraries/AP_HAL/,平台实现在 libraries/AP_HAL_ChibiOS/,硬件定义在 hwdef/。这条路径是理解 ArduPilot 外设生态的主线。