STM32两轮平衡车实战:MPU6050姿态解算与PID闭环控制
2026/9/15 11:35:37 网站建设 项目流程

简介:这套基于STM32的两轮平衡小车资料面向电赛备赛学生、嵌入式入门开发者及机器人爱好者,提供一套可落地的完整项目方案。资源以MCU源码为核心,包含大量C语言与头文件(.c/.h),覆盖姿态解算、电机控制、蓝牙通信等关键模块;硬件部分提供原理图(.schdoc)、PCB生产图纸及CAD文件,并附BOM表和姿态传感器设计说明,可支撑从打样到调试的全流程。压缩包共221个文件,除源码和图纸外,还包含蓝牙遥控器APK及源码、Keil工程配置(.uvproj)与备份文件,整体约29.34MB,结构划分清晰,便于按需查阅。目前已有120人学习下载,对于想快速搭建两轮平衡车原型或理解平衡控制算法的读者,是一份值得参考的工程资料。

1. 两轮平衡车的关键不在电机,在 5ms 实时闭环

两轮平衡车最容易让人误判的地方,是以为难点在电机驱动或机械结构。实际上电机驱动和结构件只要按图纸做都能转,真正决定车能不能站稳的,是 STM32 在每一个 5ms 控制周期里,能不能把 MPU6050 的姿态数据解算完、把三层闭环跑完。这套资料的主控工程直接以 IMU_AHRS 命名,一眼就能看出重心在姿态参考系统上;配套的 TWABV 工程、蓝牙遥控器 APK 及源码、CAD 生产图纸和 BOM 表,意味着拿到手能从硬件图纸一路做到 App 层改造。准备参加电赛、做毕设,或者想把 stm32 项目从「点灯」推进到「实时控制」的人,这套资料值得拆开看一遍。

2. 硬件电路设计细节:供电树、传感器接线与 PCB 避坑

2.1 器件选型与工程文件的对应关系

从资料正文里能看到bmp180.__iuarts.__iIMU_AHRS.uvprojTWABV.uvproj这些编译中间文件和工程文件,说明这套设计里有三个关键外围:BMP180 气压计、UART 串口(用于调试和蓝牙)、以及独立的主控工程和遥控器工程。主控 MCU 是 STM32F103 系列,具体型号以 Keil 工程设置里 Device 一栏为准,常见做法是 STM32F103C8T6,48 脚封装,72MHz 主频,足够跑姿态解算和三层控制环。

姿态传感器用的是 MPU6050 六轴(加速度计+陀螺仪),BMP180 挂在同一条 I2C 总线上,地址分别是 0x68 和 0x77,互不冲突。电机驱动选 TB6612FNG 这类低压 H 桥,相比 L298N 它的导通压降小得多,7.4V 锂电池供电时电机端电压更足,堵转时发热也小。蓝牙模块用 HC-05,走 UART 透传,和手机 APK 配对后直接收发协议帧。

2.2 供电树设计与 PCB 布局要点

整车的供电路径是:7.4V 两节锂电池 → 5V LDO 给 MCU、蓝牙模块和传感器 → MPU6050/BMP180 模块本身带 3.3V 稳压的话就无需额外 LDO。电机不经过 LDO,直接从电池正极取电,由 TB6612 的 VM 引脚供电,避免电机大电流把 MCU 电压拉垮。

PCB 布局上,电机驱动部分的地和信号地要单点连接。PWM 占空比突变时,电机电流会在功率地上产生毫伏级压降,如果功率地和 MPU6050 的地是同一段走线,加速度计读数会出现周期性跳变,表现就是车身高频抖动。电机焊盘附近放 100nF 陶瓷电容加 10μF 电解电容,吸收换向尖峰。另外 MPU6050 尽量靠近车体几何中心,且保证芯片 Z 轴垂直安装面,否则后续姿态解算要额外做安装角补偿。

2.3 接线表与上电检查步骤

以常见 STM32F103C8T6 蓝色 pill 板为参考,接线表如下:

