简介基于STM32的四轴飞行器控制系统完整工程包面向无人机爱好者、嵌入式学习者和相关专业毕业设计学生。资料涵盖硬件原理图与PCB、Keil工程源码、设计报告覆盖从传感器采集、姿态解算到电机PWM控制的关键链路可帮助理解STM32外设配置、MPU6050数据处理、PID调参以及FreeRTOS任务调度等实用技能。包体共368个文件约11.51MB以h/c源码、uvproj/uvopt工程配置、schdoc/pcbdoc硬件设计、hex/axf固件输出和doc报告文档为主并保留bak备份便于对比修改。源码包含飞控主程序与USB通信两个工程前者侧重飞控主逻辑后者可作调试通信参考。已有775人学习或下载适合直接参考工程结构和代码进行二次开发也可作为课程设计、竞赛项目的起步模板尤其对STM32外设配置、互补滤波/卡尔曼滤波、遥控通信和电源保护方案有直观的示例价值。1. STM32四轴飞行器控制系统从能转起来到能飞起来的完整链路做毕业设计拿到“基于STM32的四轴飞行器控制系统硬件、源码、设计报告.zip”这种资源包很多人以为解压、编译、烧录就能飞了。真上手过的人心里清楚四轴最难的不是写控制算法而是让姿态解算、PID输出、PWM波形和电机响应这几层在同一块板子上协同工作。硬件、源码、设计报告这三件套正好对应这个系统从选型接线、调代码到把设计逻辑讲明白的完整闭环。这篇笔记写给做嵌入式硬件或者飞控方向的开发者也写给拿到例程却始终调不出手感的新手按照硬件选型、源码结构、参数调试、故障排查这条主线把常见做法和实际踩坑一起说透。下面不会讲太多高深理论全部围绕怎么在桌面上跑通并验证一个能自稳的四轴控制系统展开。2. 硬件选型与系统拆解主控、传感器、电机驱动的分工2.1 主控选型为什么STM32F103C8T6是这类项目的性价比核心四轴飞行器的控制系统本质上是一个以姿态为被控对象的实时闭环系统。主控芯片负责周期读取惯性传感器数据、解算姿态、运行PID控制律然后通过PWM输出驱动电机。这类项目中STM32F103C8T6几乎是默认选项72MHz主频、20KB RAM、64KB Flash跑姿态解算加PID加日志缓冲绰绰有余外设资源也够用定时器、I2C、USART都有。价格便宜、资料极多尤其是基于STM32的毕业设计案例网上能找到大量可参考的工程这只芯片的内核、时钟树、外设库对新手来说最容易查到答案。选型时可以横向对比一下更高级的F405或F427。F4系列主频高、带硬件浮点单元算四元数和PID更快DMP数据解算也轻松但工程模板和例程量不如F103多调试门槛也高一些。从控制系统设计的角度看F103的浮点运算用软件模拟也能跑得过来主循环1kHz节拍下完全够用。对一个毕设级别的四轴来说F103的瓶颈从来不在算力而在引脚分配和电源设计。实际分配外设时我习惯这样排TIM3用作电机PWM四路输出PA6、PA7、PB0、PB1TIM2用作遥控器信号输入捕获PA0、PA1、PA2、PA3I2C1接MPU6050PB6、PB7USART1 接串口调试或数传。这个分配方式避开了TIM1高级定时器的死区配置问题普通定时器输出PWM不需要做刹车和互补输出初始化少踩一个坑。引脚清单如下表。外设引脚用途TIM3_CH1~CH4PA6 / PA7 / PB0 / PB1四路电机PWM输出TIM2_CH1~CH4PA0 / PA1 / PA2 / PA3遥控器油门/航向等信号输入捕获I2C1PB6(SCL) / PB7(SDA)读取MPU6050姿态数据USART1PA9(TX) / PA10(RX)串口调试与日志输出用户LEDPC13解锁/解锁状态指示2.2 传感器与执行机构MPU6050、电调、电机的接线与硬件匹配姿态检测部分MPU6050是这类项目的标准配置六轴数据三轴加速度计加三轴陀螺仪能覆盖四轴飞控的最基本需求。接线时除了I2C数据线特别要注意AD0引脚的电平决定器件地址一般接GND地址为0x68如果和别的I2C器件共用总线改AD0电平区分地址即可。常见问题是SCL和SDA接反或者漏接上拉电阻导致通信不稳定后面避坑章节再展开。电机执行机构分两大流派空心杯电机加MOS管驱动或者无刷电机加电子调速器电调。毕设和室内小四轴几乎都用空心杯方案成本低、没桨也能安全测试直接PWM驱动MOS管就能转真正户外飞的大四轴全用无刷加电调电调接收50到500Hz的PWM信号控制逻辑更接近航模标准。两套方案的差异如下表。项目空心杯 MOS管无刷 电调驱动方式低端MOS管开关PWM直驱电调解释PWM脉宽内部换相供电常见1S或2S锂电池常见3S或4S锂电电流更大调试安全小电流可手持测试大电流必须固定机身测试典型场景室内小四轴、毕设演示户外航拍、开源飞控套件如果是无刷方案电调的信号线要接在TIM3的PWM输出脚上电调GND必须和主控GND连在一起。很多硬件调试翻车现场就出现在这一根地线上——电调端的功率地回流大电流如果信号地线走线太细或者绕了远路主控的地电位会被抬高导致传感器数据乱跳。建议功率地和信号地在电源入口处单点汇合不要在主控板中间形成地环路。2.3 电源与地线布局电机启动瞬间压降是硬件调试的第一道坎飞控系统里最容易让新手翻车的就是供电设计。四个电机同时启动时电流是稳态的2到3倍如果主控板和电机驱动共用一根细杜邦线取电电压会被瞬间拉到3V以下STM32直接复位串口调试器断开。解决思路是分区供电电池正极经过总开关后分成两路一路给稳压模块比如AMS1117或MP2359输出3.3V供主控和传感器另一路直接粗线到电机驱动或电调。共地但功率路径尽量短而粗。稳压模块的输出端建议并一个10uF和0.1uF陶瓷电容输入端并100uF到470uF电解电容。电解电容离电机驱动越近越好作用是把电机启动瞬间的尖峰吸收掉0.1uF的小电容负责滤除高频噪声。主控板上的MPU6050电源脚也各加一个0.1uF去耦电容否则电调PWM信号线上的毛刺容易通过电源耦合进I2C数据。电池选型也不能只看容量。电机堵转或者油门推太快时劣质电池内阻大电压跌落严重飞控会进入低压复位循环。我一般会先测空载电压和上电瞬间电压跌落幅度跌落超过0.5V就换更大C数的电池。这也是一个很容易被忽略但非常影响控制系统实际表现的硬件细节。3. 源码结构从姿态解算到PWM输出的主循环3.1 主循环框架定时器节拍决定控制系统的时间基准飞控源码的整体结构可以分成四层传感器驱动层、姿态解算层、控制律层、输出层。传感器驱动层负责从MPU6050读原始数据姿态解算层把加速度计和陀螺仪数据融合成横滚角和俯仰角控制律层跑PID输出层把PID计算出的电机控制量转成PWM占空比。这四层都在一个固定的时基下周期执行最常见的做法是用定时器中断产生1ms节拍也就是1kHz控制频率。中断里只做传感器读取、姿态解算和PID计算串口打印、遥控器解码放主循环轮询。原因很实际串口打印是阻塞操作航模的PWM信号脉宽也只有1到2ms如果都在中断里处理万一某个函数耗时超过1ms下一个中断进来就产生了抖动姿态解算的时间基准就乱了。中断优先级设置上负责控制节拍的定时器中断优先级要高于接收串口数据时的USART中断。// 定时器中断服务函数1ms节拍控制循环核心 void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); mpu6050_read_raw(accel_raw, gyro_raw); // 1. 读取传感器原始数据 attitude_update(accel_raw, gyro_raw, 0.001f); // 2. 姿态解算dt1ms pid_controller(roll_pid, target_roll, attitude.roll, gyro.roll, 0.001f); pid_controller(pitch_pid, target_pitch, attitude.pitch, gyro.pitch, 0.001f); motor_output_apply(); // 4. 更新四路PWM占空比 } }这段代码把控制循环的主体逻辑压缩成了四个函数调用。mpu6050_read_raw负责从I2C读取加速度计和陀螺仪的原始值读出来的数据直接给姿态解算attitude_update内部完成互补滤波或四元数更新pid_controller对横滚和俯仰两个轴分别独立运行motor_output_apply把PID输出量映射到TIM3的四个比较寄存器上。参数0.001f是函数里要用的控制周期dt这个值必须和定时器配置的1ms严格一致。3.2 姿态解算互补滤波的权重怎么设DMP要不要用姿态解算是控制系统第一个关键环节。加速度计在静态时能直接算出横滚和俯仰角但动态下振动噪声大陀螺仪短时间积分很准但长时间积分会漂移。互补滤波的做法是用加速度计的结果修正陀螺仪积分的长期漂移公式只有一行却解决了大部分情况下的姿态估计问题。// 互补滤波一阶形式权重由系数alpha控制 #define ALPHA 0.98f void attitude_update(Vector3f accel, Vector3f gyro, float dt) { accel_angle_roll atan2f(accel.y, accel.z) * 57.29578f; accel_angle_pitch atan2f(-accel.x, accel.z) * 57.29578f; gyro_integral_roll gyro.x * dt; gyro_integral_pitch gyro.y * dt; attitude.roll ALPHA * (attitude.roll gyro.x * dt) (1.0f - ALPHA) * accel_angle_roll; attitude.pitch ALPHA * (attitude.pitch gyro.y * dt) (1.0f - ALPHA) * accel_angle_pitch; attitude.yaw gyro.z * dt; }ALPHA取0.98意味着陀螺仪积分占主导加速度计只以2%的权重做慢速修正。这个值对应的物理含义是陀螺仪的高频响应完全保留加速度计的噪声被大幅衰减同时长期漂移会被慢慢拉回来。如果传感器安装面有振动把ALPHA降到0.95会让姿态更平滑但代价是响应变迟钝。如果发现自稳时机身来回抽动优先怀疑这个系数太大而不是PID参数。关于MPU6050自带的DMP很多例程直接调用官方DMP库输出四元数再转成欧拉角优点是主控不需要写滤波算法。但在我看来毕设项目里手写互补滤波更合适代码量小、原理透明、答辩时能讲清楚每个系数的作用。DMP库本身也是黑匣子出了问题很难排查。当然如果目标是快速跑通而不是吃透原理DMP能省下不少调试时间。3.3 PID控制器角度环与角速度环的串级结构四轴自稳控制系统常用的不是单环PID而是角度环加角速度环的串级结构。外环是角度环输入是期望横滚角/俯仰角输出是期望角速度内环是角速度环输入是外环输出的角速度目标输出直接叠加到电机PWM上。内环的存在非常关键它首先让飞行器具备“对抗外力扰动”的能力外环才负责控制回中。如果只用单环角度PID飞行器抗风扰的能力会很弱。typedef struct { float kp, ki, kd; float integral; float last_error; } PidObject; // 串级PID输入目标角度输出电机修正量 float pid_controller(PidObject *pid, float target, float current, float rate, float dt) { float error target - current; pid-integral error * dt; if (pid-integral 100.0f) pid-integral 100.0f; if (pid-integral -100.0f) pid-integral -100.0f; float derivative -rate; // 角速度作为微分项比差分误差更平滑 float output pid-kp * error pid-ki * pid-integral pid-kd * derivative; return output; }注意这里的微分项直接用了陀螺仪的角速度而不是对误差求差分。这样做有两个好处角速度数据本身就反映了误差变化趋势而且比差分误差平滑得多不会放大传感器噪声。实际使用时每个轴各建两个PID对象一个角度环一个角速度环。角度环的P参数通常给2到5角速度环的P参数给10到30具体值跟机架重量和电机力效有关需要实测调整。积分限幅设成100是防止长时间误差积累导致积分饱和时输出过冲。3.4 PWM生成TIM3输出电机控制信号的配置要点电机控制量最终通过PWM占空比施加到电机上。STM32的定时器输出PWM时关键参数是PWM频率和分析值分辨率。对电调方案来说PWM频率一般取200到400Hz比较合适直接用MOS管驱动空心杯电机时频率可以高到10kHz以上。我习惯把PWM频率设在400Hz兼顾电调兼容性和电机响应速度。// TIM3初始化四路PWM频率400HzARR999PSC179 void motor_pwm_init(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tim; TIM_OCInitTypeDef oc; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; gpio.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_Init(GPIOA, gpio); gpio.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_Init(GPIOB, gpio); tim.TIM_Prescaler 180 - 1; // 72MHz / 180 400kHz tim.TIM_Period 1000 - 1; // 400kHz / 1000 400Hz tim.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, tim); oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_Pulse 0; // 初始占空比0电机不转 oc.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM3, oc); TIM_OC2Init(TIM3, oc); TIM_OC3Init(TIM3, oc); TIM_OC4Init(TIM3, oc); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); }预分频PSC设为180减1得到400kHz计数时钟ARR设为1000减1最终PWM频率是400kHz除以1000等于400Hz。分析值范围是0到999占空比可以直接用TIM_SetCompare1(TIM3, throttle_value)设置。占空比和电机转速的关系因电调而异无刷电调通常把1000到2000us脉宽映射到0到100油门400Hz周期对应2500us周期所以需要把油门量换算成脉宽再映射到CCR值。空心杯方案则简单很多直接按百分比设占空比。四路PWM共用一个定时器四个通道同步更新这比分开用多个定时器要可靠。4. PID参数调试先内环后外环的调参顺序与手感确认4.1 调参前的准备日志输出与安全固定PID参数不能盲调第一步是让控制系统的状态可见。最简单的做法是从USART1把姿态角、角速度、PID输出组成一个结构体格式化打印出来接到串口助手或者虚拟示波器上。即使没有上位机也能从一串串十六进制数据里看出姿态响应的大致趋势。串口波特率设115200足够每10ms打一帧不要每1ms都打否则串口会成为系统瓶颈。调试现场要把机架牢固固定。我习惯用一根粗扎带把机身绑在桌子边缘机头朝前能自由旋转但无法翻滚。测试时先不装桨只观察电机转速变化和姿态角是否跟随遥控器指令。装桨之后第一次推油门人一定要站在侧面而不是正前方这个习惯能避免很多事故。4.2 先调内环角速度环P值、D值的可观察现象串级PID必须先调内环。内环角速度环的P值决定了系统对外力扰动响应的快慢。把外环角度环的P暂时设0手动给一个很小的角速度目标观察电机响应。P值从小到大慢慢加当机身出现明显的高频抖动时说明P已经偏大退回大约70%作为起始值。角速度环的D值用来抑制抖动解决“P加大就抖、P减小就软”的矛盾。D值过大的现象是电机声音发闷、机身发硬用手掰动时阻力感很强但响应迟缓。D值的调整要配合P值交替进行先P到微抖再加D消除微抖然后继续加P。这个循环重复两三轮就能找到当前机架下内环的甜点。4.3 再调外环角度环自稳模式下的手感判断内环调好后把外环角度环的P从0开始加上去测试目标是把机身掰到一边松手姿态角能平滑回到0附近不震荡、不超调。外环P过小时机身回中缓慢像没有自稳过大时回中后会在目标位置来回摆动摆动的频率一般比内环抖动低很多肉眼可见。外环的I通常在悬停时才会体现价值。如果飞机朝某个方向缓慢漂移说明加速度计修正存在稳态误差这时外环加一点I能把漂移拉回来。I太大会导致姿态角每隔几秒抖动一下俗称“波浪状晃动”。Kd外环一般不开外环输出的是角速度目标值再给它做微分就相当于对目标角速度求导数值不稳定反而引入噪声。调参过程中的现象和参数方向如下表。参数作用调大现象调小现象内环Kp角速度响应速度高频抖动、电机发烫手感松软、抗风差内环Kd抑制角速度震荡机身发硬、响应迟钝抖动回不来了内环Ki消除角速度静差低频振荡悬停缓慢漂移外环Kp角度回中力度回中后摆动回中慢、自稳弱4.4 采样频率与PID频率的匹配一个常被忽略的控制系统参数控制系统的三个频率——姿态解算频率、PID运算频率、PWM输出频率——不少人以为越慢越稳实际上它们各有边界。姿态解算至少要做到500Hz以上否则四轴在一个控制周期内会转动好几个角度控制量严重滞后PID频率和姿态率保持一致1kHz最简单PWM输出频率反而可以低到400Hz因为电机和螺旋桨的机械响应速度远低于电调更新率。频率匹配出现问题的典型症状是把1kHz姿态解算配到50Hz的PWM输出上电机对PID输出的响应会明显滞后导致自稳时不断过冲然后修正来回抽动。判断方法很简单打印PID输出和姿态角波形如果PID输出已经收敛姿态角还在缓慢追目标多半是执行机构响应跟不上控制频率。这时先把PWM频率提高到400Hz以上再回来看PID参数是否还需要重调。5. 避坑排查电机不转、起飞翻车、数据漂移的5个常见问题5.1 四个电机不转或抖动飞控没有反应现象推油门时电机只抖动不旋转或者完全没有反应串口里PID输出正常变化但电机不跟进。原因第一种可能是电调行程未校准电调不知道油门下边界在哪里无法解锁第二种是PWM频率设置过低压到电调的识别范围之外第三种是解锁条件没满足比如油门没有在最低点停留足够时间程序里解锁逻辑不执行。解决无刷电调做行程校准动作是“油门拉到最高上电听到提示音后拉到最低”每一路电调分开校准最保险。用示波器或者串口打印确认TIM3的PWM频率是否为400Hz左右占空比是否随油门变化。解锁逻辑上常见做法是要求油门通道低于5%持续2秒以上串口打印解锁状态机当前处于哪一步。5.2 一推油门就翻转向、桨叶与PID极性现象油门超过一半后四轴不是上升而是快速朝某个方向翻倒切断油门后掉在地上。原因四个电机的转向组合不对对角电机必须同向旋转相邻电机反向如果有一个电机转向反了机身就会产生绕某条对角线旋转的力矩螺旋桨装反也是同类问题正反桨标识搞混或者装错位置升力方向不匹配还有一个隐蔽原因是PID输出极性反了飞控检测到向右倾斜反而让右侧电机加速越修正越偏。解决先不装桨用胶带在机身上做方向标记逐个点动电机确认旋转方向。X型机架的常见布局是左前顺时针、右前逆时针、左后逆时针、右后顺时针。确认转向后装桨必须和转向匹配顺时针电机装右侧标识的桨R桨逆时针电机装左侧标识的桨L桨。PID极性检测方法手持机身向右倾斜观察右侧电机转速应该下降、左侧上升如果反了把PID系数取反即可。5.3 MPU6050读不到数据或姿态角漂移现象串口全是0xFFFFFFFFI2C通信失败或者姿态角能读出来但十几秒后会缓慢飘走。原因I2C线上拉电阻缺失是通信失败的最常见原因SCL和SDA接反也会导致完全读不到姿态漂移多半是互补滤波权重设置不对或者陀螺仪数据没做滤波直接积分。解决先确认MPU6050地址是0x68还是0x69。STM32F103的硬件I2C在部分工程里容易卡死很多项目直接改成软件I2C模拟时序反而省心。漂移问题则要把原始加速度计角度和互补滤波输出的角度同时打印出来对比如果原始角度正常而滤波输出迟迟不跟随降低ALPHA系数如果原始角度本身就有偏移检查加速度计安装是否水平、电源噪声是否过大。5.4 电池电压跌落导致飞控复位现象推油门瞬间飞控和接收机同时重启电脑端串口断开LED灯熄灭又亮起。这种现象和控制系统调参无关纯粹是硬件问题。原因功率路径和信号路径共用细线取电四个电机启动电流把电池电压拉到单片机复位阈值以下。另一个常见原因是电池内阻过大新电池能过、稍微放一会电就复位。解决功率线换粗线22AWG或更粗主控板单独从电池正极经稳压模块取电电源入口并联470uF电解电容吸收瞬态跌落检查每个焊点特别是电机驱动MOS管的源极和地线连接处冷焊点在大电流下会产生额外压降。5.5 自稳模式下机身快速抽动滤波系数与频率匹配问题现象不推油门切换到自稳模式后电机忽快忽慢机身像哮喘一样抖动姿态角波形上有明显的高频毛刺。原因机架振动通过加速度计传进姿态解算互补滤波系数太靠近1就完全没滤掉振动控制信号被振动调制如果PWM频率偏低电机对PID输出的高频修正无法及时响应也会表现为抽动。解决检查MPU6050的安装是否有缓冲垫没有就加一层ALPHA从0.98降到0.95观察抽动是否减弱同时用示波器看电机PWM波形是否有毛刺排除MOS管栅极电阻过大导致的开关噪声。最后再回到PID参数内环KP微调降低10%往往能让抽动消失。6. 串口波形验证与设计报告的关键章节6.1 用串口把姿态数据打印出来跑一个悬停测试验证控制系统的最终手段不是看它飞起来多炫而是看它在固定油门下的姿态收敛过程。把飞机放在平整桌面上推油门让电机转起来但机身不脱离地面用手轻轻掰动机身再松手串口里记录的姿态角应该经历“偏差-回中-轻微超调-再次回中”的收敛过程整个过程不超过1秒且不震荡。// 串口打印姿态角与PID输出用于波形观察 void debug_send_frame(void) { uint8_t buf[18]; buf[0] 0xAA; buf[1] 0x01; int16_t roll_angle (int16_t)(attitude.roll * 100); int16_t pitch_angle (int16_t)(attitude.pitch * 100); int16_t roll_out (int16_t)(roll_pid_output * 100); memcpy(buf 2, roll_angle, 2); memcpy(buf 4, pitch_angle, 2); memcpy(buf 6, roll_out, 2); uint8_t sum 0; for (int i 0; i 8; i) sum buf[i]; buf[8] sum; uart_send(buf, 9); }这里用9字节固定长度帧发送前两个字节是帧头中间是姿态角和PID输出最后是校验和。接收端用虚拟示波器或者匿名上位机解析能看到姿态角曲线和PID输出曲线的相对关系。我自己的习惯是每次调完参数都录一段这样的波形存档方便后续改动出问题时对比回滚。6.2 一份能说明问题的设计报告怎么组织拿到这类资源包的人往往最后才写设计报告实际上报告应该在调参之前就搭好框架。报告的核心不是堆截图而是把问题讲清楚为什么选这个传感器、互补滤波系数有什么依据、PID参数为什么是这样一组值。建议按照系统总体设计、硬件选型与电路设计、软件流程与姿态解算算法、PID参数整定过程、测试数据与异常分析这个顺序来写。测试数据部分直接放上面这种串口波形截图比放一百字描述更有说服力。我第一版做飞控时连烧两块主控板后来发现是电机启动瞬间把稳压模块拉崩从那以后任何一次硬件改动都先用串口确认数据再上电机。这个习惯让后续所有控制系统调试都顺利了很多。希望这篇笔记能帮你把这个方向走通。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?