从零到一:基于STM32与MPU6050的自平衡小车PID控制实战 📅 2026/7/15 18:01:22 1. 项目概述为什么选择STM32和MPU6050两轮自平衡小车是嵌入式开发者的Hello World级项目它完美融合了传感器技术、控制理论和嵌入式编程。选择STM32作为主控芯片是因为它兼具性价比和性能优势——以STM32F103C8T6为例72MHz主频、20KB RAM和64KB Flash的资源足够处理实时控制任务而价格仅需十几元。MPU6050则是六轴运动传感器的经典之选内置三轴陀螺仪和三轴加速度计通过I2C接口输出原始数据配合DMP数字运动处理器还能直接输出融合后的姿态角。我第一次尝试做平衡小车时用的是Arduino Uno和MPU6050的组合。虽然能跑起来但响应总感觉慢半拍。后来换成STM32定时器中断可以稳定在1kHz频率运行PID控制循环小车的稳定性立刻提升了一个档次。这让我深刻体会到在实时控制系统中处理器的性能瓶颈会直接转化为控制效果的瓶颈。2. 硬件搭建从零开始的物料清单2.1 核心部件选型主控板STM32F103C8T6最小系统板蓝色小板姿态传感器MPU6050模块带DMP功能电机驱动TB6612FNG或L298N前者效率更高直流电机带霍尔编码器的N20减速电机减速比1:30电源7.4V锂电池组AMS1117-3.3稳压模块这里有个坑要注意电机的扭矩一定要足够。我最初用的电机标称扭矩是0.8kg·cm结果小车稍微一倾斜就推不动后来换成1.5kg·cm的电机才解决问题。另一个经验是MPU6050模块最好选择带电平转换的版本因为有些模块是5V供电但I2C引脚输出5V电平而STM32的IO口耐压只有3.3V。2.2 电路连接示意图STM32F103C8T6 ├── PA0/PA1 ── MPU6050(SCL/SDA) ├── PA6/PA7 ── 电机编码器A/B相 ├── PB6/PB7 ── TB6612 PWM输入 ├── PB8/PB9 ── TB6612 方向控制 └── 3.3V/GND ── 各模块供电实际接线时一定要给MPU6050和电机驱动板单独供电避免电机启动时的电压波动影响传感器精度。我曾因为共用一个电源导致MPU6050的数据时不时出现毛刺调试了半天才发现是电源问题。3. 软件架构三环PID控制详解3.1 姿态解算从原始数据到角度值MPU6050输出的原始数据是角速度和加速度需要通过算法融合成稳定的角度值。最简单的互补滤波算法如下float angle; // 全局变量保存当前角度 void update_angle(float gyro_y, float accel_angle, float dt) { float gyro_angle angle gyro_y * dt; // 陀螺仪积分角度 angle 0.98 * gyro_angle 0.02 * accel_angle; // 互补滤波 }但在实际项目中我推荐使用MPU6050自带的DMP库。它通过四元数解算姿态稳定性远胜于简单的互补滤波。初始化代码如下mpu_init(); // 初始化I2C mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL); // 启用传感器 mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL); // 配置FIFO dmp_load_motion_driver_firmware(); // 加载DMP固件 dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_SEND_RAW_ACCEL); dmp_set_fifo_rate(100); // 设置100Hz输出 mpu_set_dmp_state(1); // 启用DMP3.2 PID控制器的实现平衡小车通常采用串级PID控制内环直立环负责保持车身垂直外环速度环控制小车移动。先来看直立环的实现typedef struct { float kp, ki, kd; float integral; float last_error; } PID; float pid_calc(PID* pid, float target, float measure, float dt) { float error target - measure; pid-integral error * dt; float derivative (error - pid-last_error) / dt; pid-last_error error; return pid-kp * error pid-ki * pid-integral pid-kd * derivative; }在1kHz的中断服务函数中调用void TIM2_IRQHandler() { static PID angle_pid {.kp40, .ki0, .kd0.5}; float angle get_mpu6050_angle(); // 获取当前角度 float output pid_calc(angle_pid, 0, angle, 0.001); // 目标角度为0 set_motor_output(output); // 设置电机输出 }4. PID参数调试从理论到实践的跨越4.1 直立环调试让小车站起来调试要遵循先P后D最后I的原则比例项P从小值开始如10逐渐增大直到小车能快速响应倾斜但不剧烈振荡。我调试时发现当P35时小车能在松手后保持直立但有明显抖动。微分项D加入D项抑制振荡。D值过大会导致高频振动过小则抑制不住P引起的振荡。经过多次尝试D0.5时效果最佳。积分项I直立环通常不需要积分项因为MPU6050的零偏可以通过校准消除。如果必须使用I值要非常小如0.001。4.2 速度环调试让小车走起来速度环的调试更有挑战性PID speed_pid {.kp0.8, .ki0.1, .kd0}; float target_angle pid_calc(speed_pid, target_speed, actual_speed, dt);这里有个关键技巧速度环的输出不是直接控制电机而是作为直立环的目标角度偏移量。例如当想让小车前进时速度环会输出一个前倾角度如5°直立环则会驱动电机前进以维持这个前倾状态。调试时发现一个典型问题小车会越来越快直到失控。这是因为速度环的积分项在不断累积。解决方案是给积分项设置限幅if (pid-integral 100) pid-integral 100; if (pid-integral -100) pid-integral -100;5. 常见问题排查那些年我踩过的坑5.1 电机啸叫问题现象小车站立时电机发出高频噪音。这是PWM频率与电机电感谐振导致的。解决方法调整PWM频率到20kHz以上人耳听不到在电机两端并联104电容吸收高频噪声5.2 角度漂移问题现象小车会慢慢向一个方向移动。可能原因电机输出不对称可以通过校准左右轮PWM补偿值解决地面不平加入转向环用Z轴陀螺仪检测偏航角速度进行补偿5.3 突然倾倒问题现象小车能平衡几秒后突然倒下。检查要点电池电压是否充足电压不足会导致电机扭矩下降传感器数据是否异常用串口打印MPU6050原始数据控制周期是否稳定用示波器测量中断间隔记得第一次成功让小车平衡时我激动地拍了视频发给朋友。但第二天再试时小车却怎么也站不起来了。后来发现是杜邦线接触不良导致MPU6050数据时有时无——这个教训让我意识到在嵌入式系统中硬件可靠性往往比算法更重要。6. 进阶优化从能跑到跑得好6.1 加入转向控制通过蓝牙接收遥控指令差速控制转向void handle_steering(float cmd) { float steer_output cmd * MAX_STEER; left_motor steer_output; right_motor - steer_output; }6.2 低通滤波处理传感器噪声MPU6050的陀螺仪数据存在高频噪声可以用一阶低通滤波float gyro_lpf(float new_value) { static float last 0; last 0.9 * last 0.1 * new_value; return last; }6.3 上位机调试工具用STM32的串口发送数据到电脑配合Python matplotlib实时显示曲线import serial import matplotlib.pyplot as plt ser serial.Serial(COM3, 115200) plt.ion() while True: data ser.readline().decode().split(,) angle, output float(data[0]), float(data[1]) plt.scatter(time.time(), angle) plt.pause(0.01)这个项目最让我有成就感的是当所有参数调好后轻轻推一下小车它会像不倒翁一样晃几下又稳稳立住——那一刻仿佛看到了物理定律在代码中具现化。建议每个嵌入式开发者都亲手实现一次平衡小车它会让你对控制理论有全新的认识。