外设信号MCU 引脚说明
MPU6050SDA / SCLPB7 / PB6I2C1,地址 0x68
BMP180SDA / SCLPB7 / PB6同一条 I2C 总线,地址 0x77
TB6612PWMA / AIN1 / AIN2PA8 / PB12 / PB13TIM1_CH1 输出 PWM
TB6612PWMB / BIN1 / BIN2PA9 / PB14 / PB15TIM1_CH2 输出 PWM
编码器A 相 / B 相PA6 / PA7TIM3 编码器模式
HC-05TXD / RXDPA10 / PA9USART1,注意交叉连接

注意 STM32F103 的硬件 I2C 有时会有总线锁死问题,如果解算时读到的加速度值全为零,优先怀疑 I2C 总线状态。我一般直接改用软件模拟 I2C,把 SCL 和 SDA 配置为开漏输出加上拉电阻,稳定性反而更好,尤其是在电机电磁干扰大的环境下。

上电后别急着下载程序,先量三处电压:电池端 7.4V、LDO 输出端 5.0V、模块 VCC 端 3.3V。然后用串口线接 USART1,打开 115200 波特率终端,正常启动时能看到初始化日志,包括 MPU6050 自检通过、BMP180 气压值、蓝牙模块状态。哪一步没打印就查哪一路,比焊完直接下程序再猜问题省时间。

3. 姿态解算落地:互补滤波与 AHRS 模块化设计

3.1 为什么姿态解算要单独做成 AHRS 模块

IMU_AHRS这个工程名拆开看是 IMU(惯性测量单元)加 AHRS(姿态参考系统),说明这套代码把「传感器驱动」和「姿态解算」分成了两层。传感器驱动只负责读原始数据,解算层只负责把加速度和角速度变成稳定的俯仰角,这样换传感器或者换算法都不影响上层控制代码。

BMP180 在平衡车里的作用容易被忽略。气压计能感知高度变化,常见做法是用它做悬空检测:当车被提起或者翻倒时,气压高度突变,同时编码器脉冲也停止,MCU 据此关闭电机输出,避免轮子空转加速。另一个用途是起步辅助,测量水平面高度差,防止车在斜坡上误判零位。

3.2 三种姿态解算方案怎么选

MPU6050 官方提供了 DMP 固件,F103 可以直接读四元数,很多工程图省事就挂 DMP。但 DMP 输出频率受 FIFO 配置影响,和 5ms 控制周期对不齐的话,读取时刻会抖动。卡尔曼滤波在小车这种线性近似系统里效果确实好,但协方差矩阵需要调 4 个参数,静态收敛速度和动态响应是矛盾的,调试成本高。互补滤波代码量最小,只有一个加权公式,权重系数有明确的物理意义,平衡车只关心俯仰角 pitch 和横滚角 roll,互补滤波完全够用。这也是资料里 IMU_AHRS 工程最可能采用的方式。

3.3 互补滤波核心代码与参数说明

以下代码逻辑对应资料中 AHRS 模块的实现思路,可在工程里IMU_AHRS工程目录下搜索AHRS相关源文件对照:

#define ALPHA 0.98f #define RAD_TO_DEG 57.29578f static float g_fPitch = 0.0f; // 融合后的俯仰角,单位:度 static float g_fGyroY = 0.0f; // 陀螺仪 Y 轴角速度,单位:度/秒 // gyroY_raw: MPU6050 原始 Y 轴角速度,FS_SEL=1 时灵敏度 65.5 LSB/(°/s) // accX_raw / accZ_raw: 原始加速度值,直接参与 atan2 计算 // dt: 控制周期,单位秒,5ms 中断时传 0.005f void AHRS_Update(short gyroY_raw, short accX_raw, short accZ_raw, float dt) { float accel_pitch = atan2f((float)accX_raw, (float)accZ_raw) * RAD_TO_DEG; g_fGyroY = (float)gyroY_raw / 65.5f; g_fPitch = ALPHA * (g_fPitch + g_fGyroY * dt) + (1.0f - ALPHA) * accel_pitch; }

互补滤波的本质是一个一阶低通滤波和一个一阶高通滤波并联:加速度计计算出的角度长期稳定但短期噪声大,陀螺仪积分出的角度短期精确但会随时间漂移。ALPHA越大越信任陀螺仪,动态响应越好,但静态漂移也越明显;ALPHA越小越信任加速度计,静态稳定但动态会显得迟钝。dt必须和实际中断周期严格一致,否则陀螺仪积分项会引入固定偏差。读加速度计的accX_rawaccZ_rawatan2f计算俯仰角,比先用反正切再拼接要更抗坐标轴串扰。

