简介:本资源是一套基于STM32平台的四轴飞行器及其配套飞控遥控器的完整硬件与固件开发资料,面向嵌入式初学者、无人机爱好者及课程设计/毕业设计实践者,解决多传感器融合、PID姿态控制、无线遥控通信等典型飞控系统开发难点。压缩包共192个文件,含81个C源码与90个头文件(覆盖TIM、RCC、ADC、FLASH等外设驱动及飞控核心算法),2个Keil工程文件(uvprojx/uvoptx),2个PCB原理图文件(pcbdoc),2个PDF文档( likely为硬件说明或接线指南),以及bat批处理脚本、hex固件和调试配置文件,整体仅2.44MB,轻量易部署。已有2868人学习下载,内容结构清晰,源码注释充分,配套原理图与PCB可直接用于打样调试,bat脚本简化编译烧录流程,是理解STM32飞控底层实现与软硬协同开发的实用入门套件。 手头这套STM32四轴飞行器加飞控遥控器的项目,老实说不是那种拿开源固件烧录一下就完事的工程,而是从原理图、PCB Layout到源程序全部自己重新捋了一遍的完整方案。很多人一开始玩四轴会选择直接买Pixhawk或者改装Betaflight成品飞控,而我当时的目标很明确:从STM32芯片往上搭,把飞控板、遥控器、接收机整套通信链路都捏在自己手里。这套东西做下来,收获最大的并不是它飞得有多稳,而是你把姿态解算、PID调参、PWM输出、2.4G通信这一整条链路走通之后,再看市面上任何一款飞控都会觉得心里有底。这篇文章就把整套项目的原理图设计思路、PCB布线要点和源程序框架一起拆开讲,适合手里已经有STM32基础、想自己画板写飞控的玩家,也适合正在纠结要不要从整机方案转自研的硬件工程师。
1. 项目整体设计与思路拆解
1.1 为什么选STM32做飞控主控
飞控主控的选择,本质上是算力、外设资源、生态熟悉度这三者之间取平衡。STM32之所以成为自研飞控的默认选项,核心在于它不缺外设、资料足够厚、踩坑成本低。四轴飞控需要同时处理传感器数据采集、姿态解算、电机控制输出、遥控信号解析、状态指示等等任务,这些在STM32上都可以用硬件外设来解决,而不需要靠CPU裸算硬扛。
以我用的STM32F407VGT6为例,Cortex-M4内核带FPU,168MHz主频在跑姿态解算和PID控制环时非常舒服,尤其是浮点运算单元可以直接用硬件指令算四元数,比STM32F103那种M3核软浮点快出一个量级。再加上它有多个高级定时器(TIM1、TIM8),可以生成带死区控制的互补PWM,这在有刷电机或者某些双向电调场景下很有用;DMA+ADC的组合可以持续采样电池电压和电流,不影响主循环实时性。如果你预算更紧或者手头只有F103C8T6,那也能做,F103跑500Hz的姿态环完全够用,只是留给日志记录、无线调试的余量会少一些。
另外,STM32在四轴项目里还有一个隐性优势:几乎所有的开源飞控项目(Cleanflight、Betaflight、PX4的早期版本)都有STM32移植分支,这意味着当你自己写的算法出问题的时候,有海量参考代码可以对比排查。这一点在调试异常姿态输出时极其关键,后面我会专门讲。
1.2 整机系统架构:飞控板+遥控器+接收机的三方联动
这套项目不是单独一块飞控板,而是“飞控主板+手持遥控器+接收机模块”三个部分组成的完整链路。飞控主板是核心,装载IMU传感器(MPU6050/MPU9250)、气压计、电调输出接口和接收机输入接口;遥控器则是另一块独立的STM32板卡,负责采集摇杆电位器/霍尔摇杆的模拟量,将油门、俯仰、横滚、偏航四个通道的数据打包,通过2.4G射频模块发送出去;接收机挂在飞控上,收到遥控数据后解析出PPM信号或直接以串口协议送入飞控的UART引脚。
这套架构看起来多了一块板子和一堆通信逻辑,但它有一个关键好处:把“飞控逻辑”和“人的操作输入”完全解耦。你可以先用配套遥控器飞,也可以后续换用其他厂家的遥控器——只要接收机输出协议匹配(PPM、SBUS、DSM均可),飞控端代码只需要在接收机解析层适配一下就行。而且自己做遥控器的过程中,你会非常直观地理解油门通道定时器采集、摇杆中点校准、射频丢包处理这些概念,这些啃透了,后面玩航模遥控器基本是降维打击。
1.3 方案选型对比:自制飞控 vs 开源飞控 vs 成品飞控
很多人在这一步就陷入选择困难。我的建议是:如果目标是稳定飞行和航拍,直接买大厂成品,没必要折腾;如果目标是搞懂飞控原理、享受从焊板到升空的全过程,那么自制STM32飞控的收益远大于困难。
还是列个表直观对比一下:
| 维度 | 自制STM32飞控 | 开源飞控(Pixhawk/ArduPilot) | 成品飞控(大疆等) |
|---|---|---|---|
| 研发难度 | 中高,全链路自研 | 中等,偏配置调参 | 无 |
| 硬件成本 | 低,核心板百元内 | 较高,硬件加外围模块不便宜 | 最高 |
| 学习深度 | 全链路深入 | 算法可读但硬件封装较死 | 黑盒 |
| 飞行稳定性 | 取决于算法打磨 | 成熟稳定 | 最稳定 |
| 扩展灵活性 | 极高 | 较高 | 极低 |
| 适合人群 | 硬件开发/嵌入式学习者 | 飞手或应用开发者 | 纯用户 |
我当时选自研还有一个原因:想把飞控做的足够“小”,以及能把定制的电源管理、LED状态指示、蜂鸣器驱动都集成到一块板子上。开源飞控的硬件设计虽然开放,但器件选型和布局往往不是面向你的特定机架的,改起来反而受限于原有框架。
2. 核心细节解析与实操要点
2.1 原理图设计:最小系统之外的五大必做模块
别小看原理图设计,飞控板上每一路电源、每一个去耦电容的位置都直接影响传感器数据质量。以我画的第一版飞控为例,除了STM32最小系统(晶振、复位、BOOT配置、SWD调试接口)之外,还有五个模块是绝对不能省的。
第一是电源管理系统。四轴用3S锂电(11.1V典型值),但STM32和传感器需要3.3V,电调信号需要5V,所以必须设计多级降压。我在板上用了MP1584EN降压模块降到5V,再用AMS1117-3.3得到3.3V。这里有个关键点:如果电调的BEC输出也会给飞控供电,那两块板子的5V电源就会“打架”,最好用肖特基二极管做电源隔离。另外,整个电源路径上要加LC滤波(比如磁珠+钽电容),因为无刷电机换相时会在电源线上叠加大量毛刺,这些毛刺一旦窜进IMU的供电引脚,姿态数据立刻漂给你看。
第二是IMU传感器电路。MPU6050是I2C接口,地址引脚AD0必须通过电阻明确接高或接低,不要悬空。I2C的上拉电阻(典型4.7kΩ)不能省,而且应该靠近主控引脚放置。另外一个常被忽略的点是MPU6050的电荷泵电容(CPOUT引脚),这个电容除了提供内部负压,还承担稳压功能,建议选低ESR的X7R电容,10nF~100nF都行,放置时尽量接近引脚。
第三是电机驱动信号接口电路。STM32的PWM引脚直接连电调信号线? 理论上是可以,但工程上更稳的做法是加一级电平转换或缓冲器。很多电调的信号线耐压标的5V,实际上过冲到6V甚至更高;STM32引脚极限耐压也就VDD+0.3V,直接对接有风险。我用的是74LVC245或简单的串联电阻+TVS管做过压保护。这个问题不炸不代表风险不存在,一旦电调信号线上反电动势脉冲灌进来,烧的就不只是一个IO口。
第四是电池电压检测电路。直接用STM32 ADC采集11.1V电池电压显然是超量程的,所以必须用电阻分压网络,把电压降到0-3.3V范围。这里我用了100kΩ和10kΩ的电阻分压,再用一个100nF电容滤除高频噪声。分压电阻精度最好选1%的,并且要加TVS管或稳压管做钳位保护,防止上电瞬间的浪涌打坏ADC引脚。
第五是遥控器端摇杆模拟量采集电路。这个很多人做遥控器时会忽略,直接用STM32的ADC读摇杆电位器即可,但要注意摇杆电位器引出线较长,容易串扰,所以要在每个ADC通道输入引脚加RC低通滤波(1kΩ+100nF左右),时间常数约100μs,不会影响摇杆响应速度,但能显著减少ADC采样值的跳动。
2.2 遥控器通信链路:2.4G射频模块与数据协议
遥控器和接收机之间的通信我选的是nRF24L01+模块,2.4G频段,支持空中数据速率250kbps/1Mbps/2Mbps可选。四轴控制只需要四个通道数据,加一个摇杆校准标志和CRC校验,一包数据也就16字节以内,250kbps的速率完全够用,而且低速率下灵敏度更高,传输距离更远。
数据协议我用的是自定义的帧格式:
帧头(2字节) + 通道数据(4通道 x 2字节) + 摇杆校准标志(1字节) + CRC8校验(1字节)帧头固定为0xAA 0x55,接收机每收到一帧先校验帧头,再校验CRC,两个都通过才更新输出值。这里分享一个我踩过的坑:如果只校验帧头不校验CRC,在射频干扰较强的环境下,偶尔会有垃圾数据正好以帧头开头然后被当成有效帧解出来的情况,此时电机可能瞬间给一个全油门信号,非常危险。所以CRC校验必须做,CRC8计算函数网上很多,找一个简单的多项式0x31实现就行,也就十几行代码的事。
nRF24L01+的SPI通信速率建议设置在8MHz以下,不要盲目拉高。实测发现部分模块在10MHz SPI下偶尔出现寄存器读取错误,导致配置字被写坏,模块直接不工作。如果遇到上电后模块不响应,优先检查SPI时序和CE引脚的默认电平状态。
2.3 源程序架构拆解:从传感器数据到电机PWM的完整通路
源程序不是说把各模块堆在一起就算完,核心在于时序组织和数据流设计。在这套项目的代码里,我按“主循环+中断”的方式组织,优先级最高的IMU数据读取放在定时器中断里,PID计算和PWM输出也在中断完成,主循环只负责处理遥控信号、LED指示、状态机切换和电池电压检测。
一个典型的50Hz~500Hz姿态环架构大致是:
// 伪代码,体现主循环 + 中断的协作方式 volatile uint8_t imu_ready_flag = 0; volatile float roll, pitch, yaw; volatile uint16_t motor_cmd[4]; void TIM6_IRQHandler(void) { if (TIM_GetITStatus(TIM6, TIM_IT_Update)) { imu_read_mpu6050(&raw_acc, &raw_gyro); attitude_update(raw_acc, raw_gyro, &attitude); pid_update(&attitude, &remote_cmd, motor_cmd); pwm_update_duty(motor_cmd); imu_ready_flag = 1; TIM_ClearITPendingBit(TIM6, TIM_IT_Update); } } int main(void) { system_init(); remote_init(); while (1) { if (imu_ready_flag) { battery_voltage_monitor(); status_led_update(); remote_packet_handle(); imu_ready_flag = 0; } } }这里的pid_update函数是核心,它接收姿态解算得到的欧拉角,和遥控器解析出的期望角度做差,经过PID控制器后输出四个电机的PWM占空比。至于PID的具体参数,不同机架差异很大,后面我单独开一节讲调参经验。
2.4 常见问题与排查技巧实录
说几个我实际调试中遇到的问题,都是网上帖子不太会细讲的那种。
问题一:MPU6050的I2C总线卡死在读数据阶段
现象是飞控上电后,姿态数据偶尔更新,但更多时候卡住不动。用逻辑分析仪看波形,发现SCL线一直低电平,像是从机拉住了时钟线。排查过程:先怀疑是IMU模块虚焊,重新贴片后依旧;后来用示波器掐住I2C引脚量,发现SDA线上的上升沿非常缓慢,换了下拉电阻阻值也没改善。最后定位到是MPU6050的INT中断引脚没有接上拉,导致主控在读数据时误触发了中断标志,和I2C通信相互干扰。解决方法是把INT引脚接10kΩ上拉到3.3V,同时在代码里把中断配置为仅在数据准备好时触发。这个坑在MPU9250上更明显,因为它的磁力计数据读取对时序要求更严格。
问题二:电调PWM信号输出正常,但电机不转
这个真的很玄学,PWM用示波器看波形是对的,频率50Hz,脉宽1ms~2ms之间可调,但四颗电机全部无反应。后来查了电调说明书才发现,很多电调出厂默认是需要“油门行程校准”的,第一次通电时必须先把油门打到最高,再上电听到提示音后拉到最低,才能识别PWM的最小和最大值。不校准的话,电调不会把油门信号映射到电机转速。这个在校准完一次之后就永久生效,但如果你换了飞控板或者改了PWM频率,一定要重新校准。
问题三:气压计高度数据跳变幅度大
BMP280或MS5611气压计在飞行中受到螺旋桨气流扰动,数据跳变非常正常。我一开始以为传感器坏了,后来发现是把它装在了靠近电机散热风道的位置,螺旋桨一转,气压就不稳定。解决办法有两个层面:硬件上给气压计加海绵防风罩,并在PCB上留出一点空隙让气压计和外界连通;软件上务必实现“限幅滤波+滑动平均”,不能直接把原始气压值拿去做高度环。实际测试下来,加海绵罩后气压计的跳动幅度能降低70%以上,再做5次滑动平均,基本可以满足定高需求。
2.5 PCB Layout的专属细节
这块内容很多人觉得不过就是布线而已,但飞控板是典型的“模拟+数字+功率混合”板卡,Layout的好坏直接决定能不能稳定起飞。
主控与传感器的分区隔离:IMU传感器要尽量靠近机架几何中心,同时远离功率走线。在PCB上我特别划分了“传感器地”和“功率地”,两者通过一个0Ω电阻或磁珠单点连接。这个做法能有效隔离电机PWM大电流回路产生的噪声,让IMU的地电位更干净。实测通过这种地平面分割,陀螺仪零偏稳定性提升肉眼可见。
大电流走线:电池正负极走线需要承载整机的电流(3S电池带2204电机单颗悬停电流约4A,四颗就是16A,起步阶段甚至更高),所以电池焊盘到电调输出焊盘的走线必须加宽,至少2mm以上,并且要加铺铜,必要时开窗加锡。如果你发现电调输入端的电压在油门推高时掉得厉害,大概率就是这段走线电阻太大。解决办法是量一下走线电阻,或者直接看热成像,发热严重的铜皮就是瓶颈。
晶振和复位电路:HSE晶振附近的走线要远离I2C和SPI等高频数字信号线。晶振下方的地层最好净空,不要有其它信号线穿过。复位引脚上拉电阻和电容要尽量靠近NRST引脚,避免复位信号被干扰导致飞控在空中异常重启。这个故障现象极其隐蔽,因为在桌面测试时不会出现,只有电机产生强振动和电磁干扰时才会偶尔触发。
四层板还是双层板:如果做双层板,一定要保证底层有大面积地平面,并且顶层的地过孔密度要足够。我第一版为了省成本做了双层板,结果IMU信号质量一直不理想;第二版改成四层板,中间两层分别做地和电源,信号质量立刻改善。四层板成本高一些,但对于飞控这种小型高密度板,我认为是值得的。如果硬要选双层,则务必在IMU底部、主控底部、射频模块底部多打地孔,减小回流路径面积。
3. 实操过程与核心环节实现
3.1 元器件选型与核心板搭建
原理图设计之前,先把关键元器件定下来。除了STM32主控之外,我列几个直接影响性能的器件清单,供你参考:
| 器件 | 型号/选型建议 | 用途 | 备选方案 |
|---|---|---|---|
| IMU | MPU6050(六轴)或 MPU9250(九轴) | 姿态角速度与加速度测量 | ICM-20602 |
| 气压计 | BMP280 或 MS5611 | 高度测量与定高 | SPL06-001 |
| 射频模块 | nRF24L01+ | 遥控遥测通信 | SI24R1(兼容) |
| 电调PWM缓冲 | 74LVC245 或三态缓冲门 | 信号隔离保护 | SN74LVC1G07 |
| 5V电源 | MP1584EN模块 | 主降压 | TPS54302 |
| 3.3V LDO | AMS1117-3.3 | 数字电源 | RT9193 |
| 蜂鸣器 | 有源蜂鸣器 5V | 状态提示 | MLT-5020 |
| 摇杆电位器 | 10kΩ 线性摇杆 | 遥控器通道输入 | 霍尔摇杆(更耐用) |
比较值得说明的是,MPU6050和MPU9250的选择:如果你后续想加航向锁定功能(磁力计),建议直接上MPU9250,因为它的磁力计也有原厂校准数据;但如果只是做自稳飞行,MPU6050六轴完全够用,磁力计反而容易受机架电机磁场干扰,调试时多了变量。我最终选了MPU9250,但在代码里把磁力计数据作为扩展使用,默认姿态解算只融合六轴数据。
核心板的搭建我直接采用了MCU系统板方式——将STM32F407、晶振、复位、SWD接口、电源去耦电容整合在一个小模块上,然后飞控主板PCB通过排针与核心板连接。这比直接把芯片贴在飞控主板上更灵活,调试时可以单独换核心板,不用重烙大板。缺点是多了排针的接触电阻和体积,但以飞控这个量级来说完全可接受。如果你想要极致小型化,也可以单板直贴芯片,只是每次烧录固件都要插SWD排线,不太方便。
3.2 原理图三步设计法:从框图到连线
我画原理图不喜欢一上来就画引脚连线,推荐你先画系统框图:把主控、IMU、电源、射频、电调接口、LED、蜂鸣器当成积木块,标清楚每个块之间的接口类型和信号方向。这一步做完,原理图连接相当于已经把逻辑核实了一遍,出错的概率大减。
第二步是填充细节,比如每个模块的电源去耦电容、上拉电阻、调试引脚预留。这里要提醒一个细节:STM32的BOOT0和BOOT1引脚不要直接接地,建议通过10kΩ电阻接地或接跳线,方便后续需要串口ISP下载时切换启动模式。很多人在画板时为了省事直接把BOOT0接地,结果后面一旦固件把SWD引脚复用掉,就只能靠串口ISP救砖了。
第三步是检查“电源树和电流预算”。逐级核对电压和电流:电池电压输入→5V→3.3V,每一级的最大电流是多少,电容的耐压值是否足够。特别要注意3.3V LDO的功耗——如果5V转3.3V压差太大且电流需求高,LDO会热到烫手,此时建议改用DCDC降压模块,或者至少在PCB上给LDO留出足够的散热铜皮。
3.3 遥控器实现要点:摇杆数据采集与射频发送
遥控器的核心是摇杆模拟量采集和射频发送。摇杆电位器一般有三个引脚,两端接VCC和GND,中间滑片输出模拟电压。这个电压范围大约是0~3.3V,STM32 ADC可以直接读取。但要注意几个坑:
第一是摇杆电位器不可能是理想的线性器件,每个摇杆在机械中点时输出电压未必正好是1.65V,所以必须做“摇杆校准”。我的做法是在遥控器上电时检测一次每个通道的电位器最小值、最大值和中点值,然后把这些值存到Flash或EEPROM中,飞行时把ADC原始值映射到标准化区间[-1000, 1000]。如果你不做校准,飞控收到的期望值就是偏的,飞机会向着某个方向自动偏航。
第二是ADC采样值的稳定性。摇杆的电位器会产生一定的接触电阻变化,如果直接读取原始采样值,遥控器显示屏上的数值会不停跳动。解决方法是每个通道采样4次求平均,并且对采集结果做一次“死区”处理——当摇杆变化小于设定阈值时,保持上一次的输出值不变。这样遥控器输出给飞控的通道值非常平滑,飞机姿态也会更稳定。
第三是射频发送周期的选择。我采用10ms为一个发送周期,即100Hz的遥控数据刷新率,对应飞控端100次/秒的期望角度更新。这个频率对大多数四轴来说完全足够,而且保证了较低的空口占用率和模块功耗。如果你用的是2.4G的nRF24L01+,10ms发送一包16字节的数据,实测空中耗时约为2ms左右,留足了射频通道空闲时间,不容易互相干扰。
3.4 飞控端接收机解析与信号处理
接收机模块把收到的数据包解析后,通过UART把通道值发送给飞控。这里有个容易混淆的地方:很多论坛讲“PPM编码”,那是接收机把多个通道信号按时间序列编码到一条信号线上的方式;而我们自己做的接收机为了简化,直接用UART发送打包后的通道值,飞控端按帧格式解析。
飞控端解析代码的核心是一个状态机:空闲态等待帧头,收到0xAA后进入半帧态,再接0x55则确认帧头,随后累积读取数据字节并同步计算CRC,收满一帧后校验CRC,正确则更新遥控通道值,错误则丢弃整帧。状态机的实现可以完全避免UART字节乱序导致的数据错位问题。
UART接收建议用DMA或空闲中断,不要在中断里做耗时的数据处理。我用的方案是UART+IDLE中断把数据搬进环形缓冲区,主循环中解析。这种方式即使主循环因为PID计算偶尔变慢,也不会丢失数据。如果你发现飞控端偶尔出现通道值突变,大概率是UART接收溢出导致的帧错位,加一个环形缓冲区之后这个问题基本消失。
3.5 姿态解算算法:四元数更新与互补滤波
姿态解算是整个飞控软件中最核心、最劝退的一部分,但理解它其实并不难。我用的算法是典型的Mahony互补滤波,在STM32上实现只占用很少的算力和存储。
通俗一点说:陀螺仪测得的是角速度,把它积分可以得到角度,但陀螺仪本身存在零偏漂移,长时间积分会导致角度不断跑偏;加速度计测得的是重力方向,可以直接算出俯仰和横滚角,但它对运动加速度非常敏感,飞机机动时加速度计数值会“骗你”。所以互补滤波的思路就是:以陀螺仪积分为主,用加速度计的计算结果对积分结果做一个纠偏——陀螺仪短期可靠,加速度计长期可靠,两者一配合,就有了一个又灵敏又不漂的姿态估计。
具体到代码,Mahony算法分三步:
// 1. 从加速度计读数计算重力方向参考向量 // 2. 将当前四元数推算出的重力方向与参考向量做叉积,得到误差 // 3. 用PI控制器把误差补偿到陀螺仪角速度上,再更新四元数并归一化核心C代码简化如下:
void mahony_update(float gx, float gy, float gz, float ax, float ay, float az, float halfT) { float q0 = q0_, q1 = q1_, q2 = q2_, q3 = q3_; float vx, vy, vz; float ex, ey, ez; // 归一化加速度计数据 float norm = sqrtf(ax*ax + ay*ay + az*az); if (norm < 0.0001f) return; ax /= norm; ay /= norm; az /= norm; // 由四元数计算重力方向参考向量 vx = 2*(q1*q3 - q0*q2); vy = 2*(q0*q1 + q2*q3); vz = q0*q0 - q1*q1 - q2*q2 + q3*q3; // 叉积求误差 ex = ay*vz - az*vy; ey = az*vx - ax*vz; ez = ax*vy - ay*vx; // PI补偿,Kp和Ki需要实验调整 exInt += ex * Ki * halfT; eyInt += ey * Ki * halfT; ezInt += ez * Ki * halfT; gx += Kp*ex + exInt; gy += Kp*ey + eyInt; gz += Kp*ez + ezInt; // 四元数一阶积分更新 q0 += (-q1*gx - q2*gy - q3*gz) * halfT; q1 += ( q0*gx + q2*gz - q3*gy) * halfT; q2 += ( q0*gy - q1*gz + q3*gx) * halfT; q3 += ( q0*gz + q1*gy - q2*gx) * halfT; // 归一化四元数 norm = sqrtf(q0*q0 + q1*q1 + q2*q2 + q3*q3); q0_ = q0/norm; q1_ = q1/norm; q2_ = q2/norm; q3_ = q3/norm; // 转欧拉角 roll = atan2f(2*(q0_*q1_ + q2_*q3_), 1 - 2*(q1_*q1_ + q2_*q2_)); pitch = asinf(2*(q0_*q2_ - q1_*q3_)); yaw = atan2f(2*(q0_*q3_ + q1_*q2_), 1 - 2*(q2_*q2_ + q3_*q3_)); }这里halfT是采样周期的一半,如果姿态环运行频率是500Hz,则halfT = 0.001s。Kp和Ki的值对姿态响应影响非常大,常见初始值Kp=1.0左右,Ki=0.0~0.1,建议先用Kp=1、Ki=0跑,看姿态角度是否跟得上模拟运动,再逐步加大Ki消除稳态误差。
3.6 PID控制器与电机混控输出
姿态解算得到的是飞机的当前欧拉角(roll、pitch、yaw),PID控制器的目标就是让这个角度跟随遥控器给定的期望角度。
标准串级PID结构是:
外环(角度环):期望角度 - 当前角度 → 角速度期望 内环(角速度环):角速度期望 - 当前角速度 → 控制量为什么要串级?因为角速度环响应快,能抑制外部扰动(比如阵风、电机振动导致的角速度突变);角度环响应慢,保证飞机最终能稳定在期望姿态。如果你只做单级角度环,飞机会出现明显的“点头”或“飘”,很难调稳。
串级PID的代码示意:
void pid_update(attitude_t *att, remote_t *rc, float *motor_out) { float roll_rate_ref, pitch_rate_ref, yaw_rate_ref; // 角度环(外环) roll_rate_ref = pid_run(&pid_roll_angle, rc->roll - att->roll, 0.01f); pitch_rate_ref = pid_run(&pid_pitch_angle, rc->pitch - att->pitch, 0.01f); // 航向通道直接用摇杆作为偏航角速度参考 yaw_rate_ref = rc->yaw; // 角速度环(内环) float roll_out = pid_run(&pid_roll_rate, roll_rate_ref - att->gyro_x, 0.01f); float pitch_out = pid_run(&pid_pitch_rate, pitch_rate_ref - att->gyro_y, 0.01f); float yaw_out = pid_run(&pid_yaw_rate, yaw_rate_ref - att->gyro_z, 0.01f); // 电机混控 motor_out[0] = throttle + roll_out + pitch_out - yaw_out; // 右前 motor_out[1] = throttle - roll_out + pitch_out + yaw_out; // 左后 motor_out[2] = throttle + roll_out - pitch_out + yaw_out; // 右后 motor_out[3] = throttle - roll_out - pitch_out - yaw_out; // 左前 }注意电机混控的符号方向非常关键:如果某一轴的响应方向反了,飞机会直接侧翻甚至“翻跟头”。在真机测试之前,一定要做“电机方向测试”——用手固定机身,推油同时观察姿态修正方向是否与期望一致。如果你发现滚转方向反了,有两种修法:改电机混控矩阵的符号,或者改PID输出符号,总之要保证负反馈。
3.7 PCB布局实操:我的第二版布局经验
第二版PCB我做了大量优化,这里挑几个值得抄作业的点展开:
传感器位置:IMU芯片放在了板子正中央,而且用了两组固定孔对齐机架中心。安装时在IMU底部贴上3M减震海绵(约2mm厚),再锁紧螺丝。减震海绵不是必须的,但实测能显著减少高频振动在加速度计数据上产生的毛刺。注意不要贴太厚,否则减震变成了“晃动”,飞控反而更容易振荡。
射频天线区:nRF24L01+模块竖插在板边,天线伸出PCB边缘,周围没有任何铜皮和走线。这是针对2.4G天线净空区的要求——如果天线下方有铺铜或者走线,射频性能急剧下降,最直接的表现是遥控距离从几十米缩到几米。
LED和按键:板子四角放了四个LED指示各电机状态,旁边留了测试点,方便示波器挂探头。还加了一个轻触按键,用于切换飞行模式(自稳/定高/手动)。这些外围虽然简单,但在调试时非常有用——不用每次连串口看日志,扫一眼LED就能判断飞控卡在哪一步。
孔位尺寸:四角固定孔直径做到3.2mm,适配M3尼龙柱。不要追求极限小体积而用M2或者1.5mm孔,因为机架振动会导致螺丝松动,频繁拆装更容易滑牙,M3是比较稳妥的选择。
3.8 飞控代码烧录与调试环境
这套项目使用STM32标准外设库(Standard Peripheral Library)开发,配合Keil MDK编译。虽然ST已经主推HAL库了,但飞控这种对时序要求苛刻的应用,标准库直接操作寄存器更直观,生成的代码也更精简。如果你习惯了HAL库也没问题,只是要注意中断抢占优先级配置和DMA配置别被HAL库的默认机制干扰。
调试接口方面,我预留了SWD四线调试口(SWDIO、SWCLK、GND、3.3V),配合ST-Link V2使用。强烈建议飞控板上把调试口做成标准的1.27mm 4pin或2.54mm 4pin排针,不要省,因为你几乎肯定要在开发阶段无数次断点调试姿态解算。另一个重要调试手段是串口打印,在UART2上输出姿态角、遥控值、电池电压、PID输出等状态,配合匿名上位机或简单的PySerial脚本做曲线显示,调PID时非常省力。
我在实际调PID时的主要流程是:先把Kp、Ki、Kd全部置零,只给一个固定油门,看飞机是否能平稳起转;然后从小到大加角速度环的Kp,直到电机声音开始发闷且有抖动迹象,再回调20%;接着加角度环Kp,以飞机不会大幅度来回摆为界;最后加一点点D项抑制过冲。整个过程全程有人负责随时切油门断电,安全第一。这个调法虽然笨,但能稳扎稳打地完成初版飞控的飞行验证。
4. 常见问题与排查技巧实录
4.1 故障速查表
整理一份我在开发过程中高频遇到的故障、可能原因及排除方法,方便你按图索骥:
| 故障现象 | 可能原因 | 排查方法与解决建议 |
|---|---|---|
| 飞控上电LED不亮 | 电源焊接短路或降压模块损坏 | 先量电池端和5V端电压,再量3.3V端;依次断开外围模块确认短路点 |
| MPU6050数据一直读不到 | I2C地址错误或上拉电阻缺失 | 检查AD0引脚电平;用逻辑分析仪看I2C波形;用I2C扫描程序确认设备地址 |
| 电机全部不转 | 电调未做油门行程校准 | 油门推到最高→上电→听到提示音→油门拉到最低完成校准 |
| 单个电机不转 | 对应PWM引脚虚焊或MOS驱动异常 | 用示波器检查该路PWM是否有波形;互换两个电机的信号线判断是否为电机故障 |
| 起飞时侧翻 | 电机转向或旋桨方向装错 | 检查各电机旋转方向与螺旋桨正反装法;按“对锁”方式检查M1-M4布局 |
| 姿态角缓慢漂移 | 陀螺仪零偏未补偿或温度漂移 | 上电静止时采集1000次陀螺仪数据求平均作为零偏;运行时实时跟踪零偏更佳 |
| 遥控距离只有几米 | 射频天线净空区被铺铜遮挡 | 检查nRF24L01+天线区PCB铺铜和走线;改用外置天线模块或拉长天线净空区 |
| 飞行中出现“抽搐” | 振动耦合进IMU或PID增益过高 | 检查螺旋桨与电机动平衡;降低PID增益;给IMU加减震海绵 |
| 电池电压读取值偏大 | 分压电阻阻值精度不够 | 用万用表实测分压电阻,换算实际比例;改用1%精度电阻 |
4.2 一个印象深刻的排查案例:飞行中偶发“翻机”
这个案例,或者说这个坑,是我在整套飞控试飞阶段花费最多时间解决的,值得单独拿出来讲。现象是飞机悬停一两分钟后,偶尔会出现一个轴突然失去控制,飞机快速翻转落地。从概率上看,它只在上电后数分钟、电机达到一定温度时才出现。
最初怀疑是软件异常,比如某个数据溢出或者PID计算风评。于是我在飞控里加入了运行日志,每隔500ms记录一次姿态角、陀螺仪原始值、电机PWM值,掉电后通过串口读出来分析。翻机前的数据在末尾并没有明显异常,姿态角和PWM输出都在正常范围内。这就把问题指向了硬件层面。
后来用热成像仪检查飞控板时,发现电调PWM输出接口附近的3.3V LDO温度已经接近90℃。虽然AMS1117-3.3标称最大功耗可以承受,但当温度升高时其输出电阻变大、纹波增加,最终导致主控在某个瞬间进入掉电复位。掉电复位后IO处于高阻态,四个电调的PWM信号全变成长高/长低电平,电机瞬间满油门或停止,飞机自然就翻了。
解决措施有两点:一是把3.3V的供电来源从LDO改为低压差DCDC(MP1584EN的3.3V输出版本),电源效率更高发热更小;二是给每组电调PWM信号线加上100Ω的串联电阻作为缓冲,防止复位瞬间的异常电平灌到电调信号端。这个案例说明,飞控系统的稳定性是软硬件共同决定的,很多“软件BUG”最终查下来其实是电源或热问题。
4.3 飞控调试过程中必备的测量仪器
如果你准备长期折腾飞控,建议至少配备以下工具,不用一步到位买顶级设备,但核心工具不能省:
| 工具 | 用途 | 最低配置建议 |
|---|---|---|
| 数字万用表 | 电源电压测量、通断测试、电阻测量 | 三五十元的自动量程万用表即可 |
| 示波器 | 查看PWM波形、I2C时序、电源纹波 | 双通道100MHz带宽,二手品牌机也行 |
| 逻辑分析仪 | I2C/SPI/UART总线解码,排查通信问题 | 24MHz 8通道廉价逻辑分析仪 |
| 电烙铁 | 调试改板、更换器件 | 恒温焊台,最好带热风枪功能 |
| 稳压电源 | 电机测试、电调校准,限流保护 | 0-20V/0-10A可调电源 |
| 转速计/测振仪 | 电机动平衡测试 | 可用手机测振替代 |
其中最有价值的是示波器。飞控调试中很多诡异的“偶发故障”,本质上都是微秒级别的信号毛刺或时序异常,没有示波器根本看不到。还记得我之前说的I2C总线卡死问题,就是靠示波器抓SCL线低电平持续时间的异常波形才定位到的。如果没有条件买示波器,至少用逻辑分析仪,它能解码I2C、SPI、UART,对排查协议类问题帮助极大。
4.4 升级方向:让这套飞控飞得更远、更稳
项目目前可以稳定实现自稳飞行和定高飞行,但如果你想让它更适合航拍或长航时作业,有以下几个低成本且高收益的升级方向:
加装GPS模块:STM32F407的UART接口完全够接一个UBLOX NEO-M8N模块,实现GPS定点模式和返航功能。需要在代码中加入GPS数据解析(NMEA或UBX协议),然后再接一个简易的“位置环PID”,让飞机自动修正水平漂移。这一层实现完,你就拥有了一台自主悬停的四轴。
加装无线数传:在飞控上再挂一个2.4G或433MHz数传模块,把姿态、电压、GPS信息实时回传到地面站。这个功能对于长距离飞行和飞行后复盘分析很重要。nRF24L01+本身也可以做数传,但带宽和距离有限;如果你要地面站实时监控,建议用配套的433MHz数传模块。
优化姿态环频率和代码优化:当前姿态环跑500Hz,如果你换了更高性能主控(STM32F405/F7),可以把姿态环跑到1kHz,同时增加一个低通滤波器处理陀螺仪高频噪声。高频姿态环对电机异常响应的抑制效果更明显,飞起来的手感也会更“紧致”。
把代码从标准库迁移到RTOS:当功能变多之后(GPS、数传、日志、定高、避障等),裸机主循环会变得很臃肿,优先级控制也麻烦。如果对FreeRTOS有经验,可以尝试把传感器采集、姿态解算、控制输出放到高优先级任务,把日志、通信放到低优先级任务,整体架构会更清晰。
5. 写在最后:我的几点体会
整套STM32四轴飞行器和飞控遥控器做下来,最深的感受是:飞控不是单纯“写代码”能解决的,它强迫你把模拟电路、数字电路、嵌入式软件、自动控制原理这些知识在同一个项目里真正打通。你画原理图时对电源噪声的顾虑、Layout时对传感器干扰的考量,最终都会在飞控源码运行的稳定性上体现出来;反过来,PID调参过程中遇到的那种“硬件没问题但算法不收敛”的困惑,也会促使你回头去审视电路设计上的每一个细节。这套项目做到后期,乐趣已经不在“飞起来”本身,而在于你能准确预判——当我把某个滤波参数从0.1改成0.2时,飞机会有怎样的响应;当我把IMU从板子中心挪开2厘米时,振动频谱会有什么改变——这种掌控感是买现成飞控永远体会不到的。
如果你也想照着这条路走一遍,我最后再分享一个小建议:在首飞之前,务必先花半天时间做“安全放行检查”——遥控器摇杆校准、电调行程校准、电机转向测试、螺旋桨动平衡、电池电压检查、飞控固定螺丝紧固程度、GPS天线朝向等等,每一项都确认之后才允许自己推油门。四轴的项目可以慢慢做,但飞手的安全习惯必须一开始就养成。
本文还有配套的精品资源,点击获取