一、ArduPilot整体架构:从宏观角度俯瞰飞控系统如何“活”起来
大家好,我是你们的讲师。今天咱们正式开篇,聊聊ArduPilot的整体架构。
很多新手拿到ArduPilot源码,第一反应往往是懵的——几万行代码,从传感器驱动到导航算法,从遥控器输入到电机输出,到底是怎么串起来的?说白了,飞控系统就像一个人体:传感器是眼睛耳朵,控制算法是大脑,执行机构是手脚。但要让这一切协调工作,你得先理解它的“神经系统”和“心跳节奏”。
我个人习惯把ArduPilot的架构拆成三层来看:硬件抽象层、核心调度层、应用逻辑层。今天咱们重点讲中间那层——调度机制,因为这是飞控“活”起来的根本。
1.1 代码结构:三个关键目录
打开ArduPilot源码,你首先会看到几个顶层目录。我建议你重点关注这三个:
- libraries/:核心库,包含传感器驱动、数学工具、通信协议等。这里的东西是跨平台的,不管你是Pixhawk还是Cube,都能用。
- ArduPlane/:固定翼专用逻辑。航线控制、姿态解算、任务管理都在这里。
- AP_HAL/:硬件抽象层。这是ArduPilot的精髓——通过HAL把上层代码和具体硬件解耦。换一个MCU?只要实现HAL接口就行。
我记得刚接触ArduPilot时,总搞不清ArduPlane和libraries的区别。后来才明白:libraries是工具箱,ArduPlane是施工图纸。工具箱里的扳手、螺丝刀谁都能用,但图纸决定了怎么搭出一架固定翼。
1.2 调度机制:飞控的“心跳”
飞控系统本质上是一个实时嵌入式系统。它不能像PC那样“等用户点击”,而是必须按照固定节拍,周期性地完成传感器读取、姿态解算、控制输出。
ArduPilot的调度核心是AP_Scheduler库。它的工作方式很简单:
- 定义一个任务列表,每个任务有固定的执行频率(比如400Hz、50Hz、10Hz)
- 主循环不断轮询,检查当前时间该执行哪个任务
- 如果某个任务超时,就跳过本次执行,保证不阻塞后续任务
来看一段简化后的调度代码:
// 任务表定义
static const AP_Scheduler::Task task_table[] = {
{ &read_sensors, 400, 100 }, // 400Hz,预留100微秒
{ &update_attitude, 400, 200 }, // 400Hz
{ &update_navigation, 50, 300 }, // 50Hz
{ &update_waypoints, 10, 500 }, // 10Hz
{ &send_telemetry, 5, 1000 }, // 5Hz
};
void AP_Scheduler::loop() {
uint32_t now = micros();
for (uint8_t i = 0; i < num_tasks; i++) {
if (now - task_table[i].last_run >= task_table[i].interval) {
task_table[i].function();
task_table[i].last_run = now;
}
}
}
你可能会问:为什么不用RTOS的任务调度?嗯,这里有个历史原因。ArduPilot早期跑在8位AVR单片机上,资源极其有限,连RTOS都跑不起来。后来虽然移植到了STM32等32位平台,但这种协作式调度的架构一直保留了下来。好处是简单可靠,坏处是——如果一个任务卡死,整个系统就挂了。
1.3 核心线程:谁在干活?
虽然ArduPilot没有用RTOS,但它确实有“线程”的概念——只不过是用定时器中断模拟出来的。固定翼飞控主要靠以下几个“隐形线程”工作:
| 线程/中断 | 频率 | 职责 |
|---|---|---|
| 主循环 (Main Loop) | 400Hz | 调度所有周期性任务 |
| 定时器中断 (Timer ISR) | 1kHz | PWM生成、RC输入捕获 |
| I2C/SPI DMA | 异步 | 传感器数据搬运,不占CPU |
| UART中断 | 异步 | 数传、GPS数据接收 |
你看,真正干活的主循环只有400Hz,但通过中断和DMA,系统能处理1kHz的PWM信号和异步的传感器数据。这就是嵌入式系统的典型设计——用中断处理实时性要求高的事件,用主循环处理复杂的逻辑计算。
1.4 飞控如何“活”起来?
现在我们把所有拼图拼起来,看看一个典型的控制周期:
- 传感器读取(400Hz):从IMU读取加速度和角速度,从GPS读取位置和速度
- 姿态解算(400Hz):用EKF或互补滤波,融合IMU和GPS数据,得到当前姿态
- 导航计算(50Hz):根据当前姿态和航线目标,计算需要的控制量
- 控制输出(400Hz):把控制量转换成PWM信号,驱动舵机和电机
- 任务管理(10Hz):检查是否到达航点,是否需要切换飞行模式
整个过程就像工厂流水线——每个工位(任务)有自己的节拍,物料(数据)在工位间传递。如果某个工位慢了,后面的工位就得等,但绝不能堵死整条线。
1.5 一张图看懂整体架构
下面这张SVG图,是我自己画的ArduPilot整体架构。它把刚才讲的三层结构、调度机制、数据流都串起来了。你仔细看看,应该能对飞控系统有个全局认识。
这张图里,从上到下是三层结构,从左到右是数据流。你注意看虚线箭头——硬件层通过中断把传感器数据“推”给调度层,调度层再“喂”给应用层。这种推拉结合的方式,既保证了实时性,又避免了轮询浪费CPU。
1.6 总结
好了,这一章我们站在高处,把ArduPilot的骨架看了一遍。记住三个关键词:三层架构、协作式调度、中断驱动。下一章我们会深入ArduPlane的代码,看看固定翼的航线控制到底是怎么一步步实现的。
最后留个思考题:为什么ArduPilot不直接用FreeRTOS?如果你来设计,会怎么改进这个调度器?想清楚了,后面几章你会理解得更深。
公众号:蓝海资料掘金营,微信deep3321