
完整项目已打包,开源免费:
https://blog.csdn.net/weixin_52908342/article/details/150371964l
从一块 STM32F4 开发板,到一台能稳定飞行的四轴飞行器,这中间跨越了传感器数据采集、姿态解算、控制算法、无线通信、电机驱动等多个领域。
这篇文章记录了我和团队在校园创新大赛中完成四轴飞行器的全过程,希望能为后来者提供思路和借鉴。




四轴飞行器作为一种低空、低成本的遥感平台,已经在多个领域展现出广泛的应用潜力。相比其他类型的飞行器,它在硬件上结构紧凑、安装方便,但在软件层面却充满挑战——从传感器数据融合到姿态解算,再到快速且稳定的控制算法,每一环节都需要精心设计,也正因此让四轴飞行器更具技术魅力。
本项目以 ST 公司推出的 STM32 作为核心处理器,选用 STM32F4 Discovery 开发板作为遥控接收端,结合 MPU6050 姿态传感器,配备软塑料机架、空心杯电机、正反螺旋桨、锂电池以及四轴遥控器。经过系统化的调试与优化,最终实现了能够稳定飞行、响应迅速、并具备一定抗干扰能力的小型四轴飞行器。
无人机技术在近几年飞速发展,而四轴飞行器凭借结构简单、控制灵活、成本低廉,成为入门和研究的热门方向。
我们希望通过这个项目,深入理解嵌入式系统在多旋翼飞控中的应用,并在 STM32F4 平台上自主实现从传感器采集到飞行控制的完整闭环系统。

