STM32F103平衡车实战:用EXTI中断和MPU6050实现姿态快速响应(附完整代码)
STM32F103平衡车实战:EXTI中断与MPU6050的姿态控制艺术
平衡车的核心在于实时姿态感知与快速响应——这就像杂技演员走钢丝时,身体需要不断微调重心来维持平衡。作为嵌入式开发者,我们需要用STM32F103的EXTI中断和MPU6050传感器构建一个类似的"数字神经系统"。本文将带你从电路设计到代码实现,打造一个响应速度在毫秒级的平衡车控制系统。
1. 硬件架构设计:传感器与处理器的黄金组合
平衡车的硬件系统需要三个关键部分协同工作:姿态感知单元、主控单元和执行单元。MPU6050作为姿态感知的核心,其数据采集的实时性直接决定整个系统的响应速度。
传感器选型对比表:
| 参数 | MPU6050 | BMI160 | ICM-20602 |
|---|---|---|---|
| 加速度计量程 | ±2/4/8/16g | ±2/4/8/16g | ±2/4/8/16g |
| 陀螺仪量程 | ±250/500/2000°/s | ±125/250/500°/s | ±250/500/1000°/s |
| 通信接口 | I2C/SPI | I2C/SPI | I2C/SPI |
| 功耗(mA) | 3.9 | 1.8 | 3.5 |
| 典型应用 | 平衡车/无人机 | 可穿戴设备 | 工业设备 |
选择MPU6050主要基于三点考虑:
- 量程范围完全满足平衡车需求
- 成熟的社区支持与丰富案例
- 性价比优势明显
电路连接要点:
- MPU6050的INT引脚连接到STM32的PB5(EXTI Line5)
- I2C接口使用PB6(SCL)、PB7(SDA)
- 电机驱动使用TIM1和TIM2的PWM输出
- 确保所有数字地良好连接
提示:MPU6050的VCC建议使用3.3V供电,避免电平不匹配问题。如果使用5V供电,需要添加电平转换电路。
2. 中断系统设计:让关键事件"插队"
在平衡车系统中,姿态数据的处理必须享有最高优先级。想象一下,当车体开始倾斜时,如果系统还在处理无关紧要的串口数据,结果必然是失控摔倒。
中断优先级配置代码:
void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; // 设置优先级分组为Group2:2位抢占优先级,2位响应优先级 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置MPU6050外部中断(最高优先级) NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级0 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 响应优先级0 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 电机PWM定时器中断(中等优先级) NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStructure); // 串口调试中断(最低优先级) NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; NVIC_Init(&NVIC_InitStructure); }中断处理的最佳实践:
- 保持ISR(中断服务例程)尽可能简短
- 避免在ISR中调用延时函数
- 使用标志位将处理转移到主循环
- 临界区保护共享资源
3. MPU6050数据采集与滤波:从原始数据到可靠姿态
MPU6050输出的原始数据包含大量噪声,直接使用会导致控制系统剧烈震荡。我们需要通过软件滤波和传感器融合算法提取有用的姿态信息。
数据采集流程优化:
配置MPU6050:
- 加速度计±4g量程
- 陀螺仪±500°/s量程
- 数字低通滤波器42Hz
- 采样率1kHz
初始化I2C接口:
void I2C_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; // 使能GPIO和I2C时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 配置PB6(SCL)和PB7(SDA) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // I2C配置 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 = 0xA0; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 400000; // 400kHz I2C_Init(I2C1, &I2C_InitStructure); I2C_Cmd(I2C1, ENABLE); }- 实现互补滤波算法:
float complementaryFilter(float accelAngle, float gyroRate, float dt, float alpha) { static float angle = 0.0f; angle = alpha * (angle + gyroRate * dt) + (1 - alpha) * accelAngle; return angle; }传感器校准步骤:
- 将MPU6050水平静止放置
- 采集1000个样本计算平均值
- 保存陀螺仪零偏值
- 运行时减去零偏值
4. 控制算法实现:PID调节的艺术
平衡车的控制核心是PID算法,它需要根据姿态角度误差计算出适当的电机控制量。我们将使用位置式PID算法,因其更直观且易于调试。
PID控制器实现代码:
typedef struct { float Kp, Ki, Kd; float integral; float prevError; float outputLimit; } PIDController; void PID_Init(PIDController* pid, float Kp, float Ki, float Kd, float limit) { pid->Kp = Kp; pid->Ki = Ki; pid->Kd = Kd; pid->integral = 0.0f; pid->prevError = 0.0f; pid->outputLimit = limit; } float PID_Update(PIDController* pid, float error, float dt) { // 比例项 float proportional = pid->Kp * error; // 积分项(带抗饱和) pid->integral += error * dt; if(pid->integral > pid->outputLimit) pid->integral = pid->outputLimit; if(pid->integral < -pid->outputLimit) pid->integral = -pid->outputLimit; float integral = pid->Ki * pid->integral; // 微分项 float derivative = pid->Kd * (error - pid->prevError) / dt; pid->prevError = error; // 计算总输出 float output = proportional + integral + derivative; // 限制输出范围 if(output > pid->outputLimit) output = pid->outputLimit; if(output < -pid->outputLimit) output = -pid->outputLimit; return output; }PID参数整定技巧:
- 先调Kp直到系统开始震荡
- 然后加入Kd抑制震荡
- 最后加入Ki消除静差
- 实际调试时每次只调整一个参数
典型PID参数范围参考:
- Kp: 20.0 - 50.0
- Ki: 0.1 - 1.0
- Kd: 0.5 - 5.0
5. 电机驱动与系统集成:让算法落地
有了准确的姿态数据和优秀的控制算法,最后需要将它们转化为电机的实际动作。我们将使用PWM驱动直流电机,通过H桥电路实现正反转控制。
PWM配置代码:
void PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE); // 配置PA8(TIM1_CH1)和PA9(TIM1_CH2)为复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 定时器基础配置 TIM_TimeBaseStructure.TIM_Period = 999; // PWM频率 = 72MHz/(999+1) = 72kHz TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // PWM模式配置 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比0% TIM_OC1Init(TIM1, &TIM_OCInitStructure); // CH1 TIM_OC2Init(TIM1, &TIM_OCInitStructure); // CH2 // 使能预装载寄存器 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); }电机控制逻辑:
- 将PID输出转换为PWM占空比
- 根据输出正负决定电机转向
- 加入死区补偿防止H桥直通
- 实现软启动避免电流冲击
系统主循环结构:
int main(void) { // 硬件初始化 SystemInit(); I2C_Configuration(); MPU6050_Init(); PWM_Init(); NVIC_Configuration(); // 控制器初始化 PIDController balancePID; PID_Init(&balancePID, 35.0f, 0.5f, 2.0f, 1000.0f); while(1) { if(dataReadyFlag) { // MPU6050数据就绪标志 dataReadyFlag = 0; // 读取传感器数据 MPU6050_ReadData(&sensorData); // 计算姿态角度 float accelAngle = atan2(sensorData.accelY, sensorData.accelZ) * 180.0f / PI; float gyroRate = sensorData.gyroX; currentAngle = complementaryFilter(accelAngle, gyroRate, 0.001f, 0.98f); // PID计算 float output = PID_Update(&balancePID, currentAngle - targetAngle, 0.001f); // 电机控制 if(output > 0) { MOTOR_A_FORWARD(output); MOTOR_B_FORWARD(output); } else { MOTOR_A_BACKWARD(-output); MOTOR_B_BACKWARD(-output); } } } }注意:实际项目中还需要加入安全保护机制,如倾角过大时切断电机电源,防止损坏设备或造成危险。
