基于STM32F4的四轴飞行器控制系统设计与实现详解 📅 发布时间:2026/9/11 21:58:05 👁 浏览次数: 简介基于STM32F4的四轴飞行器控制系统完整工程与设计文档面向嵌入式开发者、无人机爱好者和电子设计竞赛学生解决从硬件电路搭建到飞控算法落地的闭环参考需求适合作为课程设计、毕业设计或二次开发的基础资料。资源共368个文件压缩包大小11.51MB其中h、c源文件构成飞控与USB双工程代码主体uvproj工程文件可导入Keil或STM32CubeIDE直接编译schdoc与pcbdoc为原理图和PCB设计源文件hex、axf固件便于烧录测试doc设计说明则提供文字版原理讲解与调试指导。目前已有562人学习浏览。通过该资料可系统了解STM32F4与惯性测量单元陀螺仪、加速度计、磁力计的接口设计、PWM电机驱动电路以及姿态解算、PID控制、FreeRTOS实时任务调度等核心代码实现设计说明还详述了器件选型原因、传感器校准方法、组装调试流程和安全操作注意事项可帮助学习者快速掌握四轴飞行器控制系统的工程化开发方法。1. 基于STM32F4的四轴飞行器控制系统该怎么落地第一次让四轴离地悬停的人都会记住那个时刻油门推到50%机架先朝一个方向歪过去补了一杆副翼它又反着晃回来。多数人在这之前已经折腾了两周硬件以为问题出在电机或电调上实际上真正让四轴“钉”在一个点上的是传感器数据质量和角速度环的响应速度。基于STM32F4的四轴飞行器控制系统之所以常见不是F4性能有多顶而是一片芯片同时给了硬件浮点、灵活的定时器矩阵和足够的DMA通道。这一篇从硬件选型、传感器布局、姿态解算、串级PID到无桨调试完整过一遍自研控制系统会走的路适合手里有散件或F4飞控板、想彻底搞明白姿态环与代码结构的人。2. STM32F4四轴飞行器控制系统的硬件设计从芯片选型到传感器布局2.1 主控与IMU选型F4系列里怎么挑四轴飞行器控制系统里最常见的MCU是STM32F405RGT6和STM32F411CEU6。F405主频168MHz带单精度FPU有8个定时器和3个12位ADC适合同时采集电压、电流、遥控信号和扩展传感器F411主频100MHz同样带FPU但定时器和ADC数量减半优势是QFN48封装小能塞进很小的飞控板。两类芯片的浮点单元让四元数解算从整数乘法变成单周期浮点指令姿态环才有余量跑到1kHz以上。选F4而不是更便宜的F1核心原因就在这里F1没有FPU同样的Mahony滤波在主频72MHz下要占用大量CPU时间留给控制环的预算就不够了。型号主频FPU定时器ADC常见封装适合场景STM32F405RGT6168MHz单精度8个3个LQFP64带电压电流采集的高配飞控STM32F411CEU6100MHz单精度5个1个QFN48紧凑飞控、5寸以下机架STM32F427VIT6180MHz单精度8个3个LQFP100带光流、外部传感器的大型飞控IMU方面MPU6050是保有量最大的选择三轴陀螺仪加三轴加速度计价格低、资料全。若需要磁力计辅助偏航修正直接上MPU9250或后续的ICM-20948。对飞行品质有更高要求时选ICM-20602或BMI088零偏稳定性和噪声密度都优于MPU系列但价格翻倍焊接难度也更大。传感器的接口选SPI而不是I2C。I2C总线在飞控高振动环境下信号线受电源噪声干扰时容易出现应答丢失SPI是同步时钟抗干扰能力更强而且可以挂在同一个SPI总线上用片选区分。有一个容易忽略的地方IMU在PCB上的摆放位置。陀螺仪和加速度计要尽量靠近机架几何中心同时远离电调功率走线。四路电调在电机换相瞬间会在母线上产生高di/dt噪声如果IMU下方正好走过电调信号线加速度计读数就会出现规律性毛刺。布局上至少保证IMU的正下方没有开关电源的电感并在传感器区域铺完整的地铜皮。2.1.1 供电系统的分层处理整个系统的电源分两级主电池通常是3S或4S锂电池直接给电调和电机供电飞控板的5V来自BEC或降压模块再经LDO降到3.3V给MCU和传感器。第一级降压不能和电调共用同一个5V输出电调负载变化会造成电压波动这个波动串进MCU的ADC参考就会让电压采样和姿态角数据抖动。常见的做法是独立降压模块输出5V飞控板上再放一个低噪声LDO单独给IMU供电。电池电压检测用分压电阻加ADC引脚采样路径串一个1kΩ到10kΩ的电阻作限流。3S电池满电12.6V分压比取4:1即上拉10kΩ、下拉3.3kΩ满电时ADC引脚电压约3.8V。要注意这个值超过3.3V参考电压所以实际设计会再把分压比提到5:1或者用两个二极管做钳位。这个电压比例系数需要写进设计说明文档因为固件里的低电压保护阈值、电量百分比显示都从这里换算出来。2.2 电机驱动与PWM定时器分配四路油门信号的来源四个电机的油门信号通常复用同一个定时器的四个通道相当于一个时基、四个比较寄存器更新一次定时器比较值就能同时修改四个通道的占空比。F405的TIM1和TIM8是高级定时器带互补输出和刹车输入TIM2到TIM5是通用定时器适合做多路PWM。PWM频率一般设在1kHz到2kHz。频率越低电调响应越慢高于2kHz会增大MOS开关损耗而且多数电调固件对超过500Hz的PWM更新频率并不敏感。用标准外设库配置TIM2输出四路PWM的代码如下void motor_pwm_init(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tb; TIM_OCInitTypeDef oc; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); gpio.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; gpio.GPIO_Mode GPIO_Mode_AF; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_TIM2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_TIM2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_TIM2); tb.TIM_Prescaler 84 - 1; // 84MHz - 1MHz tb.TIM_CounterMode TIM_CounterMode_Up; tb.TIM_Period 1000 - 1; // 1MHz/1000 1kHz TIM_TimeBaseInit(TIM2, tb); oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_Pulse 0; oc.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, oc); TIM_OC2Init(TIM2, oc); TIM_OC3Init(TIM2, oc); TIM_OC4Init(TIM2, oc); TIM_Cmd(TIM2, ENABLE); }预分频器设为84减1把168MHz的APB1定时器时钟降到1MHz自动重载值设为1000减1得到1kHz的PWM频率计数器范围0到999。也就是说油门比较值0对应0%占空比999对应100%每个PWM周期有1000个量化台阶精度足够。GPIO配置为复用推挽输出GPIO_PinAFConfig把PA0到PA3复用为TIM2的通道1到通道4。如果换成F411时钟树不同APB1定时器时钟可能是50MHz或100MHz预分频值要按实际总线时钟重算不能直接抄。电调信号线还需要注意接地。电调的地必须与飞控共地否则PWM信号的参考电压不一致电调端识别到的脉宽会漂移。共地应该就近从电源负极取不要依赖信号线本身形成回路。电调信号线过长时可以在飞控端串一个100Ω到330Ω的电阻来抑制振铃这在四轴飞行器控制系统布线中经常被忽视。2.3 上电自检的软件支持用WHO_AM_I寄存器验证硬件通路焊完板子最先做的不是写控制算法而是通过SWD调试器确认传感器能正常应答。MPU6050的WHO_AM_I寄存器地址是0x75复位值是0x68。如果读回不是0x68问题大概率出在SPI片选、MISO/MOSI接线或传感器供电上。以下代码放在工程启动阶段执行uint8_t imu_check(uint8_t cs_pin) { uint8_t who 0; spi_read_register(cs_pin, 0x75, who); return who; } if (imu_check(MPU6050_CS) ! 0x68) { error_code ERROR_IMU_NOT_FOUND; led_blink_pattern(error_code); }spi_read_register的第一个参数是片选引脚编号第二个参数是寄存器地址第三个是读取结果的输出指针。MPU6050在SPI模式下AD0引脚地址不参与选通每个传感器独立占用一个片选GPIO。这样设计后多个传感器可以挂在同一条SPI总线上通过软件控制片选来分时读取。读WHO_AM_I本质上是在验证MCU的SPI外设到传感器内部寄存器的整条通路没有断。焊接虚焊、引脚连锡、上拉电阻漏贴都会在这个最简单的操作里现形。把这段自检逻辑放到主循环启动阶段用板载LED闪烁次数区分故障类型调试时省掉大量对万用表逐点找信号的时间。3. 姿态解算与控制环路STM32F4的浮点能力用在哪3.1 四元数与Mahony互补滤波为什么不直接用DMPMPU6050内置DMP可以直接输出四元数和欧拉角很多入门者以为这能省掉解算步骤。但DMP的输出在飞控场景里往往带更明显的延迟因为它内部滤波链路参数固定不能针对具体机架的振动特性调整截止频率。自研飞控如果要调出好的手感还是自己用原始陀螺仪和加速度计数据做融合更可控。F4的FPU跑一个Mahony滤波加四元数更新在1kHz频率下CPU占用不超过15%这是软件解算能够替代DMP的根本原因。Mahony互补滤波的思路很直接加速度计提供重力方向参考陀螺仪提供短时角速度积分两者通过比例加积分的形式融合。设四元数 q 表示当前姿态陀螺仪角速度为 g则姿态变化率由四元数乘角速度给出。加速度计归一化后与当前四元数推算出的重力方向做叉积叉积误差就是陀螺仪积分漂移的修正量。核心代码如下#define MAHONY_KP 0.8f #define MAHONY_KI 0.001f static float q0 1.0f, q1 0.0f, q2 0.0f, q3 0.0f; static float integralFBx 0.0f, integralFBy 0.0f, integralFBz 0.0f; void mahony_update(float gx, float gy, float gz, float ax, float ay, float az, float dt) { float halfvx, halfvy, halfvz; float halfex, halfey, halfez; float gxr, gyr, gzr; float norm; halfvx q1 * q3 - q0 * q2; halfvy q0 * q1 q2 * q3; halfvz q0 * q0 - 0.5f q3 * q3; norm sqrtf(ax * ax ay * ay az * az) 1e-7f; ax / norm; ay / norm; az / norm; halfex (ay * halfvz - az * halfvy); halfey (az * halfvx - ax * halfvz); halfez (ax * halfvy - ay * halfvx); integralFBx MAHONY_KI * halfex; integralFBy MAHONY_KI * halfey; integralFBz MAHONY_KI * halfez; gxr gx halfex * MAHONY_KP integralFBx; gyr gy halfey * MAHONY_KP integralFBy; gzr gz halfez * MAHONY_KP integralFBz; q0 0.5f * (-q1 * gxr - q2 * gyr - q3 * gzr) * dt; q1 0.5f * ( q0 * gxr q2 * gzr - q3 * gyr) * dt; q2 0.5f * ( q0 * gyr - q1 * gzr q3 * gxr) * dt; q3 0.5f * ( q0 * gzr q1 * gyr - q2 * gxr) * dt; norm sqrtf(q0*q0 q1*q1 q2*q2 q3*q3); q0 / norm; q1 / norm; q2 / norm; q3 / norm; }halfvx、halfvy、halfvz是当前四元数推算出的重力方向分量的两倍加速度计测量值与它的叉积表示两者方向的偏差。叉积误差乘MAHONY_KP后直接叠加到角速度上完成修正MAHONY_KI则累积积分项消除常值漂移。dt是两次调用之间的时间间隔单位秒调用周期必须固定否则四元数更新步长不一致姿态角会缓慢漂移。MAHONY_KP通常设在0.5到1.0之间过大会把加速度计的高频噪声引入姿态MAHONY_KI设在0.001到0.01过大会在剧烈机动后出现姿态回摆。四元数归一化必须在每次更新后做这一步遗漏的话连续运行几分钟后四元数模长发散姿态角会出现无法收敛的漂移。代码里没有直接把四元数转成欧拉角是因为控制环通常直接使用四元数做旋转计算只在调试输出时才转成roll/pitch/yaw给上位机看。3.2 串级PID角速度内环决定四轴飞行器能不能稳住姿态控制用串级结构外层角度环内层角速度环。内环反馈的是陀螺仪角速度输出叠加到电机油门外层用期望角度和当前角度之差输出作为内环的期望角速度。这个结构的实际意义是让内环先抑制陀螺仪感受到的扰动外环再负责把角度拉回目标点。内环响应越快外环的P才能取得越大四轴在风力下的恢复速度就越快。角速度环的PID参数直接决定整机控制品质typedef struct { float kp, ki, kd; float integral, last_measurement; float out_limit, integral_limit; } pid_ctrl_t; float pid_update(pid_ctrl_t *pid, float setpoint, float measurement, float dt) { float error setpoint - measurement; 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; float derivative (measurement - pid-last_measurement) / dt; pid-last_measurement measurement; float output pid-kp * error pid-ki * pid-integral - pid-kd * derivative; if (output pid-out_limit) output pid-out_limit; if (output -pid-out_limit) output -pid-out_limit; return output; }微分项用的是测量值的差分而不是误差的差分。这是有意为之对误差做微分油门突变或遥控打杆时会产生巨大的微分冲击对测量值做微分得到的本质是角加速度只对姿态的快速变化起阻尼作用。由于陀螺仪数据本身带高频噪声微分项几乎必须配一个截止频率约50Hz的一阶低通否则角速度环的D输出会把电机的振动放大成高频啸叫。一组典型参数初值可以按下面的表来设条件是角速度环运行在1kHz、角度环运行在500Hz输出量纲为油门百分比环路PID说明角速度内环0.0300.0150.001P过大出现高频啸叫I过大低频摆动角度外环4.50.010.5数值影响起降阶段的倾斜恢复高度环气压计2.00.050.0依赖气压计数据的低通平滑I项的作用是消除重心偏离和重力分量造成的稳态误差。四轴重心不可能绝对居中某个电机长期承担更大升力角速度环的I项会逐步累积补偿恒定力矩。I过大会形成频率1Hz左右的“醉汉式”左右摆动这就是典型的低频振荡。外环的D项对角度变化提前响应能抑制起降阶段的机头下沉但外环输出到内环前必须限幅通常限在每秒200度到400度防止姿态角误差过大时内环期望角速度超过陀螺仪量程。控制周期和PWM更新频率要保持整数倍关系。比如角度环250Hz、角速度环500Hz、PWM 1kHz每级的数据新鲜度都是确定的不会出现某次PID计算用了旧陀螺仪数据的情况。频率不是越高越好电调本身刷新率有限以4kHz更新比较寄存器电调未必来得及响应反而增加定时器中断占用的CPU时间。4. 遥控、校准与软件联调软件代码里必须有的三个状态机4.1 遥控信号解析PPM解析的通道状态机接收机输出格式决定飞控的输入采集方式。PPM信号把8到10个通道的脉宽拼在同一根线上每个通道脉宽在1000到2000微秒之间帧与帧之间靠一个超过3毫秒的间隔区分。解析PPM本质是一个通道状态机捕获到高电平宽度后判断是通道数据还是帧间隔然后推进通道下标。这里有一个容易被新手写错的地方PPM的帧间隔判断阈值。普通通道脉宽最大到2000微秒帧间隔要超过4000微秒取3000微秒做阈值最安全。如果阈值取得太低随机噪声脉冲会被误判成帧起始通道数据全部错位。捕获用定时器输入捕获模式代码如下volatile uint16_t ppm_channels[8]; void TIM_IC_ISR(void) { static uint16_t last_capture; static uint8_t ch_index 0; uint16_t capture TIM_GetCapture1(TIM4); uint16_t width capture - last_capture; last_capture capture; if (width 3000) { ch_index 0; } else if (ch_index 8) { ppm_channels[ch_index] width; } }每次捕获中断计算出两次捕获之间的时间差就是当前通道的脉宽。若这个值大于3000微秒判定为一帧结束通道下标归零否则把脉宽存入对应通道并递进下标。为了过滤毛刺可以在主循环里检查ppm_channels[0]到[7]的数值范围超出500到2500微秒的通道直接置为上一次有效值或安全值。遥控信号断开时这一层的通道状态机会自然失去新数据配合一个帧年龄计数器就能实现遥控丢失检测。SBUS则完全走另一条路它是一路100000波特率的UART信号8位数据、偶校验、2个停止位一帧25字节包含16个通道。SBUS输出的电平是反相的STM32的USART要求空闲时为高电平所以必须用三极管反相器或电平转换芯片处理。如果自己画飞控建议直接把反相电路画在板上不要用杜邦线飞线否则信号质量在各种振动条件下都会出问题。4.1.1 通道映射从物理通道到控制量接收机输出的通道顺序不固定必须建立映射表把“期望油门、横滚、俯仰、偏航”与物理通道绑定。一个OSD或地面站界面上应该可以随时修改映射而不需要重新编译固件。默认映射一般如下通道序号控制量范围中间值CH1横滚1000-20001500CH2俯仰1000-20001500CH3油门1000-20001000低CH4偏航1000-20001500CH5飞行模式1000-20001500映射表的信息需要写进设计说明文档因为现场换接收机或改通道顺序时没有这份文档要重新试错很久。在代码里把映射表以结构体数组存到参数区通过串口指令读取和修改这样试飞时遇到“通道反了”只需对着地面站改配置。注意副翼和俯仰的方向也一样重要方向反了的后果和PID参数错误一样给负反馈反而变成正反馈飞机会直接翻滚。4.2 传感器校准与解锁状态机四轴飞行器的状态机至少要分六态开机自检、传感器校准、就绪等待、解锁、运行、加锁。开机自检检查IMU和遥控信号是否有效传感器校准区分加速度计六面校准和陀螺仪零偏采样就绪等待时满足解锁条件才能进入运行态。这个状态机不能省否则无人机在携带过程中误触发解锁会是很严重的安全隐患。解锁条件应当是一个多条件“与”的判据bool arm_condition_check(void) { bool throttle_low (ppm_channels[2] 1080); bool level_ok (angle_roll 10.0f angle_pitch 10.0f); bool gyro_stable (gyro_drift_max 2.0f); bool signal_ok (rx_signal_valid (ppm_frame_age 50)); return throttle_low level_ok gyro_stable signal_ok; }油门最低要求通道值小于1080而不是小于1000留出遥控器摇杆偏移的余量。机体倾角小于10度是为了防止飞控在倾斜状态下解锁导致起飞瞬间侧冲。gyro_stable表示最近500毫秒陀螺仪读数的峰峰值小于每秒2度如果机械振动过大或电调干扰严重这个条件会阻止解锁相当于一个硬件健康检查。信号有效要求最近一帧遥控数据不超过50毫秒超时后强制回到加锁状态。加速度计校准通常做六面校准把飞控依次水平、右侧、左侧、前倾、后倾、倒置在每个姿态下采集加速度计读数求出零偏和尺度因子。这里有个实际操作要点校准过程中如果用手扶着飞控手的抖动会直接影响数据因此每个面至少要采集100个样本取平均并且要剔除明显离群的点。这些校准参数应写到片内Flash掉电不丢失。4.3 调参期的串口遥测姿态角和PID输出同时看控制代码写得再多没有观测手段也定位不了问题。调参阶段最常见的做法是在主循环里用较低频率通过串口输出姿态角和PID输出。打印频率取40到100Hz即可波特率115200。打印任务不能放在控制中断服务函数里一个耗时2毫秒的打印会把1kHz的控制周期彻底打乱。正确做法是控制中断只更新数据的内存副本主循环负责把副本格式化成字符串发送。推荐输出纯文本CSV格式字段包括时间戳、期望横滚角、实际横滚角、角速度环P输出、I输出、D输出、四个电机油门值。电脑端用任意串口终端或脚本按逗号分列绘图能直观看出内环和外环各自的振荡频率。浮点用printf的%f格式会在Cortex-M4上占用很长的主循环时间建议把浮点值乘100取整后按整数输出保留小数点后两位精度的同时快很多。5. 试飞前的桌面验证从无桨测试到失控保护模拟5.1 无桨电机方向核对装桨之前先做无桨测试。给飞控上电通过遥控器把油门缓慢推到30%确认四个电机的旋转方向与代码中定义的期望方向一致。旋转方向错误的电机不仅不产生升力还会产生反扭矩让机架偏航。用记号笔在电机轴侧面画一条线低速转动时观察线的转向即可判断。电机方向由电调信号与三相线序共同决定自由调换任意两根电机线即可反转前提是确认固件里的电机方向定义。这项测试要写成专门模式而不是在正常飞行模式下手动推油门避免误操作导致状态机混乱。5.2 用STM32F4跑FFT审查陀螺仪振动数据电机高速旋转时螺旋桨和电机的不平衡会产生周期性振动振动频率与转速相关3S电池下常见转速在每分钟5000到10000转对应振动频率约80到170Hz。这些振动会通过陀螺仪耦合进角速度环的微分项导致高频抖动。用F4的FPU和CMSIS-DSP库里的arm_rfft_fast_f32函数把陀螺仪采样转成频谱就能在试飞前定位振动源arm_rfft_fast_instance_f32 fft_inst; arm_rfft_fast_init_f32(fft_inst, 1024); arm_rfft_fast_f32(fft_inst, gyro_samples, fft_output, 0);采样频率设为1kHz1024点FFT的频率分辨率约0.98Hz。在频谱上观察峰值所在频段并和电机转频对比如果峰值集中在某个转频的整数倍处通常是桨叶不平衡如果出现宽带噪声抬升则更可能是电机轴承磨损或电调换相噪声。这种用MCU跑FFT做振动诊断的思路和嵌入式频谱分析系统的做法一致飞控板本身就能完成不需要额外接逻辑分析仪。5.3 失控保护与低电压降落验证最后验证失控保护。拔掉遥控器发射机电源观察飞控是否在1秒内进入自稳降落逻辑。常见做法是检测到遥控信号丢失后强制将期望姿态回中油门按比例降到30%同时点亮LED红灯闪烁。进入这个状态的前提是解锁状态机的信号有效标志在超时后置为无效同时姿态环继续运行而不是停止输出否则四轴会直接自由落体。测试时要将四轴固定在机架上否则降落后电机转速不会归零有伤人风险。程序里用落地检测把电机输出清零油门低于5%且机体倾角持续小于5度约200毫秒即判定已经落地停止输出PWM。本文还有配套的精品资源点击获取