STM32四旋翼源码实战:从MPU6050姿态解算到串级PID调参
2026/9/15 12:45:42 网站建设 项目流程

简介:面向STM32平台的四旋翼无人机开发资料,包含完整源码工程、文档说明与制作总结,适合高校电子、自动化、计算机等专业学生用于毕业设计、课程设计、期末大作业,也适合嵌入式爱好者自学飞控与传感融合技术。源码均经过本地编译验证,可运行性有保障;据作者说明,该项目评审分达到98分,难度适中,内容经助教审定,覆盖硬件驱动、姿态解算、控制逻辑等关键环节,可作为从零搭建四旋翼项目的参考蓝本。资源包为ZIP压缩格式,约20.45MB,便于整体下载解压。目前已有244人浏览学习。相比零散教程,这份资料可直接对照代码与文档开展二次开发,制作总结则呈现项目拆解与实现思路,能帮助读者快速理解飞控系统架构、节省排错时间,尤其适合需在较短时间内完成课程任务或毕业设计开题的开发者。

1. 让STM32四旋翼源码从“能跑”变成“会飞”:先理清姿态环和角速度环的分工

看到这个标题,第一反应不是马上把源码烧进板子,而是先想清楚一个四旋翼到底需要哪几个闭环。MPU6050持续输出加速度和角速度,STM32定时器产生四路PWM去推电调,遥控器给出期望姿态,而中间把“期望”变成四路“转速差”的,是姿态解算和串级PID。没有这条链路,源码就算编译通过,上电后也只会原地打转或直接翻机。这篇文章把STM32四旋翼从选型、接线、姿态解算到调参排错串一遍,适合毕业设计要交源码和文档的人,也适合手上有一块F103核心板想低成本飞起来的人。源码怎么组织、参数怎么设、日志怎么读,下面按实际做项目的顺序讲。

2. STM32四旋翼的硬件选型与接口分配:从MPU6050到电调的信号通路

2.1 控制板选型:STM32F103C8T6够不够用

做四旋翼毕业设计时,最常见的开发环境是Keil MDK或STM32CubeIDE配合标准外设库或HAL库建工程。芯片选择上,STM32F103C8T6(72MHz主频、64KB Flash、20KB SRAM)就能支撑基本功能:姿态解算跑DMP或互补滤波、串级PID运算、四路PWM输出、串口日志和遥控接收解析。不需要引入实时操作系统,裸机主循环配合定时器中断就够,这样源码部分更容易说明白,答辩时也讲得清爽。

如果你手头是F407或F411,主频更高,可以顺手把姿态解算频率从100Hz提到200Hz,但最核心的串级PID参数并不因芯片变快而需要大改。选型核心依据是外设资源:需要1个I2C接传感器、1个四通道定时器输出PWM、至少1个USART打日志,以及若干GPIO接状态灯和遥控信号捕获。这些接口F103C8T6全都够,而且市面上最小系统板便宜、资料多,做毕业设计出问题容易查。

2.2 接口分配表:一条完整的信号通路

下面这张引脚分配表是我在类似项目里比较常用的方案,把PWM、I2C、串口和遥控捕获错开,避免引脚冲突。

模块通信方式STM32引脚说明
MPU6050I2C1PB6 (SCL) / PB7 (SDA)加速度+角速度,I2C上拉4.7kΩ
电调1(右前)PWM定时器2PA0 (TIM2_CH1)刷新率500Hz,脉宽1000us~2000us
电调2(左后)PWM定时器2PA1 (TIM2_CH2)同上
电调3(左前)PWM定时器2PA2 (TIM2_CH3)同上
电调4(右后)PWM定时器2PA3 (TIM2_CH4)同上
USART1日志串口PA9 (TX) / PA10 (RX)115200-8-N-1,输出调试曲线
遥控PPM输入定时器1输入捕获PA8 (TIM1_CH1)解析油门、俯仰、滚转、偏航
状态LEDGPIO输出PC13自检失败时快闪,解锁后常亮

电调信号线的地必须和STM32的GND共地,这是整机最容易出问题的点。很多飞起来一加油门就重启,就是因为电调大电流回流时信号地电位被拉偏。I2C在线束较长时建议把MPU6050的I2C时钟设为100kHz,400kHz虽然在手册范围内,但电机启停瞬间的电流变化容易造成通信错误。

2.3 用STM32CubeMX生成初始化工程的几个必改参数

