很多同学在制作单片机平衡车时往往只停留在通过串口打印角度数据的阶段虽然能看到姿态数据的变化但车子就是站不起来。这其实是平衡车开发中最常见的瓶颈——从数据采集到实际控制之间的关键环节没有打通。本文将带你完整实现一个基于STM32的两轮平衡车系统重点讲解如何从串口调试进阶到真正的PID闭环控制。1. 平衡车系统架构与核心原理1.1 平衡车工作原理两轮平衡车的核心原理是倒立摆控制。当车体向前倾斜时控制系统需要驱动车轮向前加速产生一个反向力矩来抵消倾斜反之亦然。这个过程需要实时检测车体姿态并快速计算控制量输出到电机。1.2 系统组成模块一个完整的平衡车系统包含以下几个关键部分主控芯片STM32F103C8T6负责数据处理和控制算法姿态传感器MPU6050实时检测车体倾斜角度电机驱动TB6612模块控制直流电机转速和方向编码器霍尔编码器测量电机实际转速电源管理为各模块提供稳定供电1.3 控制流程概述整个控制流程可以概括为MPU6050采集原始数据 → DMP库解算得到欧拉角 → PID控制器计算控制量 → PWM驱动电机 → 编码器反馈速度 → 闭环调节。很多初学者卡在第一步到第二步的转换或者PID参数调试不当。2. 硬件选型与电路设计2.1 主控芯片STM32F103C8T6资源分配这款芯片虽然价格亲民但资源足够平衡车使用TIM2产生2路PWM信号控制电机转速TIM3/TIM4编码器接口模式读取两个电机的转速TIM1系统定时器用于精确控制采样周期I2C1连接MPU6050姿态传感器I2C2连接OLED显示屏可选USART1串口调试PA9(TX)、PA10(RX)2.2 MPU6050姿态传感器详解MPU6050集成了3轴加速度计和3轴陀螺仪但直接读取的原始数据存在噪声和漂移。官方DMP库Digital Motion Processor内置了传感器融合算法可以直接输出稳定的欧拉角。接线方式VCC → 3.3VGND → GNDSCL → PB10SDA → PB11INT → 任意GPIO用于数据准备中断2.3 TB6612电机驱动模块与常用的L298N相比TB6612具有效率高、发热小的优点。关键引脚定义VM电机电源7-12VVCC逻辑电源3.3V或5VPWMA/AIN1/AIN2电机A控制PWMB/BIN1/BIN2电机B控制STBY使能引脚高电平有效2.4 霍尔编码电机参数计算以JGB37-520电机为例减速比30:1编码器PPR为11磁极对数×2。实际轮子转一圈产生的脉冲数 11 × 30 330脉冲/圈。转速计算公式转速(RPM) (脉冲数 × 60) / (330 × 采样时间) 例如100ms内计数55个脉冲转速 (55 × 60) / (330 × 0.1) 100 RPM3. 软件架构与核心算法3.1 系统初始化流程完整的系统初始化需要按顺序进行void System_Init(void) { HAL_Init(); // HAL库初始化 SystemClock_Config(); // 系统时钟配置 MX_GPIO_Init(); // GPIO初始化 MX_TIM1_Init(); // 系统定时器 MX_TIM2_Init(); // PWM定时器 MX_TIM3_Init(); // 编码器1 MX_TIM4_Init(); // 编码器2 MX_I2C1_Init(); // MPU6050 MX_USART1_UART_Init(); // 串口调试 MPU6050_Init(); // 传感器初始化 OLED_Init(); // 显示屏初始化 }3.2 姿态解算实现使用DMP库可以大大简化姿态解算的复杂度float Get_MPU6050_Angle(void) { if(mpu_dmp_get_data(pitch,roll,yaw)0) { // 低通滤波处理减少高频噪声 angle 0.95 * angle 0.05 * pitch; return angle; } return 0; }3.3 PID控制器设计平衡车需要双环PID控制外环角度环内环速度环。typedef struct { float Target; // 目标值 float Current; // 当前值 float Err; // 当前误差 float Err_Last; // 上次误差 float Kp, Ki, Kd; // PID参数 float integral; // 积分项 float output; // 输出值 }PID; float PID_Calculate(PID *pid) { pid-Err pid-Target - pid-Current; // 积分限幅防止积分饱和 pid-integral pid-Err; if(pid-integral 1000) pid-integral 1000; if(pid-integral -1000) pid-integral -1000; // PID计算 pid-output pid-Kp * pid-Err pid-Ki * pid-integral pid-Kd * (pid-Err - pid-Err_Last); pid-Err_Last pid-Err; return pid-output; }3.4 电机控制实现根据PID输出生成PWM信号控制电机void Motor_Control(int left_speed, int right_speed) { // 限制PWM值在有效范围内 left_speed constrain(left_speed, -1000, 1000); right_speed constrain(right_speed, -1000, 1000); // 设置电机方向 if(left_speed 0) { HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET); left_speed -left_speed; } // 设置PWM占空比 __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, left_speed); }4. 从串口调试到实际控制的关键步骤4.1 数据可视化调试在初期通过串口打印数据是必要的但要学会有效利用void Debug_Info(float angle, float speed, int pwm) { printf(角度:%.2f° 速度:%.2f PWM:%d\n, angle, speed, pwm); // 或者使用更直观的图形化显示 // 角度曲线**********|---------- (正负10度范围) int graph (int)(angle * 5) 50; for(int i0; i100; i) { if(i 50) printf(|); else if(i graph) printf(*); else printf( ); } printf(\n); }4.2 参数整定流程PID参数调试需要循序渐进先调角度环将速度环设定为0只调试角度平衡先将Ki、Kd设为0慢慢增大Kp直到车体开始振荡然后加入Kd抑制振荡慢慢增大直到响应平稳最后加入Ki消除稳态误差再调速度环在角度环稳定的基础上加入速度控制同样的步骤先P后D再I注意速度环的参数要比角度环小一个数量级4.3 实际调试技巧// 在main循环中加入参数在线调节功能 if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET) { HAL_Delay(10); // 消抖 if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET) { angle_pid.Kp 0.1; // 按按键增加Kp printf(Kp%.2f\n, angle_pid.Kp); } }5. 常见问题与解决方案5.1 角度数据抖动严重问题现象串口显示的角度值跳动很大车体无法稳定解决方案检查MPU6050的安装是否牢固增加软件低通滤波系数检查电源纹波必要时增加滤波电容确保I2C通信线路远离电机PWM线5.2 电机响应迟钝或过冲问题现象车体倾斜后电机反应慢或者过度补偿导致振荡解决方案检查PWM频率是否合适推荐8-10kHz调整PID采样时间推荐5-10ms检查电机驱动电压是否足够确认编码器读数是否正确5.3 车体向一边偏斜问题现象平衡时车体总是偏向一侧解决方案校准MPU6050的零偏水平放置时读取角度偏移量检查机械结构是否对称在代码中加入零偏补偿float angle_offset -2.5; // 校准得到的偏移量 float actual_angle raw_angle - angle_offset;5.4 电池电压下降导致性能变差问题现象新电池时平衡良好用一段时间后开始抖动解决方案加入电压补偿机制float voltage_compensate 12.0 / current_voltage; pwm_output pwm_output * voltage_compensate;使用稳压模块确保电机驱动电压稳定定期检查电池电量并及时充电6. 进阶优化与功能扩展6.1 双环PID协调控制完整的平衡车需要角度环和速度环协同工作void Balance_Control(void) { // 外环角度PID angle_pid.Target 0; // 目标角度直立 angle_pid.Current Get_MPU6050_Angle(); float angle_output PID_Calculate(angle_pid); // 内环速度PID speed_pid.Target angle_output; // 角度环输出作为速度环目标 speed_pid.Current Get_Motor_Speed(); float speed_output PID_Calculate(speed_pid); // 输出到电机 Motor_Control(speed_output, speed_output); }6.2 远程遥控功能通过蓝牙或2.4G模块添加遥控功能void Remote_Control(void) { if(USART1_RX_Flag) // 串口接收到数据 { switch(USART1_RX_Buffer[0]) { case F: // 前进 speed_pid.Target 10; break; case B: // 后退 speed_pid.Target - 10; break; case S: // 停止 speed_pid.Target 0; break; } USART1_RX_Flag 0; } }6.3 姿态保护机制防止车体失控的保护策略void Safety_Check(void) { float angle Get_MPU6050_Angle(); // 角度过大保护 if(fabs(angle) 45.0) // 超过45度停止电机 { Motor_Stop(); printf(角度过大系统保护\n); } // 通信异常保护 if(MPU6050_Timeout 1000) // 1秒未收到数据 { Motor_Stop(); printf(MPU6050通信异常\n); } }7. 实际项目经验总结7.1 机械结构的重要性很多平衡问题源于机械结构重心位置、轮子摩擦力、电机安装精度等都会影响控制效果。建议先确保机械结构的稳定性和对称性再调试软件参数。7.2 电源管理的注意事项电机启动时的电流冲击会导致电压跌落可能引起单片机复位。解决方法电机电源与单片机电源分离在电机电源端加大容量电容1000μF以上使用高质量的稳压模块7.3 调试工具的使用技巧除了串口调试还可以利用OLED显示屏实时显示关键参数LED指示灯显示系统状态按键用于在线参数调整蓝牙模块无线传输数据7.4 从调试到稳定的过渡当车体能够初步站立时不要满足于短暂的平衡。需要长时间运行测试30分钟以上观察在不同地面、不同电池电量下的表现进一步优化参数。平衡车项目是学习嵌入式控制系统的最佳实践从串口打印到实际控制的过程正是从理论到实践的跨越。掌握这个项目后你会发现PID控制、传感器融合、实时系统等概念不再抽象而是变成了可以直观感受和调试的实际技能。