简介:一份基于STM32微控制器的无人机飞控开发资料,面向嵌入式开发者和无人机爱好者,适合用于课程设计、项目参考或从零理解飞控系统构建。资料共212个文件,整体约4.53MB,以C源码(.c/.h)为核心,辅以Keil工程文件(.uvproj)、链接脚本(.sct)、编译中间文件(.o/.d)与调试映射文件(.map/.lst),并附有可直接烧录的hex固件,便于查看工程结构并跟踪完整编译调试流程。已有2427人学习下载。内容记录了STM32F4系列飞控的开发历程,覆盖陀螺仪/加速度计数据读取、AHRS姿态解算、PID控制环路、PWM电机驱动等关键模块;备份文件、日志和文本说明还保留了排错与调参痕迹。通过解析这份资料,可掌握飞控系统软硬件框架,也可为自主设计或移植到其他平台提供直接参考。
1. 用stm32做飞控到底在做什么:一句话说清边界
用STM32做飞控,最容易被误解成“把某个开源固件编译一下烧进去”。真正的自制飞控,是从读陀螺仪加速度计的原始数据开始,自己完成姿态解算、控制环、混控输出,最后让四轴稳定悬停。这个方向适合两类人:一类是想彻底搞懂无人机底层工作原理的嵌入式开发者,另一类是手头有STM32开发板想低成本验证控制算法的学生或爱好者。它的价值不在“能飞”这个结果,而在你亲手搭出的姿态感知与控制闭环,每一步都可调试、可复现。本文从芯片选型讲到姿态解算代码、PID控制结构和调参避坑,照着一路做下来,你会拥有一块自己写的飞控,而不是一块开源固件的搬运工。
2. 芯片与硬件方案:为什么选STM32,传感器怎么配
2.1 F103能不能做飞控:芯片选型先看这几个硬指标
很多人的第一个问题是“手头有块F103板子能不能做”。答案是能跑,但体验会很差。飞控的核心负载是姿态解算和控制环运算,F103是Cortex-M3内核,没有硬件浮点单元,float运算全靠软件模拟,一次四元数更新加PID运算要吃掉不少CPU时间。姿态解算固定周期1kHz时,CPU占用会到50%以上,留给传感器读取、PWM输出和日志记录的时间就很紧张。更麻烦的是,F103的ADC和定时器资源有限,接了四路PWM输出、串口调试、SPI读取IMU之后,外设冲突是常态。
我一般建议从带FPU的F4系列起步,比如F405或F407。它们自带硬件单精度浮点单元,同样跑1kHz解算,CPU占用可以压到10%以内,而且定时器数量多,四路电机PWM加一路蜂鸣器或LED遥控信号都够分配。F7系列性能更高,但价格和调试复杂度也上去了,对第一版飞控来说没必要。下面这张对比表是我常用的选型依据,核心指标不是主频,而是FPU有无、定时器数量、SPI口数量。
| 芯片 | 内核 | FPU | 定时器高级/通用 | 适用判断 |
|---|---|---|---|---|
| F103 | M3 | 无 | 2 / 4 | 只做简单自稳实验,不推荐 |
| F405 | M4 | 单精度 | 2 / 10 | 入门自制飞控的最优解 |
| F407 | M4 | 单精度 | 2 / 10 | 和F405差异不大,库存哪个用哪个 |
| F722 | M7 | 单精度 | 2 / 10+ | 需要跑更复杂滤波或日志时再上 |
选型时还要看一件事:IMU用SPI还是I2C接入。SPI读取速度快,可以稳定跑到1kHz甚至更高采样率;I2C虽然接线少两根,但速率上限和时序抖动会限制解算周期。F4系列的SPI支持DMA传输,用DMA读IMU数据可以做到“读数据不占CPU”,这是后面日志记录能跑起来的前提。所以选芯片时,至少留出两路SPI:一路给IMU,一路备用给外部Flash或第二个传感器。
2.2 传感器和最小硬件清单:从零搭起怎么配
飞控最核心的传感器是一颗六轴IMU,包含三轴陀螺仪和三轴加速度计。现在最常见的是MPU6000/MPU6050系列,两者传感器本质一样,区别是MPU6000支持SPI接口,MPU6050只有I2C,自制飞控务必选MPU6000,原因上面说了。如果后续要做航向锁定,再加一颗三轴磁力计,常用型号是HMC5883L或IST8310。气压计(如MS5611)用于定高,第一版可以不加。
传感器安装方向要统一。IMU的X轴对准机头方向,Z轴向下,这是一切姿态解算的前提。安装偏差哪怕三五度,悬停时飞控会认为机身是歪的,电机输出就持续不对称,表现出来就是“怎么调都往一边飘”。加速度计和陀螺仪的量程也要在初始化时确认:四轴飞行中角速度峰值一般不超过±500°/s,加速度不超过±4g,MPU6000默认量程偏保守,我一般把陀螺仪设为±500°/s、加速度计设为±4g,这样有效位数更充足,解算精度会好一些。
供电是另一个容易翻车的地方。飞控板上的MCU和传感器需要3.3V,接收机和电调信号需要5V,电调本身的BEC电源质量参差不齐。常见做法是:电调BEC输出5V后,先过一颗低压差稳压器降到3.3V给MCU和IMU供电,5V和地线再单独走一组给接收机。供电顺序也很关键,IMU的模拟电源和数字电源最好都从同一颗LDO引出,如果用两路电源,陀螺仪数据会出现周期性跳变,排查起来非常痛苦。
最小硬件清单我列一份参考:一块STM32F405核心板、一颗MPU6000模块、一颗HMC5883L模块、四只电调和四只电机、一块碳纤维或玻纤机架、一块锂聚合物电池和分电板。PCB打样不是第一版必须的,先用核心板加杜邦线把逻辑跑通,等控制算法稳定再做一体化板子。飞控的软件调试工作量远大于硬件,别在第一版硬件上花太多钱。
3. 姿态解算:从原始数据到四元数
3.1 陀螺仪积分漂移与加速度计校准:为什么非要用互补滤波
姿态解算要回答的问题是:这一刻机头朝哪、机身倾了多少。陀螺仪输出的是角速度,把它对时间积分就能得到角度,但积分会把零偏也积累进去,陀螺仪只要有一点常值漂移,几秒后姿态就会慢慢转起来,这就是静止放置时角度仍在缓慢变化的来源。加速度计则相反,它测量的是比力,静止时能精确给出重力方向,机体的振动和加减速又会污染它的读数,所以高频段完全不可信。
互补滤波的思路就是让两者各管一段:角速度负责高频姿态变化,加速度计负责低频校正。数学上说,把加速度计解出的参考方向与陀螺仪积分出的方向做误差,用PI调节器把误差反馈到陀螺仪角速度上,修正积分的漂移。只要PI参数和机架振动匹配得当,静态漂移可以控制到每分钟一两度以内,这对自稳飞行完全够用。
姿态的表示也要选对。欧拉角直观,但存在万向节死锁问题,俯仰角接近90度时偏航和横滚会耦合,控制上会出现奇异点。四元数没有这个问题,表达姿态只需四个数,更新也只是一次乘法。所以正规做法是:内部全部用四元数解算,只在调试输出时把四元数转成欧拉角给人看。旋转矩阵也可以,但计算量稍大,在F405上没必要。
3.2 Mahony互补滤波的实现代码:核心函数与参数
下面这段是我常用的Mahony互补滤波实现,输入是IMU读出的原始角速度和加速度,输出是代表机体现在姿态的四元数。代码结构是标准C,依赖一个简单的向量叉乘和归一化函数。
typedef struct { float q0, q1, q2, q3; /* 四元数,q0为标量部分 */ float twoKp; /* 比例增益,决定加速度计校正的响应速度 */ float twoKi; /* 积分增益,消除陀螺仪常值零偏 */ float integralFBx, integralFBy, integralFBz; /* 积分误差累积 */ } mahony_t; void mahony_update(mahony_t *f, float gx, float gy, float gz, /* 陀螺仪,单位rad/s */ float ax, float ay, float az) /* 加速度计,单位g */ { float norm; float halfex, halfey, halfez; /* 参考方向与实际方向之间的误差 */ float q0q0 = f->q0 * f->q0, q0q1 = f->q0 * f->q1; float q0q2 = f->q0 * f->q2, q1q1 = f->q1 * f->q1; float q1q3 = f->q1 * f->q3, q2q2 = f->q2 * f->q2; float q2q3 = f->q2 * f->q3, q3q3 = f->q3 * f->q3; /* 加速度计输出归一化,消除模长不一致的影响 */ norm = sqrtf(ax*ax + ay*ay + az*az); if (norm < 1e-6f) return; norm = 1.0f / norm; ax *= norm; ay *= norm; az *= norm; /* 用当前四元数算出重力参考方向,再与实测加速度叉乘得到方向误差 */ halfex = (ay * (2.0f*(q1q3 - q0q2)) - az * (2.0f*(q2q3 + q0q1))); halfey = (az * (2.0f*(q1q2 + q0q3)) - ax * (2.0f*(q1q3 - q0q2))); halfez = (ax * (2.0f*(q2q3 + q0q1)) - ay * (2.0f*(q1q2 - q0q3))); /* 积分误差累加,用于修正陀螺仪零偏 */ f->integralFBx += f->twoKi * halfex; f->integralFBy += f->twoKi * halfey; f->integralFBz += f->twoKi * halfez; /* 把误差反馈到角速度上,同时补偿量程和单位差异 */ gx += f->twoKp * halfex + f->integralFBx; gy += f->twoKp * halfey + f->integralFBy; gz += f->twoKp * halfez + f->integralFBz; /* 一阶龙格库塔积分更新四元数,步长就是调用周期 */ f->q0 += (-f->q1*gx - f->q2*gy - f->q3*gz) * 0.0005f; f->q1 += ( f->q0*gx + f->q2*gz - f->q3*gy) * 0.0005f; f->q2 += ( f->q0*gy - f->q1*gz + f->q3*gx) * 0.0005f; f->q3 += ( f->q0*gz + f->q1*gy - f->q2*gx) * 0.0005f; /* 归一化,防止浮点累积误差把四元数模长拉偏 */ norm = sqrtf(f->q0*f->q0 + f->q1*f->q1 + f->q2*f->q2 + f->q3*f->q3); norm = 1.0f / norm; f->q0 *= norm; f->q1 *= norm; f->q2 *= norm; f->q3 *= norm; }代码里的0.0005f对应2kHz解算周期,dt/2 = 1/2000/2。如果主循环跑在1kHz,改成0.0005f为0.00025f即可,这个系数直接影响积分步长,写错了姿态更新速度会对不上真实运动。twoKp的典型起始值是1.0f,twoKi从0.05f起步。Kp过小,姿态校正缓慢,飞起来会“软”;Kp过大,加速度计的振动噪声会直接灌进姿态,表现是悬停时机身高频颤动。
验证解算是否正常有一个土办法:飞控静止平放,串口打印四元数转出的俯仰角和横滚角,角度应该在0度附近抖动,幅度不超过±1度;然后用手快速翻转机架90度,打印值应该立即跟随。如果静止时角度匀速变化,说明twoKi太大或陀螺仪零偏没校准;如果翻转后回不到0度,优先检查加速度计方向符号是否与陀螺仪一致。
4. 控制环搭建与PID调参:从角速度环到可飞
4.1 串级PID为什么是标配:姿态环与角速度环的分工
四轴没有自稳能力,全靠四个电机的转速差维持姿态,所以控制环是飞控的命根子。自制飞控普遍采用串级PID结构:外环是姿态环,输入是目标姿态角和当前姿态角,输出是目标角速度;内环是角速度环,输入是外环给出的目标角速度和陀螺仪测得的实际角速度,输出是电机修正量。角速度环频率高,姿态环频率可以低一些,常见配置是姿态环100~200Hz,角速度环1kHz。
为什么必须串级而不是只用一个角度环?因为角度误差变化太慢,直接驱动电机,遇到阵风或外力扰动时修正滞后,飞机会“荡”。角速度环直接反应机身转动快慢,对扰动更敏感,能把外界的干扰在早期就压下去。简单说,姿态环管“角度对不对”,角速度环管“转得快不快”,两者缺一不可。
实际操作中,角速度环的参数占了调参工作量的八成。它的P项决定对旋转的抵抗力度,I项负责消除悬停时的常值偏差,D项抑制振荡。姿态环反而简单,一般只用P,甚至有人直接把姿态环P设置成常数不调。常见的一个现象是:新手拼命调外环觉得“不够力”,其实问题是内环P太小,给多少角速度指令电机都不够狠,换外环根本没解决。
4.2 代码骨架:控制环的C实现
控制环的代码骨架如下,PID结构体可以复用,积分项加了限幅,微分项加了低通,这两个细节在实际飞行中能避免不少“炸机玄学”。
typedef struct { float kp, ki, kd; float integral, last_error, derivative; float integral_limit, d_lpf; /* 积分限幅和微分低通系数 */ } pid_ctrl_t; /* 更新一次PID,dt为调用周期,单位秒 */ float pid_update(pid_ctrl_t *pid, float target, float feedback, float dt) { float error = target - feedback; float output; /* 积分项累加并限幅,防止长时间偏差导致积分饱和 */ pid->integral += error * dt; if (pid->integral > pid->integral_limit) pid->integral = pid->integral_limit; if (pid->integral < -pid->integral_limit) pid->integral = -pid->integral_limit; /* 微分项带低通,滤掉高频噪声,避免D项放大震动 */ pid->derivative = (1.0f - pid->d_lpf) * (error - pid->last_error) / dt + pid->d_lpf * pid->derivative; pid->last_error = error; output = pid->kp * error + pid->ki * pid->integral + pid->kd * pid->derivative; return output; } /* 角速度环调用示例,1kHz周期 */ float rate_pitch_out = pid_update(&rate_pitch_pid, attitude_pitch_target_rate, gyro_pitch, 0.001f);角速度环的输出还要过一次混控矩阵,把三个轴的修正量分配到四个电机上。四轴X型机架的混控公式是固定几何关系:每个电机的输出 = 油门基础值 + 对应轴的修正量加减组合。这个矩阵如果符号写错,飞机会在解锁瞬间直接翻滚。建议在装机后先不装桨,用手转动机架观察电机转速变化来验证混控方向,而不是直接上电试飞。转速变化方向对了,再装桨。
integral_limit我一般设在油门最大值的15%左右,太大飞机抬头回不来,太小悬停会有静态误差。d_lpf取0.1~0.2,太低D项无法发挥作用,太高会把震动直接放大成电机啸叫。这些参数不是经验玄学,它们对应的是电机响应带宽和机架共振频率的折中。
4.3 调参顺序:先自稳后调试,参数与现象对照
调参顺序比参数本身更重要。我的习惯是:先做电调油门行程校准,再做角速度环P,然后角速度环I,接着姿态环P,最后才考虑D。油门行程校准是第一步,让所有电调知道油门信号的最小值和最大值范围,否则四颗电机对同一油门值的转速响应不一致,飞控再怎么调都是歪的。
具体校准流程是:飞控上电不接电池,把油门信号调到最高,再给电调上电,电调发出确认音后把油门信号拉到最低,电调保存行程。每颗电调都要单独做一遍。做过和没做过的区别,在低油门推杆时非常明显:没校准的飞机会出现某个电机先转、某个后转,起飞瞬间机架明显歪斜。
角速度环的P项调整方法是:把飞机固定牢,用手快速掰动机身一个轴,观察电机响应是否有力且不振荡。P从小往大加,直到机身出现轻微的高频抖动,再退回30%。P太小,掰动机身时感觉电机“犹豫”;P过大,固定状态下能听到电机高频鸣叫。I项在P项稳定后加,作用是悬停时消除缓慢的漂移,加到悬停时机身不再缓慢低头或侧偏即可,过大会引起低频振荡,表现为飞机有节奏地点头。
姿态环P一般直接给一个中等值,比如对手动自稳模式,俯仰和横滚的角速度目标与姿态误差的比值设为8~10,再根据手感微调。调参时的安全底线是:所有试验都在桨叶朝外、周围没人没物的环境中进行,第一次解锁推油油门不超过30%,飞机姿态异常立即切油门。这块的“翻车”代价是旋翼打手或机架损坏,不值得用身体去验证。
5. 用stm32做飞控的几个经典踩坑与排查路径
5.1 上电复位与电机响应异常
现象:接上电池后飞控反复重启,OLED或调试串口刚亮又灭;或者推油门时某个电机转速忽快忽慢,像是接触不良。排查到最后往往是同一个根源:供电质量。电调BEC的峰值电流不足,电机启动瞬间把5V电压拉低,MCU欠压复位。我遇到的情况是四个电调共用一个BEC,解锁后四个电机同时启动,电压跌到4.2V左右,飞控立刻重启。解决方法是换用独立的高电流BEC(至少3A)给飞控供电,或者用电调信号端单独供电的方式隔离。检查方法也简单,用示波器看飞控电源引脚电压,推油门瞬间压降超过0.3V就要处理。
5.2 静止时姿态缓慢漂移
现象:飞控水平静止放置在桌面上,调试界面里俯仰角从0度开始匀速往一个方向偏,每分钟偏好几度。原因基本是陀螺仪零偏没补偿,或者Mahony的twoKi参数太小,积分修正跟不上漂移速度。解决方法是先读陀螺仪静止时的平均值,把它作为零偏在每次初始化时减去,再配合twoKi从0.05往上调。注意零偏会随温度变化,开机静止采集几秒再做零偏校准,比写死一个固定值可靠得多。
5.3 解锁推油就翻机
现象:解锁后轻轻推油门,飞机不是垂直上升,而是往某个方向快速翻转,甚至在地面就“跳”起来。这个错误的排查顺序是固定的:先看螺旋桨方向,再看电机转向,最后看混控符号。螺旋桨装反是最常见的低级错误,正反桨的区分是看桨叶边缘的导角方向,这个装错没有任何软件可以补救。电机转向用眼睛看就可以确认:解锁电机低速旋转,机架左侧电机顺时针还是逆时针要严格匹配。混控符号错误则需要查看电机输出表,把油门基础值设为固定值,然后单独给某个轴一个正的修正量,观察对应电机转速是否按预期方向变化。三个步骤做完,翻机问题能排除九成。
5.4 悬停时机身高频抖动
现象:飞起来能稳住,但机身在某一两个轴上有肉眼可见的快速抖动,伴随电机高频噪音,像手机震动一样。这个现象通常来自两个方向:姿态解算把机体振动当成了真实加速度,或者角速度环D项放大噪声。先从机架入手,用软性减震垫固定飞控板,把高频振动和IMU隔开;然后检查陀螺仪低通滤波器截止频率,400Hz的原始数据可以先降到80Hz左右试试;最后再往回调D项参数,因为D项对噪声最敏感。这条排查路径顺序很重要,先改软件后改硬件是很多人的习惯,但震动源头在机械结构时,软件再调也压不住。
5.5 飞行中油门响应有明显迟滞
现象:推杆后飞机反应慢半拍,松杆后还会继续飘一段距离才回正。原因是角速度环带宽不够,常见于内环P调得太保守。很多人因为害怕高频抖动,把P设得非常小,结果角度环给的目标角速度电机根本追不上,整个系统反应迟钝。正确做法是先用前面说过的方法把内环P推到临界抖动再退回30%,保证带宽足够,再回头调外环。如果P推到临界抖动时机架噪音已经很大,则说明减震和低通处理不到位,回到上面一条继续优化机械结构。
6. 黑匣子日志:让每一次坠机都有据可查
6.1 日志回放的基本做法
把PID中间量写到外置Flash或SD卡,坠机后取出来回放,是排查疑难问题最有效的技巧。记录项不用多:姿态角误差、角速度误差、PID三项输出、最终电机指令就够了,采样率可以降到250Hz,比全速记录数据量小得多,也够分析用。回放时重点看坠机前最后0.5秒,如果电机指令已经疯狂振荡,问题在控制环;如果电机指令平稳但姿态突然变化,问题在传感器或外力。
6.2 验证参数修改
一个更有用的习惯是:每次调参只改一个参数,并在日志里记录参数值,然后试飞。很多“越调越差”的情况是同时动了三个参数,出问题后根本不知道是哪一步改坏的。日志回放配合单参数修改,可以把调参从感觉驱动变成数据驱动。固定姿态误差下,可以对比修改前后油门响应曲线,判断是P不够还是I太慢。
往后走,如果想让飞控更稳,在两个方向上投入性价比最高:一是加磁力计做航向锁定,用航向误差修正偏航漂移;二是加气压计做定高,把油门控制从手动改成高度环。这两个方向都会让你的飞控复杂度上一个台阶,但底层的姿态解算和控制环框架不用推翻重来。
做完这些,我自己的感受是:飞控这东西,第一次飞到稳定悬停时确实兴奋,但真正值钱的是后面每一次坠机后查日志、找原因的过程。硬件和代码都会有意外,唯独排查问题的方法是积累下来的。希望帮到你,按上面的顺序走,少走几条弯路。
本文还有配套的精品资源,点击获取