⚙ 硬件连接建议
传感器接口线尽量短,减少干扰 电源部分加去耦电容,防止电机启动时的电压波动影响 MCU
互补滤波的原理很简单:
公式:
angle = α * (angle + gyro * dt) + (1 - α) * acc_angle;其中 α 通常取 0.96 ~ 0.98。
void IMUupdate(float gx, float gy, float gz, float ax, float ay, float az)
{
float norm;
float vx, vy, vz;
float ex, ey, ez;
// 先把这些用得到的值算好
float q0q0 = q0*q0;
float q0q1 = q0*q1;
float q0q2 = q0*q2;
// float q0q3 = q0*q3;
float q1q1 = q1*q1;
// float q1q2 = q1*q2;
float q1q3 = q1*q3;
float q2q2 = q2*q2;
float q2q3 = q2*q3;
float q3q3 = q3*q3;
if(ax*ay*az==0)
return;
norm = sqrt(ax*ax + ay*ay + az*az); //acc数据归一化
ax = ax /norm;
ay = ay / norm;
az = az / norm;
// estimated direction of gravity and flux (v and w) 估计重力方向和流量/变迁
vx = 2*(q1q3 - q0q2); //四元素中xyz的表示
vy = 2*(q0q1 + q2q3);
vz = q0q0 - q1q1 - q2q2 + q3q3 ;
// error is sum of cross product between reference direction of fields and direction measured by sensors
ex = (ay*vz - az*vy) ; //向量外积在相减得到差分就是误差
ey = (az*vx - ax*vz) ;
ez = (ax*vy - ay*vx) ;
exInt = exInt + ex * Ki; //对误差进行积分
eyInt = eyInt + ey * Ki;
ezInt = ezInt + ez * Ki;
// adjusted gyroscope measurements
gx = gx + Kp*ex + exInt; //将误差PI后补偿到陀螺仪,即补偿零点漂移
gy = gy + Kp*ey + eyInt;
gz = gz + Kp*ez + ezInt; //这里的gz由于没有观测者进行矫正会产生漂移,表现出来的就是积分自增或自减
// integrate quaternion rate and normalise //四元素的微分方程
q0 = q0 + (-q1*gx - q2*gy - q3*gz)*halfT;
q1 = q1 + ( q0*gx + q2*gz - q3*gy)*halfT;
q2 = q2 + ( q0*gy - q1*gz + q3*gx)*halfT;
q3 = q3 + ( q0*gz + q1*gy - q2*gx)*halfT;
// normalise quaternion
norm = sqrt(q0*q0 + q1*q1 + q2*q2 + q3*q3);
q0 = q0 / norm;
q1 = q1 / norm;
q2 = q2 / norm;
q3 = q3 / norm;
Q_ANGLE.Z = GYRO_I.Z;//atan2(2 * q1 * q2 + 2 * q0 * q3, -2 * q2*q2 - 2 * q3* q3 + 1)* 57.3; // yaw
Q_ANGLE.Y = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3; // pitch
Q_ANGLE.X = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3; // roll
}
b)PID控制部分源码:
void CONTROL(float rol_now, float pit_now, float yaw_now, float rol_tar, float pit_tar, float yaw_tar)
{
u16 moto1 = 0, moto2 = 0, moto3 = 0, moto4 = 0;
vs16 throttle;
float rol = rol_tar + rol_now;
float pit = pit_tar + pit_now;
float yaw = yaw_tar + yaw_now;
throttle = Rc_Get.THROTTLE - 1000;//油门 节流阀
if (throttle < 0) throttle = 0;
PID_ROL.IMAX = throttle / 2;
Get_MxMi(PID_ROL.IMAX, 1000, 0);
PID_PIT.IMAX = PID_ROL.IMAX;
PID_ROL.pout = PID_ROL.P * rol;
PID_PIT.pout = PID_PIT.P * pit;
if (rol_tar * rol_tar < 0.1 && pit_tar * pit_tar < 0.1 && rol_now * rol_now < 30 && pit_now * pit_now < 30 && throttle > 300)
{
PID_ROL.iout += PID_ROL.I * rol;
PID_PIT.iout += PID_PIT.I * pit;
PID_ROL.iout = Get_MxMi(PID_ROL.iout, PID_ROL.IMAX, -PID_ROL.IMAX);
PID_PIT.iout = Get_MxMi(PID_PIT.iout, PID_PIT.IMAX, -PID_PIT.IMAX);
}
else if (throttle < 200)
{
PID_ROL.iout = 0;
PID_PIT.iout = 0;
}
PID_ROL.dout = PID_ROL.D * MPU6050_GYRO_LAST.X;
PID_PIT.dout = PID_PIT.D * MPU6050_GYRO_LAST.Y;
PID_YAW.pout = PID_YAW.P * yaw;
vs16 yaw_d;
if (1480 > Rc_Get.YAW || Rc_Get.YAW > 1520)
{
yaw_d = MPU6050_GYRO_LAST.Z + (Rc_Get.YAW - 1500) * 10;
GYRO_I.Z = 0;
}
else
yaw_d = MPU6050_GYRO_LAST.Z;
PID_YAW.dout = PID_YAW.D * yaw_d;
PID_ROL.OUT = PID_ROL.pout + PID_ROL.iout + PID_ROL.dout;
PID_PIT.OUT = PID_PIT.pout + PID_PIT.iout + PID_PIT.dout;
PID_YAW.OUT = PID_YAW.pout + PID_YAW.iout + PID_YAW.dout;
/*
rol 翻滚
pat 俯仰角
yaw //偏航
*/
if (throttle > 200)
{
#define YAW_ADD (-30)
moto1 = throttle + PID_ROL.OUT - PID_PIT.OUT +YAW_ADD+ PID_YAW.OUT/**/;
moto2 = throttle - PID_ROL.OUT - PID_PIT.OUT -YAW_ADD- PID_YAW.OUT/**/;
moto3 = throttle - PID_ROL.OUT + PID_PIT.OUT +YAW_ADD+ PID_YAW.OUT/**/;
moto4 = throttle + PID_ROL.OUT + PID_PIT.OUT -YAW_ADD- PID_YAW.OUT/**/;
}
else
{
moto1 = 0;
moto2 = 0;
moto3 = 0;
moto4 = 0;
}
if (ARMED) Moto_PwmRflash(moto1, moto2, moto3, moto4);
else Moto_PwmRflash(0, 0, 0, 0);
}调参经验:
遥控器端通过摇杆产生期望角度数据,nRF24L01 发送到飞控板,由飞控板解码并更新目标姿态。
数据结构示例:
typedef struct {
float roll_target;
float pitch_target;
float yaw_target;
float throttle;
} RC_Command;这个四轴飞行器项目不仅让我们掌握了 STM32 在飞控中的应用,更让我们体会到跨领域协作的重要性——硬件、电路、嵌入式编程、控制理论,缺一不可。
飞行器的第一次平稳悬停,是最有成就感的时刻。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。