3.4 姿态解算结果怎么验证

代码下进去先别急着装车,把g_fPitch通过串口打印出来,频率 100Hz。车体水平放置时角度应接近 0,误差超过 2° 就检查加速度计零偏和安装方向。用手把车绕 Y 轴前倾 20°,串口数值应立即跟到 20° 附近,松手回到 0°,回程超调超过 3° 说明ALPHA偏大。静止 30 秒,角度漂移如果超过 1°,检查陀螺仪零偏补偿,一般要在初始化时采集 100 组陀螺仪数据取平均作为零偏,在积分前减掉。这步做完再进入控制环调试,否则 PID 参数再准也救不回来。

4. 三层闭环实现:直立、速度、转向的参数整定

4.1 控制拓扑与时序分配

两轮平衡车的控制结构是三个并联反馈环:直立环用 PD 控制车身俯仰角,速度环用 PI 控制轮子平均速度,转向环用 P 控制左右轮差速。三者输出叠加后分别给左右电机,而不是串联结构。直立环必须跑在最高优先级,资料里的 5ms 周期刷新一次是常见配置;速度环积分的本质是让车通过整体前倾或后倾来调节前进速度,周期放 20ms 甚至 50ms 都没问题,太长车会晃但不会倒;转向环和直立环同步刷新即可。一个常见的错误是把三个环算完再一起输出 PWM,直立环本身计算量小,但 UART 发送调试信息会打断时序,我一般会把串口打印放在主循环里用计数器降频到 20Hz。

4.2 Balance.c 核心实现代码

以下为简化后的控制逻辑,对应工程中 Balance 或 Control 模块:

#define PWM_MAX 720 int g_iBalancePwm = 0; int g_iSpeedPwm = 0; int g_iTurnPwm = 0; // 参数由外部整定,初始值参考 4.3 表 float g_fKAngle = 800.0f; float g_fKAngleSpeed = 2.5f; float g_fKSpeed = 30.0f; float g_fKSpeedI = 0.4f; float g_fKTurn = 1.8f; static int g_iSpeedIntegral = 0; // 5ms 周期调用 void Balance_Control(float pitch, float gyroY, int speedLeft, int speedRight, float turnRate) { int speedError = (speedLeft + speedRight) / 2; // 目标速度为 0 g_iSpeedIntegral += speedError; if (g_iSpeedIntegral > 1500) g_iSpeedIntegral = 1500; if (g_iSpeedIntegral < -1500) g_iSpeedIntegral = -1500; g_iBalancePwm = (int)(g_fKAngle * pitch + g_fKAngleSpeed * gyroY); g_iSpeedPwm = (int)(g_fKSpeed * speedError + g_fKSpeedI * g_iSpeedIntegral); g_iTurnPwm = (int)(g_fKTurn * turnRate); int pwmL = g_iBalancePwm + g_iSpeedPwm - g_iTurnPwm; int pwmR = g_iBalancePwm + g_iSpeedPwm + g_iTurnPwm; if (pwmL > PWM_MAX) pwmL = PWM_MAX; if (pwmL < -PWM_MAX) pwmL = -PWM_MAX; if (pwmR > PWM_MAX) pwmR = PWM_MAX; if (pwmR < -PWM_MAX) pwmR = -PWM_MAX; Motor_SetPWM(pwmL, pwmR); }

注意几个量纲问题:pitch单位是度,gyroY单位是度/秒,PWM_MAX对应定时器 ARR 值的一半,比如定时器 7200 分频 10 后满量程 720。这个表里g_fKAngleSpeed直接作用在角速度上,相当于 PD 控制的微分项,数值远小于比例项是正常的。积分限幅必须比 PWM 限幅小,否则速度环积分饱和后松开遥控器,车会以最大速度冲向一边。转向环的turnRate我习惯用陀螺仪 Z 轴角速度,比左右轮编码器差更平滑,也避免了差速计算在大占空比时溢出。

4.3 PID 参数三步整定法

