简介:基于STM32的两轮自平衡小车纯平衡控制程序,面向嵌入式初学者和PID控制爱好者,用于学习从传感器采集到电机驱动的完整闭环控制流程。程序由博主实际调试完成,模块划分清晰,包含初始化配置、传感器读取、姿态解算、PID算法与电机控制等部分。资源共234个文件,约8.57MB,以C源码和头文件为核心,附带MDK工程配置、编译中间文件、Hex烧录文件及说明文档,便于直接阅读算法逻辑,也可快捷烧录到硬件上观察效果。已有2214人学习下载,适合想系统掌握STM32编程、陀螺仪与加速度计DMP姿态解算、PID参数整定和PWM电机驱动的开发者。工程中的I2C通信、定时器中断、ADC采集等底层细节均有体现,能够帮助理解倾斜角度如何实时映射为电机转速补偿;反复调整P、I、D参数还可直观对比响应速度、稳态误差与超调变化,为后续扩展蓝牙遥控或直立行走功能打下扎实基础。 先说个可能戳中不少人的场景:从某个论坛或网盘里下载了一个“平衡小车程序(纯平衡).zip”,解压之后发现里面是完整的STM32工程,心想这下省事了,直接烧录不就站起来了?结果要么电机疯狂抽搐两下然后躺平,要么一上电就往一个方向加速冲出去,还有的连串口都打不开。问题恰恰就出在这里——平衡车的程序只占整个项目成功的一半,另一半在机械结构、参数整定和调试方法里。这篇内容就是围绕“纯平衡”这个最基础的版本展开,讲讲这个程序包里到底有什么、每个模块在干什么,以及怎么把它调得真正能站起来。
如果你手里正好有这样一个压缩包,或者正准备自己做一台两轮自平衡小车,这篇内容适合你。我会按“拆包看结构—理解控制逻辑—按顺序整定参数—排查翻车现场”这条线走,争取让你少走几个月的弯路。
1. 拿到“纯平衡.zip”后的第一件事:拆包,别急着烧录
1.1 压缩包内部的典型工程结构
一般来说,网上流传的纯平衡程序包都是基于STM32F103系列主控的,工程结构大概长这样:
Balance_Car/ ├── HARDWARE/ │ ├── MOTOR/ // PWM输出、电机方向控制 │ ├── ENCODER/ // 测速,速度环反馈来源 │ ├── MPU6050/ // 姿态传感器驱动,读取角速度和加速度 │ └── OLED/ // 显示调试信息 ├── SYSTEM/ │ ├── delay/ // 延时函数 │ ├── sys/ // 时钟配置、中断分组 │ └── usart/ // 串口打印,调试必备 ├── USER/ │ ├── main.c // 主循环、状态机 │ ├── control.c // 核心:PID计算、姿态环控制逻辑 │ └── inv_mpu6050.c // DMP姿态解算库 └── README.txt先花半小时把每个文件打开扫一遍,比直接烧录有用得多。你至少要在心里回答三个问题:这个程序用的是定时器几的PWM输出?编码器接在哪个定时器的哪个通道?MPU6050是读DMP的四元数还是自己写的滤波?这三个问题的答案,决定了程序能不能跑在你的具体板子上。
1.2 “纯平衡”到底砍掉了什么
“纯平衡”这个词暗示了它只是完整功能的子集。通常网上流传的平衡车程序按复杂度分为三档:纯平衡、平衡+遥控、平衡+循迹。纯平衡版本只保留了核心的直立环和速度环,转向环往往是预留接口或者直接禁用。这意味着你拿到的是一个最适合学习的“最小可用系统”——代码量少、逻辑清晰、容易定位问题。
我见过不少人直接拿“平衡循迹”这种大而全的程序来入门,结果连哪个文件负责循迹传感器采集都找不到,更别提改参数了。纯平衡版本存在的意义就是让你把注意力集中在“如何站住”这一件事上,等它稳定站住了,再往这个骨架上加蓝牙遥控、加循迹模块,反而是很自然的事。
1.3 烧录前必须检查的四处关键配置
代码里通常有宏定义开关或者条件编译段,这些配置跟你的具体硬件强相关,不改的话大概率直接翻车:
- 电机方向宏:有的程序里定义了
MOTOR_DIR或者LEFT_MOTOR_DIR之类的宏,正负号决定电机往哪边转。如果方向反了,平衡车会表现出“越调越晃,然后加速倒下”的诡异状态,因为控制量在正反馈地作用。 - PWM频率和分辨率:常见的是10kHz到16kHz的PWM频率。频率太低电机会有明显啸叫声,频率太高则驱动芯片可能跟不上。确认你的驱动板(TB6612、DRV8833、L298N等)支持当前频率。
- 定时器通道映射:每个定时器的通道是固定的,拷贝别人的工程时最容易出错。如果PWM没有输出,先查这个。
- PID初值:很多程序包里会写着一组看起来能用的参数,但那是在作者的车架、电机和电池条件下调的。轮子直径不同、电机转速常数不同、重心高度不同,都会让同一组参数的表现天差地别。
2. 控制逻辑拆解:为什么纯平衡也要用“双环”
2.1 平衡车的物理本质是不稳定的倒立摆
要调好平衡程序,先把物理模型想清楚。两轮平衡车本质上是一个轮轴在上、重心在下的倒立摆(实际上重心的位置是在轮轴上方,整体结构像一个倒过来的钟摆)。你让它直立,它天然就是不稳定平衡——任何微小的倾斜都会因为重力力矩而进一步倾斜,最终倒下。
控制系统的任务只有一个:检测到车身倾斜后,让轮子朝倾斜方向加速运动,使底盘“追”着重心跑。这就是“动态稳定”的概念——静止状态下它其实一直在前后微调,看起来像站稳了,实际是在做持续的微小修正。
这个过程就是串级控制的最佳教学案例:内环叫直立环(也叫角度环),负责响应“车往哪个方向倒了多大角度”;外环叫速度环,负责解决“车虽然在站,但整体在朝一个方向慢慢溜走”的问题。两个环配合,才是一个完整的纯平衡控制。
2.2 直立环——PD控制,负责“不倒下”
直立环的输入是车身倾角和倾角变化率,输出是电机PWM的基础值。公式长这样:
直立环输出 = Kp_angle * angle + Kd_angle * angular_rate- Kp(比例项):角度偏差越大,电机修正力量越强。这就像一个“弹簧”,把车拉回垂直位置的力度和当前偏移角度成正比。
- Kd(微分项):对角度变化率起作用,相当于“阻尼”。如果没有它,车会在竖直位置附近来回振荡,而且振荡幅度越来越大,直到失控。
实际调的时候你会发现,Kp太小车软绵绵的,稍微推一下就倒;Kp太大车会像筛糠一样高频抖动,电机发热严重。Kd的作用就是抑制这种高频抖动,让修正动作变“黏滞”、变果断。
2.3 速度环——PI控制,负责“不乱跑”
直立环调好后,车能勉强站稳,但你会发现它会缓慢朝一个方向溜过去,用手推一下也不会停下来。因为单纯的角度闭环并没有解决“整体位移”的问题——车保持直立的同时可以在任何位置。
速度环的输入是编码器测得的实际速度(通常左右轮平均),目标是让速度为0,输出会作为角度目标值的修正量叠加到直立环上:
目标角度 = 0 + 速度环输出 直立环输出 = Kp_angle * (目标角度 - 当前角度) + Kd_angle * 角速度这是非常多新手容易搞混的地方:速度环的输出不是直接加到最终的PWM占空比上,而是加到直立环的目标角度上。你可以这样理解:当车往右溜,速度环检测到正向速度,就把“目标角度”往左偏一点,让车身微微左倾,依靠倾斜产生的水平分量把车拉回来。
PI控制里的I(积分)项用来消除静差。如果车子总差一点点速度才能回到零,积分项会缓慢累积修正量,直到速度真正归零。纯平衡程序里速度环的P和I一般都比较小,因为车身本身就是通过倾斜来调整速度的,惯性大、响应慢,参数太激进反而会引起震荡。
3. 参数整定的实操顺序:从“颤抖”到“如履平地”
3.1 第一步:先让直立环“敢扶”车
拿到程序之后,第一件事是把速度环暂时禁用(注释掉控制周期里的速度计算与叠加),只保留直立环。然后把Kp和Kd设成比较保守的初值,比如Kp_angle = 60,Kd_angle = 0.5。
接着把车拿在手里,让轮子悬空,目视车身保持竖直状态。如果程序正常,你会看到电机在轻微地来回修正——轮子会随着你手动倾斜车身而主动转动,试图让车身回到竖直位置。这个过程叫“手扶法”。
这里有个判断标准:当你把车身慢慢向前倾斜,车轮应该同步向前加速;向后倾斜,车轮应该向后加速。如果方向相反,就直接改电机方向宏,不要怀疑控制逻辑写错了,绝大多数都是硬件方向没对上。
3.2 第二步:手扶着“站”起来,观察振荡
把车放在地上,双手扶住车架让它勉强站直,然后慢慢松手,观察动作:
- 一松手就朝一个方向加速倒下:Kp太小,回复力不足,车直接“趴下”。增大Kp。
- 来回剧烈摆动,越摆越大:Kp偏大,系统回到竖直位置时积累了过多动量冲过头。先增大Kd,若仍不行再减小Kp。
- 高频抖动(滋滋响):Kd对噪声太敏感,或者滤波延时过大。适当降低Kd,或者检查MPU6050的输出频率。
这一步是最考验耐心的。我调第一台车的时候,光直立环就花了两天时间,每次改参数都要反复上电、扶车、观察、再改。建议你每次只改一个参数,而且把改动记下来,否则一旦调乱了,根本不知道是哪个改动导致了现状。
3.3 第三步:恢复速度环,解决“溜车”问题
直立环稳定后,把速度环的代码恢复回来。速度环通常给出很小的初值,比如Kp_speed = 10,Ki_speed = 0.5。
速度环调校的核心观察指标是:车在完全静止状态下,缓慢漂移的速度是收敛还是发散。如果车仍然朝某个方向慢慢溜,或者绕着一个点打转,可以适当增大Ki来消除静差。如果车出现了低频振荡(前后摇摆,周期大概一两秒),说明速度环的P或I太大了,响应过度,往回缩一缩。
这个过程中有个很容易混淆的点:电机存在死区——当PWM小到一定程度,电机根本不转。这会导致速度环的反馈在小速度时失真,车表现为“起步迟钝,一冲一冲”。解决方式是做死区补偿:在电机驱动函数里,当PWM绝对值小于某个阈值(比如5%)时,直接输出阈值,并且用平滑处理避免跳变。
下面这个表格是我在实际调试中用的结论,可以当作起点参考:
| 参数 | 作用 | 典型范围(STM32+TB6612+编码器电机) | 整定表现 |
|---|---|---|---|
| Kp_angle | 回复力/弹簧刚度 | 30 ~ 100 | 太小易倒,太大会抖 |
| Kd_angle | 阻尼/抑制振荡 | 0.2 ~ 2.0 | 太小振荡发散,太大迟钝 |
| Kp_speed | 速度负反馈强度 | 5 ~ 30 | 太大低频前后晃 |
| Ki_speed | 消除速度静差 | 0.1 ~ 1.0 | 太大绕圈漂移加剧 |
3.4 关于采样与控制周期的一个提醒
多数的平衡控制主循环跑在200Hz到500Hz之间(也就是2到5毫秒执行一次PID计算)。如果你的主循环里塞了OLED刷新、串口打印或者其他耗时操作,控制周期会变得极不稳定,表现就是参数怎么调都别扭。
我见过一个案例:OLED显示函数里有软件延时,加起来让控制周期从2ms变成了7ms,平衡车怎么调都抖个不停。后来把显示改成定时刷新、控制中断里只跑姿态采集和PID,问题瞬间消失。纯粹的控制循环里不要放任何阻塞式操作,这是平衡车项目的铁律。
4. 代码里看不到、但决定成败的隐形变量
4.1 姿态传感器的数据质量和滤波
MPU6050返回的原始数据不能直接用,因为车身振动会产生大量高频噪声。常见做法有两种:DMP解算和互补滤波。
- DMP模式:直接读MPU6050内部DMP算出的四元数,再转成欧拉角,优点是省MCU资源、输出稳定,缺点是受一些寄存器配置影响,个别程序里读数会跳变。
- 互补滤波:把加速度计的长期稳定信息与陀螺仪的短期精准信息融合,公式简洁、易于理解:
角度 = 互补系数 * (角度 + 陀螺仪角速度 * dt) + (1 - 互补系数) * 加速度计角度互补系数通常取0.95 ~ 0.98。系数越接近1,越信任陀螺仪积分的结果,这会减小噪声但也会放大零点漂移;越接近0,越信任加速度计,会引入振动噪声。这是一个必须靠实验取折中的参数。
另外还有一个容易踩的坑那就是陀螺仪零漂。每次上电,陀螺仪的零点可能都会偏移一点。如果不校准就开始控制,速度环的反馈会始终带一个小偏差,导致车总要不停修正。好的程序一般会有上电校准流程:静止采样几百个点,算出gyro零偏,然后减去这个零偏再参与控制。
4.2 供电和电机噪声对单片机的致命影响
车一启动,两个电机同时加速,瞬时电流动不动就一两安培。如果电池电压波动太大,电源引脚上的噪声会让ADC读数抖动、让单片机复位,甚至让MPU6050的输出突然跳到错误值——这不仅影响姿态解算,严重时程序就直接卡死了。
所以纯平衡程序能不能稳定运行,供电设计占了很大比重:电机驱动单独用电池供电,STM32和传感器用稳压模块分开供电,两块电源共地即可。如果你只有一路电源,也至少要在电机驱动板的电源输入端并一个大容量电解电容(470uF或更大),同时在单片机的3.3V处加强滤波电容。
4.3 机械重心高度对参数的放大效应
同一个程序,如果把电池从车架底部抬高到立杆顶部,整组PID参数可能就完全失效。原因是重心越高,重力产生的倾斜力矩越大,系统越不稳定,需要更大的修正力度和更强的阻尼。所以调试时尽量把电池固定牢靠,不要用两根扎带随便绑;重心越低,车越好调,这是纯平衡程序最容易上手的前提。
轮子的摩擦力也常常被忽略:一个磨损不均的轮子会导致两个电机负载不一致,左右轮反馈速度不同。速度环闭环之后,车可能会出现“往一边绕圈”的症状。先检查轮子按上去是不是还有均匀的纹路,再考虑是不是PID参数该左右分别校准。部分程序中左右轮的控制是有独立PID参数的,实战中左右差异明显时,这比费劲找一个全局折中值有效得多。
5. 实测中常见的“站起来就倒”现象与排查顺序
5.1 一套高效排查流程图
我把平时调试的排查顺序整理成了表格。当你面对“站不起来”的情况时,按这个顺序查能省下大量时间:
| 优先级 | 现象 | 原因 | 对应检查项 |
|---|---|---|---|
| 1 | 一上电就朝一个方向加速冲出去 | 电机方向/角度方向反了 | 检查电机方向宏、传感器安装方向 |
| 2 | 来回抖动、幅度越来越大 | PID参数不合理 | 禁掉速度环,先手动调直立环 |
| 3 | 能站住但一直朝某方向缓慢漂移 | 速度环未生效或零偏未修正 | 检查编码器计数、速度环输出 |
| 4 | 被碰一下缓慢倒下,不恢复 | 阻尼过强或响应太慢 | 降低Kd,检查滤波带来的相位延时 |
| 5 | 参数怎么调都抖,跟理论对不上 | 控制周期堵塞、电源波动 | 检查主循环是否有延时、电池电压 |
5.2 每个现象背后的关键判断细节
“一上电就往一个方向打死”是最常见的错误,原因大概率不是PID参数,而是方向搞反了。你现在可以做一个动作作为验证:手动把车倾斜一个角度,看车轮是否向着“让车身回正”的方向滚动。如果车轮朝相反方向滚动,那么控制环在帮倒忙——车身越歪,修正推力越大,结果就是瞬间打满冲出去。这也解释了为什么有人把Kp调到0.1车还会冲出去:不是修正过猛,而是正反馈。
“发抖但幅度不大”通常是Kp偏大、Kd不足。典型的特征是电机发出高频嗡嗡声,车身在竖直位置附近以肉眼可见的频率颤动。这时候先加大Kd,再考虑减少Kp;如果加Kd之后抖动反而变“闷”了,说明阻尼起效了,可以逐步推进到下一个参数。
“能站住但会慢慢绕圈”就要分别检查左右轮是否对称。先在代码里打印左右编码器计数,空转比较单位电机的转速;如果转速差异过大,调整左右轮的速度比例系数,而不是去改速度环的全局Ki。
“串口什么都打印不出来”时,先查MPU6050的I2C地址是否正确。很多MPU6050模块的地址引脚(AD0)默认接地,地址是0x68;有些高配模块默认接高,地址为0x69。程序里选错地址,初始化就过不去,更谈不上后续的PID控制了。
5.3 最后一招:多平台对比交叉验证
手头如果有示波器,可以把“当前角度”和“输出PWM”两路数据通过DAC或逻辑分析仪抓出来看。正常平衡状态的输出波形应该是高频微小修正叠加偶尔的中幅摆动;如果你看到输出一直贴着饱和值(比如PWM满幅或0),说明控制量已经打满,机械响应跟不上,问题大概率在参数之外——要么重心太高、要么电机转矩不足,要么电池电压跌得厉害。
如果没有示波器,退而求其次的办法是让小车斜靠墙壁站立,用手推它一下,让它小范围摆动,从恢复速度和超调幅度判断参数方向。这个方法虽然简陋,但在没有专业仪器的情况下足以判断出哪个环出了问题。
写在最后的一段经验
调平衡车,说难是真难,说简单其实也就那几步:先确认物理方向正确,再调直立环站稳,最后加速度环锁位置。我见过不少人一上来就找代码库要完整参数,抄过去发现还是倒,然后怒斥程序有问题——但真相往往是他们用的传感器安装角度和原程序差了180度,或者电池没充满导致电压不稳。
如果你手上正好有这样一个“纯平衡”程序包,先从把“速度环禁止”这一行注释恢复成禁用状态开始吧,力争让车身在2秒内不倒下。这个小目标达成之后,后面的路就顺了。下次再听到有人问“为什么我的车往一边猛冲”,你就可以直接把这篇内容的排查顺序丢给他了。
本文还有配套的精品资源,点击获取