在CubeMX里选好芯片后,依次配置RCC、I2C、TIM2和USART。RCC的HSE设为Crystal/Ceramic Resonator,按你板子上的晶振设置倍频到72MHz;I2C1打开后把速度设为Standard Mode或Fast Mode均可,地址选自动生成;TIM2选择PWM Generation,四个通道的模式都置为PWM1,预分频设为71,得到1MHz计数频率,自动重载值ARR设为1999,这样PWM频率为500Hz。这里有一个常见误区:直接把PWM频率设成50Hz去模拟普通舵机刷新率,现代无刷电调在500Hz下响应更跟手,悬停修正也更细腻。

USART1使能后设置波特率115200,最后打开TIM2的更新中断,这个中断是后面100Hz控制律的节拍来源。下面这段是CubeMX生成工程后,我在main.c里补的PWM输出设置函数:

// 四路电机PWM写寄存器,参数单位是计数器的比较值 void motor_set_pwm(uint16_t ch1, uint16_t ch2, uint16_t ch3, uint16_t ch4) { TIM2->CCR1 = ch1; // 右前电机 TIM2->CCR2 = ch2; // 左后电机 TIM2->CCR3 = ch3; // 左前电机 TIM2->CCR4 = ch4; // 右后电机 }

这里的参数单位是计数器的比较值,对应高电平时间。由于预分频后计数频率为1MHz,所以写1000代表1000us,写2000代表2000us。这是电调都认的高电平时间区间,解锁前四路必须都设为1000,解锁后缓慢升到1050us作为怠速,最大不要超过1900us,留一点余量避免满油门时控制量叠加后顶到限幅。

2.4 电调和遥控接收机部分的常见做法

常见的做法是遥控接收机输出PPM信号,一根信号线上串行携带四个通道数据。把接收机PPM脚接到PA8后,用TIM1的输入捕获模式测相邻两次上升沿的间隔,就能解析出油门、俯仰、滚转和偏航。每个通道的脉宽范围是1000us到2000us,中位1500us,和电调PWM的脉宽概念一致。2.4G无线模块通常只做透传,不参与控制律运算。

接线完成后先做一次电调油门行程校准。拔掉螺旋桨,解锁后把油门拉到最高,电调上电后听到确认音,再把油门拉到最低,等待自检音结束。需要提示的是,电调的校准顺序和品牌有关,别跳过这一步直接上桨试飞。上电自检时,程序要在主循环里检查MPU6050的ID是否正确、遥控器油门是否低于1050us,条件不满足就让状态LED快闪,这时候禁止任何PWM输出大于1000us。

提示:所有接线和校准操作都要在拔掉螺旋桨的状态下完成,保护手指是第一位的。

3. 四旋翼姿态解算与串级PID控制:STM32定时器、中断和PWM的配合

3.1 姿态解算的两个选择:DMP还是互补滤波

MPU6050里能直接读到的原始数据只有加速度和角速度,必须经过解算才能得到俯仰、滚转和偏航角。DMP方案直接读取芯片内部姿态解算后的四元数,STM32通过I2C读出四元数再转欧拉角,代码量小;互补滤波方案则是自己把角速度积分,再用加速度计修正漂移。两者都能满足悬停需求,但毕业设计文档里一定要写清楚自己用了哪一种,答辩时被问到“姿态角是怎么来的”才能答得利落。

DMP对供电纹波比较敏感,电调大油门时I2C数据偶发错误,程序里要做好超时和重试,读取失败时保留上一帧姿态而不是跳出控制循环。互补滤波不需要额外加载固件,也更方便讲清楚原理,但需要自己对三轴分别调权重。我一般会用DMP方案省时间,代码结构更接近“读四元数—转欧拉角—PID”三件事,出问题时容易定位。无论选哪种,角速度的符号约定要和欧拉角定义保持一致,否则PID输出方向反了,飞机会直接向地面加速。

3.2 串级PID:外环角度环、内环角速度环的分工

四旋翼控制不直接用单环PID,是因为角速度环响应快、角度环响应慢,串起来以后内环给系统增加阻尼,外环保证姿态收敛。外环以期望角度和当前角度之差作为输入,输出期望角速度;内环以期望角速度和IMU测得的实际角速度之差作为输入,输出期望修正力矩,再映射到PWM增量。

// 姿态控制任务,以100Hz频率执行,dt取固定值0.01f void attitude_control(float dt) { // 外环角度环:期望俯仰角pitch_cmd与当前pitch_angle的偏差 float pitch_err = pitch_cmd - pitch_angle; float pitch_rate_cmd = pid_angle_calc(&pid_pitch_angle, pitch_err, dt); // 内环角速度环:期望角速度与陀螺仪y轴角速度的偏差 float pitch_rate_err = pitch_rate_cmd - gyro_y; float pitch_output = pid_rate_calc(&pid_pitch_rate, pitch_rate_err, dt); // 输出量映射到PWM增量,油门基础值加上修正值 motor_pwm[FRONT_RIGHT] = throttle_common + pitch_output; motor_pwm[BACK_LEFT] = throttle_common - pitch_output; // 滚转、偏航按同一思路叠加到对应电机 }

