STM32F411四轴飞行器开发实战教程:从零基础到项目精通
本教程将全面介绍基于STM32F411的四轴飞行器开发过程,融合平衡小车之家、小马哥四轴和正点原子四轴等开源项目的经验,从硬件设计到软件实现,从基础理论到高级应用,帮助开发者系统掌握四轴飞行器开发的核心技术。
一、项目概述与基本原理
1.1 四轴飞行器工作原理
四轴飞行器是一种通过四个旋翼产生的升力实现飞行的无人机,其核心控制原理基于欧拉角动力学模型。四轴飞行器通过改变四个电机的转速来实现六自由度控制(前后、左右、上下移动和俯仰、横滚、偏航旋转)1。
核心控制原理:
- 姿态感知:通过MPU6050/MPU9250等惯性测量单元获取飞行器的三轴加速度和三轴角速度数据
- 姿态解算:使用互补滤波或卡尔曼滤波算法将传感器数据转换为欧拉角(俯仰、横滚、偏航)
- 控制算法:采用串级PID控制,内环控制角速度,外环控制角度
- 电机混控:将控制量分配到四个电机,实现稳定飞行
1.2 四轴飞行器类型
四轴飞行器主要有两种结构布局:
- "+"型布局:机头方向与一个电机方向一致,结构简单但机动性较差
- "X"型布局:机头方向在两个电机之间,机动性好,是主流选择1
本教程采用"X"型布局,四个电机编号为Motor1、Motor3(逆时针旋转)和Motor2、Motor4(顺时针旋转),通过对称旋转抵消反扭矩1。
二、硬件系统设计
2.1 硬件选型指南
部件 | 推荐型号 | 关键参数 | 注意事项 |
---|---|---|---|
主控芯片 | STM32F411 | Cortex-M4内核,100MHz主频,带FPU | 选择LQFP64封装便于手工焊接 |
姿态传感器 | MPU6050 | 六轴(加速度+陀螺仪) | 或MPU9250(含磁力计) |
气压计 | BMP280 | 用于高度保持 | 替代MS5611,性价比更高 |
无线模块 | NRF24L01+ | 2.4G通信,SPI接口 | 需外加PA+LNA模块增加距离 |
电机 | 720空心杯 | 或8520空心杯电机 | 根据负载选择 |
电调 | 集成MOSFET | 如SI2302 | 注意散热设计 |
电池 | 3.7V锂电 | 500-1000mAh | 多并提高续航 |
2.2 核心电路设计
STM32F411最小系统:
- 8MHz晶振连接OSC_IN/OSC_OUT
- 复位电路连接NRST
- Boot0通过10K电阻接地
- 3.3V稳压电路(AMS1117-3.3)
电源系统设计:
- 主电源:1S锂电池(3.7V)
- 3.3V稳压:为MCU和传感器供电
- 电机电源:直接连接电池,注意大电流走线
传感器接口电路:
- MPU6050:I2C接口(SCL/PB6, SDA/PB7)
- BMP280:SPI或I2C接口
- NRF24L01+:SPI接口(SCK/PA5, MISO/PA6, MOSI/PA7, CS/PA4, CE/PA3)
电机驱动电路:
采用N沟道MOSFET(SI2302)驱动空心杯电机,注意:
- 栅极串联100Ω电阻限流
- 10K下拉电阻防止误开启
- 添加续流二极管保护MOSFET9
2.3 PCB设计要点
-
布局原则:
- 主控芯片居中放置
- 传感器尽量靠近MCU
- 电机驱动靠近电机接口
- 电源模块靠近电池接口
-
布线技巧:
- 电机大电流走线加宽(≥1mm)
- 模拟与数字部分分开布局
- 晶振下方不走线
- 添加足够的滤波电容
-
常见问题:
- QFN封装焊接困难,建议使用LQFP
- 电机固定要牢固,避免振动影响传感器
- PCB厚度建议1.6mm,0.8mm太软9
三、软件架构与核心算法
3.1 系统软件架构
采用分层设计:
-
硬件抽象层(HAL):
- 传感器驱动(MPU6050、BMP280)
- 无线通信(NRF24L01+)
- 电机PWM输出
- 系统时钟配置
-
算法层:
- 姿态解算(互补滤波/卡尔曼滤波)
- PID控制算法
- 数据滤波处理
- 电机混控算法
-
应用层:
- 任务调度
- 遥控指令处理
- 飞行模式管理
- 安全保护机制
3.2 姿态解算实现
互补滤波算法:
// 互补滤波函数
float ComplementaryFilter(float accelAngle, float gyroRate, float dt, float alpha) {
static float angle = 0;
angle = alpha * (angle + gyroRate * dt) + (1 - alpha) * accelAngle;
return angle;
}
// 姿态解算调用示例
void IMU_Update() {
// 读取传感器原始数据
MPU6050_ReadData(&accelX, &accelY, &accelZ, &gyroX, &gyroY, &gyroZ);
// 计算加速度角度(俯仰和横滚)
float accelPitch = atan2(accelY, accelZ) * 180.0 / PI;
float accelRoll = atan2(-accelX, accelZ) * 180.0 / PI;
// 获取陀螺仪角速度(转换为度/秒)
float gyroPitchRate = gyroX / 16.4f;
float gyroRollRate = gyroY / 16.4f;
// 互补滤波
float dt = 0.002f; // 500Hz采样周期
float alpha = 0.98f; // 滤波系数
currentPitch = ComplementaryFilter(accelPitch, gyroPitchRate, dt, alpha);
currentRoll = ComplementaryFilter(accelRoll, gyroRollRate, dt, alpha);
}
卡尔曼滤波实现:
可参考开源KalmanFilter库11,对姿态数据进行更精确的估计。
3.3 PID控制算法
四轴飞行器采用串级PID控制:
- 外环(角度环):控制飞行器姿态角度
- 内环(角速度环):控制旋转速率
PID结构体定义:
typedef struct {
float Kp, Ki, Kd;
float integral;
float prevError;
float integralLimit;
} PID_Controller;
PID更新函数:
float PID_Update(PID_Controller *pid, float error, float dt) {
// 比例项
float proportional = pid->Kp * error;
// 积分项
pid->integral += error * dt;
// 积分限幅
if(pid->integral > pid->integralLimit) pid->integral = pid->integralLimit;
else if(pid->integral < -pid->integralLimit) pid->integral = -pid->integralLimit;
float integral = pid->Ki * pid->integral;
// 微分项
float derivative = pid->Kd * (error - pid->prevError) / dt;
pid->prevError = error;
return proportional + integral + derivative;
}
串级PID控制实现:
void Attitude_Control(float rollTarget, float pitchTarget, float yawTarget) {
// 外环PID计算(角度环)
float rollRateTarget = PID_Update(&rollAnglePID, rollTarget - currentRoll, dt);
float pitchRateTarget = PID_Update(&pitchAnglePID, pitchTarget - currentPitch, dt);
float yawRateTarget = PID_Update(&yawAnglePID, yawTarget - currentYaw, dt);
// 内环PID计算(角速度环)
float rollOutput = PID_Update(&rollRatePID, rollRateTarget - gyroRoll, dt);
float pitchOutput = PID_Update(&pitchRatePID, pitchRateTarget - gyroPitch, dt);
float yawOutput = PID_Update(&yawRatePID, yawRateTarget - gyroYaw, dt);
// 电机混控
Motor_Mixing(rollOutput, pitchOutput, yawOutput, throttle);
}
3.4 电机混控算法
"X"型布局的电机混控:
void Motor_Mixing(float roll, float pitch, float yaw, float throttle) {
// 计算四个电机的基本输出
float motor1 = throttle - roll + pitch + yaw; // 前左
float motor2 = throttle - roll - pitch - yaw; // 前右
float motor3 = throttle + roll - pitch + yaw; // 后右
float motor4 = throttle + roll + pitch - yaw; // 后左
// 限幅处理
motor1 = constrain(motor1, 0, MAX_PWM);
motor2 = constrain(motor2, 0, MAX_PWM);
motor3 = constrain(motor3, 0, MAX_PWM);
motor4 = constrain(motor4, 0, MAX_PWM);
// 设置电机PWM
Motor_SetPWM(MOTOR_1, motor1);
Motor_SetPWM(MOTOR_2, motor2);
Motor_SetPWM(MOTOR_3, motor3);
Motor_SetPWM(MOTOR_4, motor4);
}
四、系统实现与调试
4.1 开发环境搭建
-
工具链安装:
- Keil MDK-ARM开发环境
- STM32CubeMX初始化代码生成工具
- ST-Link/V2调试器驱动
-
工程配置:
- 设置系统时钟为100MHz(HSE 8MHz,PLL倍频)
- 配置调试接口(SWD)
- 启用FPU单元
-
外设初始化:
- PWM定时器(TIM1/TIM8)用于电机控制
- SPI接口用于NRF24L01+通信
- I2C接口用于MPU6050
- USART用于调试输出
4.2 主程序框架
int main(void) {
// 硬件初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init(); // PWM定时器
MX_I2C1_Init(); // MPU6050
MX_SPI1_Init(); // NRF24L01+
MX_USART1_UART_Init(); // 调试串口
// 外设初始化
MPU6050_Init();
NRF24L01_Init();
Motor_Init();
// 主循环
while (1) {
// 1. 读取传感器数据
MPU6050_ReadData(&imuData);
// 2. 姿态解算
currentPitch = ComplementaryFilter(...);
currentRoll = ComplementaryFilter(...);
// 3. 读取遥控指令
Remote_GetCommand(&rollTarget, &pitchTarget, &yawTarget, &throttle);
// 4. PID控制计算
Attitude_Control(rollTarget, pitchTarget, yawTarget);
// 5. 状态监测与保护
Safety_Check();
HAL_Delay(1); // 1ms控制周期
}
}
4.3 关键模块实现
MPU6050初始化与数据读取:
void MPU6050_Init(void) {
// 复位设备
MPU6050_WriteByte(MPU6050_RA_PWR_MGMT_1, 0x80);
HAL_Delay(100);
// 唤醒设备,选择时钟源
MPU6050_WriteByte(MPU6050_RA_PWR_MGMT_1, 0x01);
// 设置陀螺仪量程 ±2000°/s
MPU6050_WriteByte(MPU6050_RA_GYRO_CONFIG, 0x18);
// 设置加速度计量程 ±2g
MPU6050_WriteByte(MPU6050_RA_ACCEL_CONFIG, 0x00);
// 设置低通滤波器带宽 44Hz
MPU6050_WriteByte(MPU6050_RA_CONFIG, 0x03);
// 设置采样率 1kHz
MPU6050_WriteByte(MPU6050_RA_SMPLRT_DIV, 0x00);
}
void MPU6050_ReadData(MPU6050_Data *data) {
uint8_t buf[14];
MPU6050_ReadBytes(MPU6050_RA_ACCEL_XOUT_H, 14, buf);
data->Accel_X = (int16_t)(buf[0] << 8 | buf[1]);
data->Accel_Y = (int16_t)(buf[2] << 8 | buf[3]);
data->Accel_Z = (int16_t)(buf[4] << 8 | buf[5]);
data->Temp = (int16_t)(buf[6] << 8 | buf[7]);
data->Gyro_X = (int16_t)(buf[8] << 8 | buf[9]);
data->Gyro_Y = (int16_t)(buf[10] << 8 | buf[11]);
data->Gyro_Z = (int16_t)(buf[12] << 8 | buf[13]);
}
PWM输出配置:
void PWM_Init(void) {
TIM_OC_InitTypeDef sConfigOC = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 999; // 100kHz PWM
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim1);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
// 配置四个PWM通道
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2);
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3);
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4);
// 启动PWM输出
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
}
4.4 PID参数整定方法
调参步骤:
-
先调内环(角速度环):
- 将外环PID参数设为0,只启用内环
- 先调Kp:从较小值开始(如0.1),逐渐增大直到飞行器对摇杆响应迅速但不振荡
- 再调Kd:从0.01开始,增大以抑制振荡
- 最后调Ki:从0.001开始,消除静态误差
-
再调外环(角度环):
- 保持内环参数不变,启用外环
- 调Kp:从2.0开始,增大直到飞行器能保持角度但不振荡
- 调Kd:从0.1开始,抑制角度振荡
- Ki通常设为0,角度环不需要积分项
典型参数范围:
- 角速度环:Kp=0.3-1.0, Ki=0.001-0.01, Kd=0.01-0.05
- 角度环:Kp=2.0-5.0, Kd=0.1-0.5, Ki=0
调试技巧:
- 使用匿名地面站实时监控和调整参数5
- 记录飞行数据并分析响应曲线
- 小幅度逐步调整参数
- 先调ROLL/PITCH再调YAW
五、进阶功能与优化
5.1 无线通信与遥控
NRF24L01+通信实现:
void NRF24L01_Init(void) {
// 初始化SPI接口
HAL_GPIO_WritePin(NRF_CS_GPIO_Port, NRF_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(NRF_CE_GPIO_Port, NRF_CE_Pin, GPIO_PIN_RESET);
// 配置NRF24L01+
NRF_Write_Reg(NRF_WRITE_REG + CONFIG, 0x0E); // 使能CRC,PWR_UP,PRIM_RX
NRF_Write_Reg(NRF_WRITE_REG + EN_AA, 0x00); // 关闭自动应答
NRF_Write_Reg(NRF_WRITE_REG + EN_RXADDR, 0x01); // 使能数据通道0
NRF_Write_Reg(NRF_WRITE_REG + SETUP_RETR, 0x00); // 关闭重发
NRF_Write_Reg(NRF_WRITE_REG + RF_CH, 76); // 设置频道76
NRF_Write_Reg(NRF_WRITE_REG + RF_SETUP, 0x07);// 0dB增益,2Mbps
NRF_Write_Reg(NRF_WRITE_REG + STATUS, 0x70); // 清除状态寄存器
NRF_Write_Reg(NRF_WRITE_REG + RX_PW_P0, 16); // 接收数据长度16字节
// 设置接收模式
HAL_GPIO_WritePin(NRF_CE_GPIO_Port, NRF_CE_Pin, GPIO_PIN_SET);
}
uint8_t Remote_GetCommand(float *roll, float *pitch, float *yaw, float *throttle) {
uint8_t rx_buf[16];
if(NRF_Read_Buf(rx_buf) == 0) {
*roll = ((int16_t)(rx_buf[0] << 8 | rx_buf[1])) / 100.0f;
*pitch = ((int16_t)(rx_buf[2] << 8 | rx_buf[3])) / 100.0f;
*yaw = ((int16_t)(rx_buf[4] << 8 | rx_buf[5])) / 100.0f;
*throttle = ((int16_t)(rx_buf[6] << 8 | rx_buf[7])) / 100.0f;
return 1;
}
return 0;
}
5.2 高度保持功能
利用BMP280气压计实现高度保持:
void BMP280_Init(void) {
// 初始化I2C接口
BMP280_Write_Reg(0xF4, 0x27); // 温度oversampling x1,压力oversampling x1,正常模式
BMP280_Write_Reg(0xF5, 0x00); // 待机时间0.5ms,滤波器关闭
}
float BMP280_GetAltitude(void) {
int32_t adc_T, adc_P;
BMP280_Read_Trim();
BMP280_Read_Data(&adc_T, &adc_P);
// 计算温度
float var1 = (((float)adc_T)/16384.0 - ((float)dig_T1)/1024.0) * ((float)dig_T2);
float var2 = ((((float)adc_T)/131072.0 - ((float)dig_T1)/8192.0) *
(((float)adc_T)/131072.0 - ((float)dig_T1)/8192.0)) * ((float)dig_T3);
float t = (var1 + var2)/5120.0;
// 计算压力
var1 = ((float)dig_P6) * t * t / 32768.0;
var2 = ((float)dig_P5) * t / 128.0;
float p = (var1 + var2 + ((float)dig_P4)) / 16.0;
// 计算高度
float altitude = 44330.0 * (1.0 - pow(p / 101325.0, 0.1903));
return altitude;
}
void Altitude_Hold(float targetAlt) {
static float altIntegral = 0;
static float prevAlt = 0;
float currentAlt = BMP280_GetAltitude();
float altError = targetAlt - currentAlt;
// 高度PID控制
altIntegral += altError * dt;
altIntegral = constrain(altIntegral, -100, 100);
float altDerivative = (currentAlt - prevAlt) / dt;
prevAlt = currentAlt;
float altControl = altPID.Kp * altError +
altPID.Ki * altIntegral -
altPID.Kd * altDerivative;
// 调整油门
throttle = baseThrottle + altControl;
throttle = constrain(throttle, 0, MAX_THROTTLE);
}
5.3 安全保护机制
异常状态检测与保护:
void Safety_Check(void) {
// 角度过大保护
if(fabs(currentPitch) > 45.0f || fabs(currentRoll) > 45.0f) {
Motor_StopAll();
while(1); // 进入保护状态
}
// 通信丢失保护
static uint32_t lastRxTime = 0;
if(HAL_GetTick() - lastRxTime > 500) { // 500ms未收到信号
Motor_StopAll();
} else {
lastRxTime = HAL_GetTick();
}
// 低电压保护
float voltage = Battery_GetVoltage();
if(voltage < LOW_VOLTAGE_THRESHOLD) {
// 缓慢降落
throttle = constrain(throttle - 5, 0, MAX_THROTTLE);
}
}
六、项目总结与进阶学习
6.1 常见问题解决
问题1:飞行器无法起飞或动力不足
- 检查电机转向是否正确
- 检查PWM信号是否正常输出
- 检查电池电压是否充足
- 增加油门微调或重新校准电调
问题2:飞行器剧烈振荡
- 减小角速度环的Kp和Kd
- 检查传感器数据是否正常
- 检查机械结构是否牢固
- 增加传感器数据滤波
问题3:飞行器偏向一边
- 检查传感器校准
- 检查重心位置是否居中
- 重新校准遥控器中立点
6.2 学习资源推荐
开源项目参考:
- 小马哥DragonFly四轴开源项目23
- 正点原子MiniFly四轴开源项目6
- Betaflight开源飞控
推荐书籍:
- 《STM32库开发实战指南》
- 《无人机设计与控制》
- 《自动控制原理》
进阶方向:
- 改用RTOS实现多任务
- 加入光流/超声波定位
- 实现GPS导航
- 开发手机APP控制界面
- 尝试机器学习控制算法12
6.3 项目展示与分享
博客撰写要点:
- 项目背景与意义
- 系统设计与实现
- 关键技术难点与解决方案
- 效果展示(视频/图片)
- 经验总结与未来改进
面试项目介绍要点:
- 突出技术难点和解决方案
- 展示对系统原理的深入理解
- 说明个人贡献和收获
- 准备技术细节的深入讨论