参数作用建议初值调大后的现象
KAngle直立环比例项,决定恢复力600 ~ 1000车身高频抖振,电机发烫
KAngleSpeed直立环微分项,提供阻尼1.5 ~ 3.5回中变慢,转向迟钝
KSpeed速度环比例项20 ~ 50前后周期震荡,幅度变大
KSpeedI速度环积分项0.3 ~ 0.6回中粘滞,容易原地抽动
KTurn转向环比例项1.0 ~ 2.5急转向时车身侧倾明显

第一步只跑直立环,把速度环输出和转向环输出直接注释掉。手持车体在桌面正上方,KAngle从 300 开始往上加,车会开始自动找平衡点。如果车身来回摆动幅度越来越大,多半是KAngle符号反了,把角度反馈取反即可。加到车能在松手后原地站 2 秒以上,再加入KAngleSpeed,直到手推车身能迅速回正且不抖。第二步打开速度环,空载时看车是否会在原地缓慢移动,速度环积分项能消除静态误差,但积分增益过大会导致车周期性前冲后撤。第三步打开转向环,手机蓝牙发送左转/右转指令,观察转弯半径是否平滑。整个调试过程建议把蓝牙遥控和串口调参分开,避免遥控数据帧在控制周期内打断直立环。

4.4 蓝牙遥控链路与 APK 源码改造

蓝牙模块走 UART 透传,MCU 在串口接收中断里解析协议帧。协议格式是典型的 8 字节帧:AA 55 04 01 00 00 00 1A,前面AA 55是帧头,04表示后续数据长度,01是命令字,后两个字节分别代表前后速度(-100 ~ 100)和左右转向(-100 ~ 100),末位是校验和,等于前 7 字节累加取低 8 位。手机端 APK 源码直接搜AA 55就能定位到数据打包函数,下载下来的 APK 可以先安装看效果,再用 jadx 反编译确认协议细节,最后对比源码修改。需要改包名的话,Android Studio 里 Refactor 一下包名即可,但要同步改 manifest 里的声明以及蓝牙权限,Android 12 以上还需要在运行时申请BLUETOOTH_CONNECT权限,否则扫描不到 HC-05。

HC-05 上电后如果一直慢闪,说明没有配对。按住模块上的按键上电进入 AT 模式,此时快闪,串口发AT+UART=115200,0,0改波特率,AT+ROLE=1设为主机或从机,AT+PSWD=1234设置配对码。APK 扫描到 HC-05 后输入 1234 即可连接,连上后指示灯变为双闪。常见问题是 HC-05 默认波特率 9600 而源码里初始化 115200,串口打印全是乱码,这个先查波特率再查接线。

5. 源码复现与排错清单

资料里IMU_AHRS.uvproj是主控工程,TWABV.uvproj从命名看对应遥控器侧 MCU 固件,.uvgui_*.bak这类文件是 Keil 界面布局的备份,不影响编译可以忽略。先用 Keil 打开主控工程,确认 Device 型号和仿真器设置,ST-Link 选 SWD 模式,烧录前检查Utilities里的 Flash Download 配置。

现象原因处理
烧录报错 No Flash Algorithm Found芯片型号选错或 Pack 未安装安装对应 Device Family Pack,重新选择芯片
SWD 无法连接目标程序里把 SWD 引脚复用为 GPIOBOOT0 拉高复位进入 ISP 后重新连接
串口打印乱码波特率不匹配检查代码里 UART 初始化和终端设置
蓝牙模块一直慢闪不配对未进入 AT 模式或配对码错误按住按键上电进入 AT,重新配对
电机发出高频尖锐声PWM 频率过低或直立环比例过大检查 PWM 频率是否低于 10kHz,降低 KAngle

最后分享一个调试技巧:先用逻辑分析仪抓电机 PWM 输出,确认占空比极限值,再在直立环代码里加一个条件断点,当pitch大于 30° 时强制关闭电机输出。这样即使参数整定失败,车也只是倒下而不是飞出去。车能站稳之后,把串口输出的pitch用 pySerial 导成 CSV,画个曲线看稳态误差和超调量,比肉眼调参快得多。

提示:排错时优先关闭蓝牙串口中断。遥控器高频数据会在 5ms 控制周期里插入串口处理,导致直立环计算被延迟,车身会莫名抖动。把蓝牙数据接收改为主循环轮询,问题通常立刻消失。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询