资讯详情

STM32四轴飞行器DIY全流程:从姿态解算到PID调参与炸机复盘

📅 2026/10/28 1:03:29 | 华诺云谱 👁 阅读
STM32四轴飞行器DIY全流程:从姿态解算到PID调参与炸机复盘
五月十九号北京一场四轴飞行器DIY工作坊我从早上九点焊到下午六点。三台机子飞起来两架有一架栽在了窗台上——这套基于STM32微控制器的自制四轴项目基本把圈子里能踩的坑都踩了一遍。如果你想自己动手做一台四轴飞行器而不是买个成品回来按遥控器这篇文章大概能替你省下好几周的弯路。先说清楚这不是一篇“照着做就能一键起飞”的教程而是一次完整项目的实战复盘。我会把从芯片选型、硬件接线、姿态解算到PID调参、真机试飞炸机的全流程串起来重点讲清楚每一步的“为什么”。四轴的核心从来不是焊得漂亮而是软件算法把传感器数据变成四个电机的正确转速——这一点想通了整个项目就成功了一半。1. 为什么是STM32F103C8T6一块能跑控制环的微型电脑1.1 选型逻辑从Arduino到STM32的升级很多刚入门的DIY玩家第一反应是用Arduino NANO或者UNO做飞控。不是说Arduino不行网上确实有Arduino跑通四轴的案例但用起来会很别扭AVR的16MHz主频跑姿态解算还行一旦要同时处理I2C传感器、4路PWM输出、串口遥测和遥控器PPM信号解码CPU占用率就逼近上限。而且Arduino的库封装了太多细节出了问题你既不知道是传感器数据坏了也不知道是定时器配置错了排查起来像在黑箱里摸。STM32F103C8T6就不一样。它本质是一颗72MHz的ARM Cortex-M3内核微控制器性能比ATmega328P高了将近四倍但板子价格反而不贵几块钱到十几块钱就能买到带最小系统的开发板。更重要的是它把“控制需要的所有外设”都集成了多路定时器输出PWM、硬件I2C、硬件UART、硬件SPI。这意味着一条信号线走的就是一个独立的硬件外设CPU只需要往寄存器里写一个值PWM信号就自动生成了——这才是飞控真正需要的架构。用一句话总结我选它的理由STM32不是“能用”而是“有余量”。飞控最怕的不是性能不够用而是性能刚好够用却没给调试留出空间的尴尬局面。1.2 核心资源盘点与配置F103C8T6这颗芯片的具体资源做四轴之前必须心里有数主频72MHz单周期乘法指令跑浮点需要软件模拟不过姿态解算频率控制在1kHz以内没问题。Flash64KB如果你只用标准库不跑FreeRTOS大约占用20~30KB如果加上FreeRTOS和调试协议也还够。SRAM20KB四元数解算的中间变量、PID控制器状态、串口FIFO缓冲总共用不到10KB。定时器TIM1是高级定时器能输出4路互补PWM还要带死区TIM2/TIM3/TIM4是通用定时器各有4路PWM捕获/比较通道。四轴需要4路独立的电机PWM一颗TIM1或者TIM2就能搞定。I2C硬件I2C接MPU6050速率400kHz够用。UART至少需要1路接蓝牙或数传模块做调试1路接遥控接收机解析PPM/串口协议。引脚分配这块我在工作坊里反复强调一个原则电机PWM输出尽量用同一个定时器的四个通道因为四路PWM必须严格同步更新这样四轴在急加速时四个电机的响应时间才一致。用不同定时器虽然也能飞但会出现微小相位差调PID的时候你会莫名其妙发现某些频率下有抖动很难排查。还有一个小细节F103C8T6是LQFP48封装引脚不多但所有GPIO几乎都能复用为定时器通道。我之前见过有人把电机PWM接到非复用引脚上然后用GPIO模拟PWM结果PWM频率一高CPU就喘不过气。正确做法是翻开芯片数据手册在“Pinout”表里精确查找你要的定时器通道对应的引脚号别想当然。2. 硬件搭建元件清单、供电方案和接线细节2.1 一套适合工作坊的硬件清单这套四轴定位为250mm轴距室内外兼顾的小机用的是空心杯电机加MOS驱动板不需要电调大幅降低了初学者焊接难度。具体清单如下飞控主控STM32F103C8T6最小系统板带8MHz晶振和3.3V稳压。传感器MPU6050六轴模块三轴加速度计三轴陀螺仪I2C接口板上最好自带滤波电容。机架250mm轴距四旋翼机架配5030桨直径5英寸螺距3英寸。工作坊统一用白色桨尖和黑色桨尖区分正反桨。电机8520空心杯电机7.4V额定电压配减速组或直驱。这类电机用在小型四轴上很合适电流小PID刚度也够。电机驱动四路NMOS驱动板或者自己用SI2302搭建四路半桥。注意空心杯电机是单方向旋转的不需要H桥。电池2S锂电池850mAh30C以上放电倍率。250mm轴距的小机用2S是能耗比最均衡的。稳压模块5V/1A BEC再经板载LDO到3.3V给STM32和MPU6050供电。遥控系统支持PPM或SBUS输出的接收机以及对应遥控器。其他杜邦线、热缩管、扎带、镍带、焊锡、螺丝铜柱若干。预算方面全套下来不含遥控器大约300到400元。比起商品飞控几百上千的价格这套装置最大的价值不是省钱而是你能在一根连线上理解“信号”和“电源”分开走的意义。2.2 供电拓扑为什么十个问题里有八个是电源问题四轴飞行器DIY最常见的问题不是代码跑不通而是供电设计不合理导致系统复位、传感器数值跳变、电机转速不稳。我在工作坊里贴了一张供电拓扑图——这里不用画图用文字描述清楚就行电池正极进来后先接一个总开关或XT60插头然后分成两路第一路直接接电机驱动板的正极由主控PWM信号控制MOS管通断给电机提供脉冲电流。第二路接BEC降压模块把电池电压降到5V再经过最小系统板上的LDO降到3.3V给STM32、MPU6050供电。这里有一个大多数人会忽略的关键点电机回路和逻辑回路之间必须有足够的电容隔离。空心杯电机启动瞬间的电流尖峰能达到几百毫安甚至安培级这个脉冲如果直接耦合进3.3VMCU的ADC参考电压就会抖动MPU6050的数据也会跟着抖。解决办法是在电机驱动板正负极之间并一个1000μF电解电容吸收电机启动尖峰。在BEC输出端并一个100μF钽电容和0.1μF陶瓷电容滤高频噪声。在MPU6050模块的VCC和GND之间加一个0.1μF陶瓷电容这个模块很敏感。我实测过不加电容时油门推过60%就会出现随机复位加了上面三个电容后同样的油门下系统纹波电压从400mV降到了80mV以内飞控再也没出现过无征兆重启。这事的教训很直接电源电路是飞控的地基焊完所有线之后先别急着上桨用示波器或者万用表测一下电压纹波比什么都管用。2.3 信号线接线与电机转向信号线接线相对简单但方向搞错会直接导致四轴在原地翻跟头。以X型四轴标准布局为例四路PWM输出和四个电机的对应关系如下电机位置旋转方向对应PWM通道左前M1逆时针TIM2_CH1右前M2顺时针TIM2_CH2左后M3顺时针TIM2_CH3右后M4逆时针TIM2_CH4注意对角线的电机转向相同相邻的电机转向相反。机架上会有正反桨反桨在逆时针旋转时产生向上的力。如果接反电机推力方向就反了飞机会直接横滚。接线时还有一个容易踩的坑把MOS驱动板的GND和STM32的GND接在同一根线束上。这根线我建议用粗一号的硅胶线因为电机电流和逻辑电流会汇合细线会有压降轻则导致逻辑误触发重则飞控掉电。信号线用普通的杜邦线就行但要和电源线分开捆扎避免电机大电流的电磁干扰通过线束感应进信号里——尤其PWM信号这种方波对噪声很敏感线束靠近电源线的部分最好加磁环或者绕几圈。3. 姿态解算MPU6050采集与四元数融合3.1 为什么不能直接拿加速度计角度去飞这是整个项目里最核心、最容易被新手忽略的一环。很多人第一次接触四轴看到网上有现成的姿态解算库就直接调用出了问题也不知道是库的问题还是自己接线的问题。我的做法是先把原理讲透再上代码。加速度计直接输出的是三个轴上的比力值含重力加速度分量理论上只要算出重力矢量在机体坐标系中的方向就能反推出俯仰角和横滚角。但实际飞行中机体的每一次加减速都会让加速度计读数混入运动加速度比如急加速时机身重心位移会额外产生一个明显的惯性加速度此时如果直接用加速度计算姿态角角度会在油门变化时抖动甚至反转。陀螺仪的角速度积分不存在这个问题但积分会随时间漂移时间越长误差越大。所以标准的做法是互补滤波加速度计提供低频稳定的绝对值有噪声但无漂移陀螺仪提供高频准确的角速度短时准确但积分会漂移两者通过一个系数α做融合得到既不漂移又不抖动的角度。工程上更扎实的做法是用四元数完成这个融合因为四元数能避免欧拉角的万向锁问题而且计算效率更高特别适合嵌入式实现。3.2 数据预处理零偏校准与低通滤波算法好不好用一半取决于数据干不干净。MPU6050刚上电时陀螺仪的零偏每个芯片都不一样温度变化也会导致零偏漂移。所以在上电后、飞行前我习惯做一次静态校准采集200个陀螺仪的原始角速度样本取平均值作为零偏值在每次读取原始数据后都减去这个零偏。这个操作必须放在循环之外只做一次否则无人机起飞后还在不停校准姿态就会漂。加速度计还需要做低通滤波。MPU6050自带数字低通滤波器DLPF配置寄存器就行了但实际用下来它的截止频率设置得再低机架震动产生的谐波还是会漏进来。所以我在代码里又加了一级一阶低通滤波系数选0.9#define ALPHA 0.9f float accelXf ALPHA * accelXf (1.0f - ALPHA) * rawAccelX / 16384.0f;注意这个滤波增加了相位延迟系数太大会让姿态角反应迟钝太小则滤不掉震动。0.9左右的小机机身硬度下体验比较折中供参考。3.3 四元数姿态解算的落地实现我这里选的是简单直观的互补滤波四元数方式。每读取一组加速度计和陀螺仪数据后执行一次融合更新频率大约为1kHz。核心逻辑如下用当前四元数估计重力加速度在机体坐标系中的方向向量。用加速度计实测值和估计值做叉积得到姿态误差。将这个误差作为修正量补偿到陀螺仪的角速度上。用修正后的角速度做四元数一阶积分更新。代码骨架大致是static float q01.0f, q10.0f, q20.0f, q30.0f; void mahony_update(float gx, float gy, float gz, float ax, float ay, float az) { float recipNorm; float halfvx, halfvy, halfvz; float halfex, halfey, halfez; float qa, qb, qc; // 归一化加速度计数据 recipNorm inv_sqrt(ax*ax ay*ay az*az); ax * recipNorm; ay * recipNorm; az * recipNorm; // 用当前姿态估计重力方向 halfvx q1*q3 - q0*q2; halfvy q0*q1 q2*q3; halfvz q0*q0 - 0.5f q3*q3; // 误差修正 实测与估计的叉积 halfex ay*halfvz - az*halfvy; halfey az*halfvx - ax*halfvz; halfez ax*halfvy - ay*halfvx; // 补偿到陀螺仪 gx Kp * halfex Ki * integralFBx; gy Kp * halfey Ki * integralFBy; gz Kp * halfez Ki * integralFBz; // 四元数微分更新 qa q0; qb q1; qc q2; q0 (-qb*gx - qc*gy - q3*gz) * dt; q1 ( qa*gx qc*gz - q3*gy) * dt; q2 ( qa*gy - qb*gz q3*gx) * dt; q3 ( qa*gz qb*gy - qc*gx) * dt; // 归一化四元数 recipNorm inv_sqrt(q0*q0 q1*q1 q2*q2 q3*q3); q0*recipNorm; q1*recipNorm; q2*recipNorm; q3*recipNorm; }Kp和Ki分别取0.5和0.1左右开始试。这个算法不算最优但胜在结构简单、代码量少初学者能逐行看懂调起来心里有底。实际飞行中这套解算出来的姿态角最大误差在2度以内对于DIY项目完全够用。网上现成的MPU6050 DMP库精度更高占用CPU也低但那是封装好的库一旦姿态异常你很难定位问题在哪里。我的建议是第一版飞机用自写解算跑通了再考虑换DMP或者更强的传感器。4. 控制环路串级PID怎么调才能稳4.1 内环角速度与外环角度两层环路各管什么四轴飞行器能稳定悬停靠的不是简单的姿态比例控制而是串级PID内环控制角速度也就是角速度误差外环控制角度。为什么需要两层用生活化类比外环角度环像“你看着方向盘想让车保持正对街角”内环角速度环像“你用手上的力气控制方向盘转动快慢”。光有角度环一旦机身受到扰动角度误差很大系统的修正力矩也很大飞机会像过冷的水龙头一样猛摆。有内环角速度环的限制修正力矩会被约束在一个合理范围内飞行手感稳重得多。具体到代码实现我的姿态控制循环大约如下// 期望角度来自遥控器roll_cmd, pitch_cmd // 当前姿态roll_est, pitch_est // 内环角速度gyro_x, gyro_y float err_roll_angle roll_cmd - roll_est; float err_roll_rate Kp_angle_roll * err_roll_angle; float err_roll_rate_cmd err_roll_rate - gyro_x; float roll_output Kp_rate_roll * err_roll_rate_cmd Ki_rate_roll * roll_integral;外环输出的是“期望角速度”作为内环的目标值内环再根据当前实际角速度算出最终的电机修正量。这样做的好处是只要内环参数合适外环参数的调节范围就被大大放宽了新手更容易调出一个能悬停的参数组合。4.2 调参顺序先内环后外环别倒过来现场调PID最怕的就是一上来就调外环结果飞机抖得跟筛糠一样你还找不出原因。我的方法固定为四步先把四轴绑在支架上或者用绳子挂在龙门架下确保它不能真的飞起来只能小幅摆动。先把外环增益调到很小的值比如角度误差输出角速度的系数Kp_angle给到3~4这是为了防止外环过度干预。先调内环Kp_rate。从小值开始比如0.5逐步增加直到你用手碰一下机身能感觉到阻尼明显增强、机身很快回正但还没有高频振荡。记住这个值这基本就是内环Kp的“甜点区”。再逐步增加外环Kp_angle直到悬停时姿态角误差变小、回正速度足够快。Ki的作用是消除稳态误差。如果悬停时机身持续向一侧偏光加Kp没用因为重力分量造成的常值误差需要积分来抵消。但积分项调太大又会引起低速振荡俗称“积分呼吸”。我给Ki的起始值通常是Kp的十分之一左右然后慢慢加。最后是微分项Kd。内环角速度环的微分项一般不太需要因为角速度本身已经是微分量了外环加D会放大噪声很多新手加上去反而头晕。我的做法是基础版本只用PI就能悬停Kd留到提高机动性时再调。4.3 混控分配四个电机怎么算转速姿态控制器算出来的roll_output、pitch_output、yaw_output是三个轴的修正力矩要变成四个电机的油门需要做混控。X型四轴的标准混控公式如下其中throttle是遥控器油门指令float m1 throttle roll_output - pitch_output yaw_output; float m2 throttle - roll_output - pitch_output - yaw_output; float m3 throttle - roll_output pitch_output yaw_output; float m4 throttle roll_output pitch_output - yaw_output;这里正负号代表滚转时电机转速差分量俯仰时前后电机差分量偏航时对角电机差分量。如果发现打副翼时飞机往反方向滚就把roll_output的两个正负号对调同理俯仰反向就调pitch的两个符号。这个符号问题现场调试时几乎人人都遇到过一次就能记住——都是看飞机往哪边翻再把符号反过来。输出到PWM寄存器前要给油门做限幅。空心杯电机的最低输出不能低于驱动MOS的导通阈值否则电机会出现爬行或者堵转。我通常把PWM占空比限制在13%到96%之间低于13%直接输出0高于96%截断。油门的下限为什么设这么高因为螺旋桨在低转速下几乎没有力但MOS管导通状态下电压太低反而会让电机持续发热不如直接关断。5. 软件架构从裸机轮询到FreeRTOS任务划分5.1 飞控要不要上操作系统这个问题老生常谈。小四轴的控制频率在1kHz左右姿态解算1kHz遥测50Hz纯裸机写一个超大while循环也可以跑代码量小调试直观适合第一版验证。但代码到了后期一定会膨胀你要加避障、加定高存储、加遥控器自动检测、加电池电压监测全都塞进一个主循环里稍不留神某个任务的延时就会拖累控制环的实时性。我的做法是第一版用裸机跑通了再迁移到FreeRTOS。FreeRTOS的好处不是多任务并行而是优先级隔离。把控制任务设为最高优先级1kHz精确触发姿测任务次之5ms周期遥测和数据记录任务再低一级50ms周期电机限位和按键扫描低到10ms。这样即使某个遥测任务因为串口阻塞卡了500μs也完全不影响控制环。具体任务划分参考如下控制任务1kHz读取PID控制器当前状态输出PWM混控。这个任务必须使用最高优先级避免被其他任务延迟。姿态任务200Hz读取MPU6050原始数据执行四元数融合更新姿态角。遥测任务20Hz通过串口把姿态、电机输出、电池电压发给上位机。监控任务10Hz检查电池电压和遥控器信号强度异常时执行关机或降落逻辑。5.2 传感器读取与PWM输出的时序控制FreeRTOS下最容易出现的坑是I2C读取MPU6050时有阻塞等待而I2C时序对中断响应要求很高如果不小心把I2C读取放在中断里或者恢复I2C的优先级低于控制任务就会时不时出现姿态数据跳变。这里给一个很实在的建议I2C读取MPU6050不要用阻塞式的库函数用DMA或中断方式。STM32的硬件I2C有自带FIFO和DMA请求配上DMA传输读一次传感器6个寄存器大约只要几十微秒而且不占用CPU。如果你用的标准库把I2C配置成快速模式然后按照时序读多字节配合一个简单的软件超时实测下来即使有偶发错误也不会让主控制任务卡死。传感器读取完成后紧跟着的就是控制环输出到PWM寄存器。PWM更新有一个小技巧先更新所有通道的CCR寄存器再统一刷新一次更新事件。这样四个电机能同时收到新的占空比飞机的动作才是连贯的。如果分步更新四个电机收到的信号有几十微秒的时间差在急推油门时就会感觉飞机“发飘”。5.3 串口调试协议不必复杂我见过很多DIY项目死在调试协议上——串口打印一大堆原始数据你根本分不清哪个数值对应哪个物理量。我的建议是设计一种极简的文本协议一行一个变量格式如下R,roll_est:12.3,roll_cmd:5.0,motor1:1500,motor2:1510用逗号分隔字段开头一个大写字母表示数据类型。上位机用任何串口助手或者Python脚本就能解析不需要复杂的二进制协议。调PID的时候把这个串口接到电脑上的蓝牙串口打开图模式看曲线比用眼睛盯着机架发呆高效十倍。如果要做高级一点可以加一个“参数回写”命令S,kp_rate_roll:0.8收到这条命令后飞控直接把参数写进Flash下次上电自动加载。这样你在调试现场就能用手机蓝牙调参不用每次都接USB线也不用重新编译烧录。这算是个小技巧但能极大缩短调试周期。6. 实测复盘飞行调试中的坑与对策6.1 第一次上电前要做的三件事真机试飞之前至少要做三件事能帮你省下一堆麻烦第一断电检查。确认螺旋桨的正反桨方向是否正确——正桨桨尖朝上且逆时针旋转时产生推力反桨相反。用手转动电机能感觉到明显的风力方向即可判断方向。不对的话把电机三线中的任意两根对调无刷电机或者换控信号极性空心杯电机。第二上电不装桨测试。电池接通后用遥控器慢慢推油门观察每个电机的转速是否按比例增长。此时可以用手机录像慢放因为电机转速变化太快肉眼很难看出细微差异。如果发现某一个电机转速明显跟不上优先检查这一路的PWM信号线和MOS驱动板GND是否接触良好。第三姿态自检。把机身水平放置观察串口输出的roll和pitch是否接近0倾斜机身角度是否跟随变化。如果不跟随检查MPU6050的安装方向和IMU坐标系是否与机体系一致。6.2 我遇到的几个典型故障这套项目过程中我们碰到了三类高频故障值得单独复盘第一个是MPU6050的I2C地址错误。某块板子上MPU6050模块暗含地址引脚选择默认地址0x68但有些模块把AD0引脚拉高了地址变成0x69。现象是程序能读取但数据全是0或固定值。解决方法是写一个扫描I2C总线上所有地址的函数看哪个地址有ACK响应然后强制用它。第二个是电机PWM频率过低导致的“啸叫”。空心杯电机适合8k到16kHz的PWM频率低于4kHz时电机会发出刺耳的电磁噪声同时力矩波动加大悬停时能看到机身轻微抖动。我们把TIM2的PWM频率设为12kHz后声音顺滑多了手感也更稳。第三个是“炸机”常客电池电压识别错误。2S电池满电是8.4V放电到7.0V就该报警降落了。有人把ADC采样电阻算错导致电压测量偏差0.5V结果电池低于7.2V还在大油门飞空心杯电机瞬间过热冒烟。解决办法是每次上电时都做一次电池电压校准先测实际电压再读ADC求出比例系数保存到Flash里。6.3 真机试飞的安全边界第一次试飞强烈建议在开阔的草地上并且把遥控器的油门曲线调软让50%以下的摇杆区域对应0到40%的动力输出也就是初期油门响应温和一点。或者更稳妥的做法是设置一个悬停油门限位先把飞机绑在支架上摸清悬停油门的位置再对应到遥控器75%的位置确保即使新手误操作也不会瞬间满油门冲出去。另外千万记住螺旋桨是危险品主轴转速上万转时塑料桨切到手指能直接见血。测试时全组成员站在飞机后方和侧面不要站在正前方。电池尽量使用防爆袋充电时必须有人看管——这是我能给到最实在的忠告。写在最后的个人体会整套项目从头到尾走下来我最深的感触是四轴飞行器DIY的难点不在某一个环节多高深而在于每个环节都会咬你一口——选型不合适、电源纹波大、姿态融合参数没调好、PWM和遥控协议冲突、真机测试又遇到意外。任何一个环节没处理好飞机就是不肯安安稳稳飞起来。对我来说STM32之所以是这类项目的绝配不在于它的性能压榨而在于它让你始终握有对系统的完全控制权。每一次传感器采样、每一次PID运算、每一次PWM更新你都能清楚知道发生了什么这种“看得见底”的感觉才是DIY最大的乐趣。希望这篇复盘能帮你少走些弯路也希望你第一架飞机第一次试飞就能平稳悬停。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