首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
ArduPilot 底层换血:ChibiOS 为何成为飞控新基石?
📅 2026/9/19 6:27:38
✍️ 爱科研究院
👁 阅读 3,247
我第一次意识到 ArduPilot 的底层已经换了天是在一块基于 STM32F405 的自研飞控板上。当时我脑子里还是 Pixhawk 时代留下来的印象遇到点问题总想去找 NuttX 的 shell、mount这类命令结果翻到libraries/AP_HAL_ChibiOS这个目录才反应过来整个固件的底层早就不是当初那套系统了。对玩飞控有些年头的人来说ChibiOS 这次上位可以说是 ArduPilot 近年来影响最深、也最成功的一次底层升级。这篇文章不打算照着 RTOS 教科书讲抽象理论而是从一个实际折腾 ArduPilot ChibiOS 的开发者的角度把“为什么要换 RTOS”“ChibiOS 到底强在哪里”“ArduPilot 是怎么用它的”以及“我在移植自研飞控板时踩过的坑”一次讲清楚。如果你正准备深入 ArduPilot 源码或者想基于开源方案做自己的飞控硬件这部分内容应该能帮你省下不少时间。1. 换 RTOS 不是折腾NuttX 时代的痛点1.1 从 Arduino 到 Pixhawk飞控按需引入 RTOSArduPilot 的前身 APM 最早是在 8 位 AVR 单片机上跑的整个程序就是main里的一个超级循环按顺序处理遥测、传感器、控制率。后来 Pixhawk 带来了 STM32F407板上外设数量和任务复杂度一下子上来了单循环里什么都管已经变得很难维护。于是 ArduPilot 和 PX4 一样选择了 NuttX 作为底层系统。NuttX 的 POSIX 风格很浓线程、消息队列、信号量、文件系统、网络协议栈都很全写应用时能很大程度上复用 Unix 的开发经验这也是它能进入早期开源飞控生态的原因。但问题也恰恰出在“全”上。NuttX 为了兼容 POSIX内部要维护文件描述符表、任务控制块、各种内核对象这些在 PC 上不算什么放到只有 192KB RAM 的 STM32F405 上就成了很现实的压力。我早期在 Pixhawk 上编译 ArduPilot测试过那种长时间飞行加高速日志写入的场景SD 卡操作偶尔会把主控制循环拖出可感知的抖动。这类问题难的不是不会排查而是很难稳定复现处理起来特别磨人。1.2 NuttX 的维护成本与 ChibiOS 的机会ArduPilot 团队考虑切换维护成本是非常关键的因素。NuttX 的版本迭代很快每次升级ArduPilot 都要同步跟进修补加上 NuttX 的外设驱动采用通用框架对具体 MCU 的寄存器级优化相对弱团队还要为不同芯片额外维护大量底层补丁。与此同时ChibiOS 提供了一个更“小而专”的路线它专门围绕 STM32 这类 MCU 设计内核轻量配置阶段静态化还自带 USB 协议栈、FatFS 文件系统、CAN 驱动几乎就是为飞控固件这种场景准备的。ArduPilot 换到 ChibiOS 之后一个很直接的好处是官方不用再为几百种飞控板分别打 NuttX 补丁而是通过一套统一的板级描述文件来管理不同硬件。新增一块飞控板的工作量从原来改一堆 C 驱动代码变成了集中编写一个hwdef.dat这个变化是革命性的。编译产物和启动时间也有改善飞控上电到进入主循环的等待明显缩短。1.3 为什么不是 FreeRTOS很多人会问FreeRTOS 在嵌入式圈子的普及度那么高ArduPilot 为什么没选它。我的理解是FreeRTOS 本质上是一个“精简内核”它把任务调度、同步原语做得很扎实但没有提供自带的外设驱动模型、USB 协议栈和文件系统。飞控不能只靠内核跑起来还要接 IMU、接 SD 卡、接 USB 虚拟串口、接 CAN 总线。如果选 FreeRTOSArduPilot 就需要自己维护一大堆上层组件再和各个厂商 HAL 对接工作量完全不比 NuttX 时代小。ChibiOS 提供的是“RTOS 外设栈 文件系统 USB CAN”的一揽子方案。它的社区活跃度可能不如 FreeRTOS但在飞控这种特定领域里外围配套的完整性比单纯流行度重要得多。另外ChibiOS 的许可证也能和 ArduPilot 的 GPLv3 兼容团队不用担心授权冲突。所以从工程角度看ChibiOS 胜出并不意外。2. ChibiOS 在飞控场景的硬指标实时性、资源占用与生态对照2.1 实时性抖动比延迟更致命飞控系统追求的是硬实时不是普通操作系统的“尽量快”。很多人会把 RTOS 和 Linux 混为一谈其实两者目标完全不同Linux 调度器追求的是公平性和高吞吐而飞控要的是确定性和最坏情况延迟。传感器读取、姿态解算、电机指令更新都是严格按固定频率执行的如果某个任务慢了 100 微秒可能导致整个控制环路性能下降甚至引发飞行异常。ChibiOS 的调度器非常直接固定优先级抢占式调度高优先级任务可以立刻打断低优先级任务内核里没有复杂的动态调度策略。在 ArduPilot 的实际配置里SPI 读取 IMU 往往通过 DMA 完成传输结束中断触发后在中断上下文里直接处理数据并置事件标志这样上层循环拿到的数据时间戳非常稳定。相比之下NuttX 因为要兼容更多 POSIX 语义系统调用路径更长调试选项默认也更重实时路径上的抖动会略大。这也是飞行日志里 IMU 时间间隔均匀性在 ChibiOS 上明显更好的原因之一。2.2 资源占用静态配置和可预期的栈ChibiOS 的核心设计理念之一就是“尽量在编译期决定一切”。线程栈、消息池、内存池都在编译阶段固定运行时的动态分配尽可能少。ArduPilot 在 STM32F405 这类芯片上移植时RAM 占用比早期 NuttX 方案要低不少省下来的内存可以留给日志缓存、传感器缓冲和任务栈。这种静态化对飞控调试的意义也很大。我在看线程栈使用率时直接通过 shell 的ps命令就能知道每条线程的高水位标记没有任何堆碎片化的困扰。跑得久了也不会因为频繁malloc/free产生内存空洞系统行为始终可预期。对于需要长时间稳定运行的无人机载具来说这一点比理论性能更重要。2.3 外设生态USB、文件系统和 CAN 的完整度不是所有 RTOS 都内置完整的外设栈ChibiOS 在 STM32 生态上做得很全。USB 协议栈支持 CDC、HID、MSC 等常见配置ArduPilot 直接拿它做 USB 虚拟串口和地面站通信文件系统用 FatFS日志稳定写入 SD 卡CAN 驱动支持 DroneCAN可以在不额外写驱动的情况下驱动电调、空速计等外设。对比一下用 FreeRTOS 需要自己集成这些组件维护量不是一个数量级。ChibiOS 的外设抽象也更加贴近 STM32 的寄存器模型。它不像 Linux 驱动那样绕几层而是直接操作定时器、DMA、GPIO 复用配置。飞控底层要的就是这种“透明感”每个引脚、每个 DMA 通道、每个定时器资源怎么分配都能在配置里看到出了问题可以顺着配置一路查下去。对照项NuttX 方案ChibiOS 方案调度风格POSIX 优先抢占兼容层重固定优先级抢占调度路径短内核资源占用较高动态对象多较低静态配置为主外设驱动框架通用框架需为芯片单独打补丁与 STM32 寄存器模型深度绑定USB/文件系统/CAN自带但集成成本高USB、FatFS、CAN 均模块化内置新板适配方式修改 C 代码和补丁集中编写 hwdef.dat 描述文件调试手段NSH shell 等功能全但偏重ChibiOS shell 轻量直接栈高水位清晰3. ArduPilot 的 ChibiOS HAL 是如何工作的线程、中断、DMA 与 PWM 输出3.1 AP_HAL_ChibiOS 的目录结构与角色ArduPilot 能跨平台靠的是AP_HAL抽象层它定义了线程、时间、外设、存储等基础接口。ChibiOS 对应的实现放在libraries/AP_HAL_ChibiOS目录里里面又按功能细分出UARTDriver.cpp、SPIDevice.cpp、I2CDevice.cpp、RCOutput.cpp、Scheduler.cpp等文件。上层自动驾驶算法根本不会直接接触 ChibiOS API而是通过统一的 HAL 接口调用底层能力。这样设计的好处是换 RTOS 时不需要重写飞控算法只需要把AP_HAL的实现从 NuttX 换成 ChibiOS。真正复杂的部分是让底层调度和外设驱动配合好。ChibiOS 之所以让这件事变得简单是因为它的配置密度很高所有板级细节都能在hwdef.dat中描述清楚编译时自动生成board.h、mcuconf.h、chconf.h等文件整个硬件配置一目了然。3.2 线程模型调度器线程、IO 线程和中断上下文ArduPilot 在 ChibiOS 上并不是每个传感器都开独立线程去抢总线。它的主线程也就是调度器线程负责按固定时间片轮询姿态控制、导航、遥测等任务IO 线程负责处理日志写入、文件系统操作这类相对慢速的活儿避免阻塞主控制任务。UART、SPI、I2C 等外设通常由 DMA 驱动数据到达后通过中断触发事件上层调度器拿到数据时已经不需要等待传输完成。这里有一个关键点ChibiOS 的线程对象是静态创建的栈大小在编译时确定。ArduPilot 在创建线程时传入栈大小比如日志写入线程、CAN 线程、USB 线程都各有自己的栈空间。如果栈开小了问题的表现不是立刻崩溃而是高负载时偶尔进入 HardFault非常隐蔽。所以每次调试线程行为我都会先通过 shell 里的ps看栈高水位这个习惯养成了真的能省很多事。3.3 DMA 与缓存一致性H7 上最容易被坑的一环STM32F7/H7 内核带 D-CacheDMA 直接把数据从外设搬到内存时CPU 的缓存里可能还是旧数据。如果驱动不处理缓存一致性问题读上来的 IMU 数据就会每隔一段时间跳几个数。ArduPilot 在 ChibiOS 驱动里对 DMA buffer 做了特殊处理缓冲区按照 32 字节对齐DMA 写完之后执行SCB_InvalidateDCacheDMA 读之前执行SCB_CleanDCache保证 CPU 和外设看到的是一份一致的数据。我刚上手 STM32H743 的时候在这个问题上卡了一整天。现象很奇怪SPI 读 BMI088大部分时间正常偶尔一个轴的数据突然跳变日志里又没有明显的电气干扰。后来我关掉 SPI DMA问题消失才确定是缓存一致性而不是传感器问题。解决方式也不复杂在驱动里对每个 DMA buffer 做对齐并在合适位置调用 cache 维护函数问题就彻底消失了。3.4 定时器、PWM 输出和 DShot 的底层实现电机控制是飞控实时性要求最高的部分之一。ArduPilot 在 ChibiOS 上不是通过普通 GPIO 翻转去模拟 PWM而是直接使用 STM32 定时器和 DMA。每个 PWM 通道对应一个定时器输出比较通道DMA 批量更新比较寄存器CPU 不需要逐个通道去翻转电平DShot 协议更是需要精确到纳秒级的波形手写延时根本不可靠。ChibiOS 的好处在于这些定时器资源可以在hwdef.dat里明确映射。比如PB1 TIM3_CH4表示把 PB1 引脚连接到定时器 3 的通道 4RCOUT层会在启动时配置好复用关系和 DMA 通道。自己画飞控板的时候只要按照原理图逐行写好映射就能让驱动自动识别引脚功能不需要改驱动源码。这一点对硬件调试和产品快速迭代的帮助非常大。4. 在自研飞控板上跑 ChibiOS踩坑记录与排查链路4.1 一切从 hwdef.dat 开始把 ArduPilot 移植到自研飞控板最容易上手的路径不是改 C 代码而是写hwdef.dat。它本质上是一个板级描述文件在libraries/AP_HAL_ChibiOS/hwdef/下新建一个板型目录放置hwdef.dat按照官方参考板的格式逐项配置MCU 型号、外部晶振频率、Flash/RAM 大小、引脚复用、外设使能、DMA 映射、电压阈值、LED 定义等。我拿一块自己画的 F405 板子举例关键配置大概是这样MCU STM32F405 OSCILLATOR_HZ 8000000 FLASH_SIZE_KB 1024 RAM_SIZE_KB 192 DMA_OPTIMIZE 1 # 舵机 PWM 输出 PB1 TIM3_CH4 RCOUT PB0 TIM3_CH3 RCOUT # 串口 PA9 USART1_TX UART1 PA10 USART1_RX UART1 # I2C PB6 I2C1_SCL I2C1 PB7 I2C1_SDA I2C1 MAIN_THREAD_STACK_SIZE 8192用 Waf 构建时这套配置会自动生成 ChibiOS 相关的头文件再进到编译流程。第一次编译成功后先不要急着上天先把串口打印、USB 枚举、传感器数据这些基础项逐项过一遍确认板级配置没有隐藏错误。4.2 线程栈不够的调查方法线程栈不足时飞控的表现往往很迷惑。可能是飞行中突然重启也可能是高负载写入日志时进 HardFault。ChibiOS 提供了两个很实用的调试开关CH_DBG_ENABLE_STACK_CHECK和CH_DBG_FILL_THREADS。开启后系统会周期性检查栈边界并且在创建线程时用固定模式填充栈空间便于后续查看栈实际使用量。在 ArduPilot 的调试阶段我建议直接把这两个宏打开再用 USB/串口进入 ChibiOS shell执行ps。它能看到每条线程的栈用量和剩余量。如果某条线程的栈高水位已经接近上限就在对应的hwdef.dat配置里调整栈大小。有一点要注意栈不是越大越好RAM 是固定的某个线程栈加得太多其他线程就可能分配不出来。理性做法是先看实际占用再留 30% 到 50% 余量。4.3 DMA 乱序和缓存不一致的真实现场除了前面说的 H7 缓存一致性问题还有一个高频坑是 DMA 中断和主循环的竞争关系。SPI 传输完成中断触发后会在中断服务里把数据复制到目的缓冲区并置事件标志。如果主循环在 DMA 还没结束时就读取缓冲区可能拿到半帧数据。ArduPilot 的官方驱动用事件标志来同步自己写外设驱动时最好不要绕开这套机制。一次我在写一个外接激光测距传感器驱动时为了省事直接在 SPI 传输结束后不加判断就读取缓冲区结果测出来距离数据偶尔会跳变。后来老老实实改成 DMA 完成中断置标志、再等事件同步的方式数据立刻稳定。这个经验给我留下的印象很深飞控驱动里不要总想着省那一个中断同步的开销确定性永远排在首位。4.4 中断优先级配置引起的“玄学故障”ChibiOS 对中断优先级的配置要求比较严格不是所有中断都能用同一个优先级。STM32 采用抢占优先级和子优先级如果外设中断优先级设置不恰当可能出现一种现象开一个外设后另一个外设的工作变得不正常单独测试又没问题。我遇到过一例给飞控板加了一个自定义串口设备后PWM 输出偶尔会抖动一拍去掉这个设备就完全正常。一开始以为是电气干扰查了很久才发现问题出在新设备的串口中断优先级比定时器更新中断高导致 PWM 的 DMA 更新在某些时刻被串口中断打断得太久。把中断优先级按 ArduPilot 官方驱动的默认配置对齐后问题立刻消失。飞控这类系统优先级配置虽然不起眼但破坏力极大。4.5 排查工具shell、backtrace、逻辑分析仪ArduPilot 在 ChibiOS 上的调试手段比 NuttX 时代更“透明”。通过 USB 虚拟串口进入 shell 后ps看线程栈和状态top看线程 CPU 占用dmesg看内核消息help可以列出全部可用命令。这套工具在日常底层调试中非常有用比如确认某条线程是否因为等待外设而长期挂起或者判断 CPU 占用是否已经接近极限。遇到 HardFault 时先看fault.txt里记录的 LR、PC 地址再配合arm-none-eabi-addr2line定位到具体代码。如果怀疑外设时序问题逻辑分析仪依然是不可或缺的工具。软件层面的线程栈、优先级检查只能解决逻辑问题真实的波形异常还得靠示波器或逻辑分析仪去发现。5. 把 ChibiOS 版 ArduPilot 用起来编译、烧录、调优与选型思考5.1 从 Waf 到固件一次干净的构建ArduPilot 使用 Waf 做构建系统编译流程比老式 Makefile 清晰很多。自定义板型的构建命令大致是这样./waf configure --board MyF405 ./waf copter编译完成后固件会输出在build/MyF405/bin/下。如果是第一次接触 Waf建议直接参考官方文档把arm-none-eabi-gcc和 Python 环境提前装好。要注意不要直接去改build目录下自动生成的文件这些文件每次配置都会重新生成。真正的配置源头只有hwdef.dat。5.2 bootloader 与固件的配合关系ArduPilot 固件能够通过 USB 刷写前提是板子里先有配套的 bootloader。自研板第一次烧录时通常要用 ST-Link/J-Link 通过 SWD 接口把 bootloader 刷进去之后才能用地面站的“固件升级”功能。bootloader 和固件必须匹配同一个板型配置尤其是晶振频率、串口号和 USB 引脚定义否则会出现固件能刷但启动不了的情况。我早期调试时遇到过一种“假砖”现象固件烧进去了但上电后地面站不识别设备最后发现是 bootloader 里配置的 USB 引脚和固件里配置的不一致导致 USB 枚举失败。换回一致的配置后立刻恢复。所以自研板验证阶段建议先仔细核对 bootloader 源工程里的引脚定义和hwdef.dat是否一致。5.3 跑通之后的性能调优固件能正常起飞之后可以进一步做性能调优。打开hwdef.dat里的DMA_OPTIMIZE 1让传感器读写尽量走 DMA根据飞控的主频和外设延迟合理设置调度器主频SCHEDULER_LOOP_RATE比如从默认 400Hz 提升到 1000Hz但需要同时确认 IMU 采样频率跟得上。通过top命令观察 CPU 占用如果主线程占用长期超过 70%说明调度周期设置得太激进或者某个传感器驱动效率太低。调优后的验证不能只看稳定飞行还要看日志质量。检查IMU数据的时间戳间隔是否均匀、RCOUT输出是否出现周期抖动、CPU 负载曲线是否平稳。这些指标比单纯看“飞得爽不爽”更能反映系统的真实状态。5.4 关于 RTOS 选型的一些个人思考这几年做开源飞控一个越来越强烈的感受是选 RTOS 不是在选功能最多、用户最多的那个而是在选和业务场景匹配度最高的那个。ArduPilot 选择 ChibiOS核心原因是想让底层变得更加可预期、可维护而不是追求某个跑分数据。对大多数飞控用户来说RTOS 选哪个并不直接影响手感但如果你做自研硬件、写底层驱动、调传感器时序ChibiOS 这套静态配置加轻量内核的设计会让人省心很多。我后来在非飞控项目里也试过其他 RTOS包括 FreeRTOS 和 RT-Thread它们各有优势但每次回到 ArduPilot 的 ChibiOS 世界看到那熟悉的hwdef.dat和 shell 命令总会有一种踏实感。如果你正准备把手上的飞控方案迁移到 ChibiOS或者正在考虑为自研飞控板选型建议多读几块官方支持板卡的hwdef.dat把那些配置吃透比看多少理论都管用。真遇到问题源码里的示例往往就是最好的答案。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/9/19 6:27:38
无人机SAR雷达成像技术:时域回投影算法解析
2026/9/19 6:27:38
三防布行业现状、TOP10企业分析与未来技术趋势
2026/9/19 6:27:38
ANSYS仿真工作流闭环:从PPT课件到工程复现的全链路解析
2026/9/19 7:22:41
给Homebrew做可视化客户端:SwiftUI封装命令行实践
2026/9/19 7:22:41
_s 的 SASS 架构揭秘:Generic/Base/Components 分层设计完整指南
2026/9/19 7:22:41
uni-app x canvasToTempFilePath 指南:画布指定区域导出为临时图片文件
2026/9/19 7:22:41
邮箱验证的正确姿势:从RFC 5322语法到投递证明的分层架构
2026/9/19 7:22:41
基于S7-1200的PLC多电机编号启动控制方案详解
2026/9/19 7:17:41
自建桌面端CRM系统实战:从需求拆解到权限管控的完整指南
2026/9/19 0:02:13
PixiJS v8 遮罩(Masking)完全指南:AlphaMask、StencilMask、ScissorMask 与 ColorMask
2026/9/19 0:02:13
GLM 5.3 Flash 被 Artificial Analysis 收录:用 TaoToken 复现同一把 Key
2026/9/19 0:02:13
分布式雷达多维度干扰建模与抗干扰算法实现
2026/9/18 16:05:49
拯救者Y7000黑屏故障排查与维修实战指南
2026/9/18 3:56:12
AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
2026/9/18 13:25:13
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化