外环比例系数一般给3到6,内环比例系数给0.08到0.15,这个比例差是因为外环输出的期望角速度是角度误差的放大结果,内环需要快速收敛。积分项在外环通常不用,内环积分不要开太大,否则悬停时机身会有低频振荡。PID输出必须限幅,内环限幅值直接决定最大修正力矩,限幅太小跟不上姿态突变,限幅太大又会在打杆回中时过冲。

3.3 TIM2更新中断:100Hz控制律怎么让每个周期严格对齐

主循环while(1)里不能直接做控制律,因为MPU6050读取和串口打印都会造成时间抖动,PID的微分项按不稳定的时间间隔计算,参数标定全乱。CubeMX里打开TIM2更新中断后,在中断服务函数里置位控制标志,主循环检测到标志再执行控制律:

volatile uint8_t ctrl_tick = 0; void TIM2_IRQHandler(void) { if (TIM2->SR & TIM_SR_UIF) { TIM2->SR = 0; ctrl_tick = 1; // 置位100Hz控制标志 } } int main(void) { while (1) { if (ctrl_tick) { ctrl_tick = 0; mpu_read_and_update(); // 读取MPU6050并更新姿态 attitude_control(0.01f); // 100Hz串级PID计算 motor_update_pwm(); // 将motor_pwm写入CCR寄存器 } } }

中断标志位必须在判断后立即清零,ctrl_tick用volatile修饰,防止编译器优化掉主循环里的检测。控制周期固定10ms,PID微分项要除以0.01而不是按主循环实际运行时间算,否则在不同代码路径下PID参数会漂移。串口打印放在控制任务之外,或者用DMA传送,避免占住CPU导致控制周期抖动。

3.4 源码里容易出问题的三个细节

第一,MPU6050的角速度原始值需要按量程换算,不同库的缩放系数不一样,代码注释里要写明用的是±2000dps还是±500dps。第二,四路PWM在解锁前必须固定为1000us,很多源码里有“PWM输出最小值校验”的函数,这个校验必须放在遥控解锁判断之前执行。第三,I2C读取要加超时和重试机制,否则静电或电机启停会让总线卡死,整机表现为控制律还在跑但姿态不再更新。这三个问题在毕业设计源码检查里是最常被挑出来的点,提前处理好省很多答辩麻烦。

4. 四旋翼源码的工程组织与飞行调试:从串口日志到解锁逻辑的排错闭环

4.1 源码模块怎么分:driver、algorithm、task三层

毕业设计源码最怕一个main.c写完,看起来能飞,答辩时讲不清。常见做法是分三层:driver层包括mpu6050.c、pwm.c、uart.c,只做寄存器读写,不处理控制逻辑;algorithm层包括attitude.c、pid.c、remote.c,输入原始数据,输出控制量;task层在main.c里维护调度循环,决定100Hz控制律、50Hz遥控刷新、10Hz日志输出。

这样分层有一个直接好处:换传感器型号或换电调,只需要改driver层,PID和姿态解算不用动。文档章节跟着这层结构走,从“硬件驱动”到“控制算法”再到“任务调度”,逻辑和代码一致,看起来工作量也清晰多了。在代码里用#ifdef CTRL_DEBUG包住调试输出,编译时通过宏切换,不占用正常飞行时的串口带宽。

4.2 遥控解锁与安全保护:解锁条件不是一个flag那么简单

解锁逻辑至少要满足三个条件同时成立:油门低于最低阈值、姿态角小于15度、解锁成功后电调输出从低脉宽缓慢升到怠速。如果只判断一个遥控器拨杆位,飞行中误触就会导致电机全速运转。常用的实现如下:

uint8_t check_arm_condition(void) { // throttle为接收机解析出的原始PWM值,非控制量 if (throttle < 1050 && fabs(roll_angle) < 15.0f && fabs(pitch_angle) < 15.0f) return 1; return 0; } void update_motor_idle(void) { static uint16_t idle_pwm = 1000; if (idle_pwm < 1050) idle_pwm += 2; // 每10ms加2us,缓慢升到怠速 for (int i = 0; i < 4; i++) motor_pwm[i] = idle_pwm; }

注意throttle是从接收机解析出来的原始PWM脉宽,不是PID算出来的油门叠加量。源码里如果直接把PID输出加到四路PWM上,先检查有没有先叠加throttle_common,漏掉这个,解锁瞬间电机会直接抽动。解锁状态下持续检测油门是否低于950us超过1秒,如果是则自动上锁,电机立刻回到1000us。

4.3 串口日志就是飞行记录仪

调试时所有现象都要能对应到日志。用一个调试端口输出逗号分隔的文本:时间戳、欧拉角、角速度、PWM输出、遥控通道和当前状态,格式固定,后面画曲线不用再改解析脚本。起飞前拔掉螺旋桨做一次手持强制姿态测试,用手把机架倾斜30度,观察日志里角度误差和电机PWM是否反方向变化。日志输出示例:

// 按100Hz控制循环同步打印,串口波特率115200 printf("%u,%d,%d,%d,%d,%d\r\n", (uint32_t)(millis()), (int)(roll_angle * 10), // 乘10保存一位小数,避免浮点打印 (int)(gyro_y * 10), motor_pwm[0], // 右前电机PWM比较值 throttle_cmd, // 当前遥控油门 (int)ctrl_state); // 0=上锁 1=解锁 2=失控保护

嵌入式printf默认不启用浮点打印,把角度乘10转成整数发送,既省CPU又避免链接浮点库导致Flash不足。把日志存成CSV后,用Excel或Python脚本画曲线。第一次试飞前至少做一小时的桌面调试:电调不上桨,遥控器逐步加满油门,观察四路PWM最大值是否都在1900us以内。如果有一路先到顶而另外三路还没跟上,说明PID输出叠加时符号没对齐,这是调试中常见的坑。

4.4 PID调参的第一步不是调大P,而是确定内环响应够快

先单独给内环加期望角速度阶跃:遥控器俯仰打杆拉到最大,记录日志里角速度到达目标值的时间,通常60ms以内算正常。内环P调到在日志里出现轻微振荡,再往回退20%。之后才动外环,外环先设很小的P值,确认角度不发散再加。整机试飞时不要从大油门开始,先把油门限制在30%以内做贴地飞行,观察有无低频点头或抖振。

如果有现成PID参数,不要直接抄,要看它对应的是多少轴距、什么桨叶、多大电池电压。同是F103平台,450机架和穿越机机架的惯量差很多,同一组PID参数在其中一个机架上稳定,在另一个机架上可能剧烈振荡。记录每次试飞的三要素:机架型号、电压、PID参数,同一组参数至少飞两个起落再动,避免每次只改一个数却连着改了其他条件。

5. 毕业设计文档与制作总结的整理思路:从源码到答辩材料的闭环

5.1 文档结构照“硬件—算法—测试”三段写

标题里的“文档说明”和“制作总结”不是在最后一周补的,而是飞行之前就要开始积累。文档说明建议按硬件设计、算法设计、系统测试四部分组织,软件部分与源码三层结构对应。制作总结不要写成“遇到了很多困难最后做出来了”,而是用数据说话:飞了多长时间、几个架次、炸机原因、修复方式。可以贴一张飞行测试记录表,表头列清楚架次、状态、现象和日志定位,一张表就能把调试工作量交代清楚。

架次状态现象日志定位
01电机启转右前电机不转I2C中断占用过长,PWM更新被阻塞
02解锁失败解锁指示灯闪烁油门最小值未到达接收机校准值
03悬停低频前后漂移外环P过小,俯仰跟随滞后约200ms

5.2 答辩时把“日志曲线”做成亮点

答辩时用串口曲线截图比贴代码更直观。把一次悬停过程的俯仰角、角速度、PWM输出画在同一张图里,指出角度误差最大出现在油门突变后的200ms处,内环角速度峰值先于角度误差峰值,说明串级PID的内外环相位关系是对的。这条曲线能同时证明传感器、控制律、执行器三个链路都在正常工作,比念一段“实现了飞行控制”有说服力得多。

整理制作总结时,只写自己改过的、调过的、修过的内容。从MPU6050的I2C超时处理到电调行程校准的教训,每一条都对应一个具体现象和解决手段,这种小结里最忌讳出现“代码写好了、硬件再调一调、四轴飞起来了”这种没有信息量的表述。飞行日志件数、PID参数迭代记录、结构件改动位置,都是答辩时容易被追问的高分素材。

5.3 源码交付前做一次干净工程检查

删除调试用的临时宏、未用函数和大量被注释的代码,用Keil或STM32CubeIDE重新编译一次,确认没有警告;确认代码里所有涉及油门和PWM的变量要么是无符号数要么符号一致。最后在源码根目录放一个README,写清硬件型号、引脚分配、编译步骤、烧录方式和串口波特率。烧录时用ST-Link V2配合STM32 ST-LINK Utility烧写hex或bin,上电后IMU自检通过指示灯常亮,才算交付完成的检查闭环。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询