两轮自平衡小车:MPU6050配PID怎么调稳

2026-10-31 20:14:31 43阅读

两轮自平衡这件事,难的不是把车拼起来,是把那三个字母调对。MPU6050 一上电就会告诉你"我现在歪了 12 度",可这 12 度该换成多大的 PWM 给电机,才是真正卡人的地方。Circuit Digest 上 Aswinth Raj 那篇 DIY Self Balancing Robot 把整套流程写全了:器件清单、接线表、DMP 初始化、PID 三个参数怎么一个个加上去。这篇按他的顺序重新捋一遍,把容易翻车的点标出来。

先给个量级:Arduino UNO 加两个黄色减速直流电机、L298N、MPU6050、7.4V 锂电,作者报的整机成本 30 到 50 美元,做完 8 到 12 小时,难度标的是中级偏上。他自己也承认电机首选其实是步进,直流减速是"为了简单"的妥协;轮子抓地不好,PID 调到手抽筋也白搭。

两轮自平衡小车整机实拍,Arduino UNO 与黄色减速电机

这台就是作者最后调通的样子:两块亚克力板夹着电池和洞洞板,后面插着 USB 线,方便边调边看串口

一、先把账算清:器件怎么挑

器件规格挑的时候注意什么
主控Arduino UNO板载稳压,7.4V 可以直接进 Vin
姿态传感器MPU6050,GY-521 模块六轴带 DMP,别拿纯加速度计凑合
电机黄色减速直流电机 ×25V 到 12V 都能转,便宜但齿隙大
驱动L298N 双 H 桥四路输入全接 PWM 脚
电池7.4V 锂电轻,且能省掉升压模块
车体3D 打印件或板材刚性优先,晃一下就别想调出来

控制器选 UNO 的理由很实在:不用外接下载器,插上 USB 就能改代码、看串口。换成 Nano 也能跑,只是调试时多一层转接,作者的建议是新手先别省这一步。

电机这段值得多说。自平衡车理论上最好的选择是步进电机,没有齿隙、没有死区、力矩曲线干净。但作者为了压低门槛,用的是最常见的那种黄色减速直流电机。代价很明确:减速箱有回差,低速段有死区,PID 输出在 60 以下的 PWM 可能根本推不动轮子,参数就得在能推动的区间里找。

传感器没有替代方案,就是 MPU6050。他专门点了一句:别想着用 ADXL345 这类纯加速度计顶替。纯加速度计静态能算倾角,车一动起来全是加速度噪声,分不清"车在倾斜"和"车在加速",这个坑绕不过去。

MPU6050 陀螺仪加速度计模块 GY-521

GY-521 模块,MPU6050 就在这块蓝板上,走 I2C,VCC、GND、SCL、SDA 之外还引了一根 INT

二、接线与装配:六根信号线,重心往下压

接线表基本可以照抄。MPU6050 走 I2C,占 A4、A5,中断接 D2;L298N 的四路输入接 D6、D9、D10、D11,全部是 UNO 的 PWM 脚,因为调速要靠改占空比。

模块引脚接到 UNO
MPU6050 VCC+5V
MPU6050 GNDGND
MPU6050 SCLA5
MPU6050 SDAA4
MPU6050 INTD2
L298N IN1 / IN2D6 / D9
L298N IN3 / IN4D10 / D11

供电这段他讲得最细:电池 7.4V 正极一路进 Arduino 的 Vin,一路进 L298N 的 12V 端子;UNO 板载稳压把 7.4V 降到 5V,ATmega 和 MPU6050 都吃这一路。电机标称 5 到 12V,实际就跑在 7.4V。共地必须做,地没接好,I2C 读出来不是 0 就是一串随机数。

方向靠 IN1 到 IN4 的高低电平组合决定,速度靠 PWM 占空比,而这个占空比就是后面 PID 算出来的 output。所以这四根线接错一根,车不是不动,是原地打转。

Arduino UNO 与 MPU6050 L298N 自平衡小车接线图

接线走线示意:电池、UNO、MPU6050、L298N 和两个电机的相对位置,红黑是电源,彩色是信号

结构比电路更容易被低估。作者的车体是自己用 SolidWorks 画的,STL 挂在 Thingiverse 上,没有悬垂结构,25% 填充就能打,电机和板子用 3mm 螺母螺栓锁死。重心这条是硬要求:电池和驱动板尽量压到底层,整机高度别超过底盘长度太多。

他实际装的时候把电池和 UNO 的位置对调了,图的是插 USB 改程序方便,还加了一块洞洞板走线,所以最后的样子和最初的设计图并不一样。这种"装到一半改方案"的细节,比漂漂亮亮的效果图有用。

两轮自平衡小车硬件连接与三层结构实拍

三层结构,底层电机和驱动板,中层主控,顶层电池,重心全压在轮轴附近

还有一处他吃过亏:最初那对"超级朴素"的塑料轮子抓地太差,车在原地打滑,PID 怎么调都像在抽搐,换轮子之后才正常。这条对后来人价值最大——先确认轮子不打滑,再谈参数。

三、角度从哪来:DMP 出 yaw,PID 出 PWM

MPU6050 给两种数据。加速度计测重力方向,静态准、动态全是噪声;陀螺仪测角速度,短时准、时间一长就漂。单用哪一个都不行,必须融合。作者没自己写互补滤波,直接用芯片里的 DMP(数字运动处理器),把融合交给硬件,Arduino 只管读四元数再换算成 yaw、pitch、roll。

代码是站在 jrowberg 的 MPU6050_DMP6 例程上改的,PID 用 br3ttb 的 PID_v1 库。三个库先装进 Arduino 的 libraries 目录,再重启 IDE。

#include "I2Cdev.h"
#include "PID_v1.h" //From https://github.com/br3ttb/Arduino-PID-Library/blob/master/PID_v1.h
#include "MPU6050_6Axis_MotionApps20.h" //https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050

DMP 初始化里有一组偏移量必须换成自己传感器的值:X 轴 220、Y 轴 76、Z 轴 -85、Z 轴加速度 1688,这是作者手上那颗芯片的,同一批货也可能不一样,得单独标定。偏移不对,车会把"歪着"当成竖直,setpoint 怎么设都别扭。

参数区就四个数:setpoint 176、Kp 21、Kd 0.8、Ki 140。setpoint 不是理论上的 180,因为传感器贴上去有装配误差,得让车真的立住,再读串口显示的角度值填回来。这也是为什么换一台车、换一个传感器,这几个数字就得重来。

主循环的逻辑很短:等 DMP 中断,没新数据就先算一次 PID,把 input 和 output 打到串口;只要角度落在 150 到 200 之间,也就是车在倒,output 为正就往前推,为负就往后推;超出这个区间直接停电机。

    while (!mpuInterrupt && fifoCount < packetSize)
    {
        //no mpu data - performing PID calculations and output to motors    
        pid.Compute();  
       
        //Print the value of Input and Output on serial monitor to check how it is working.
        Serial.print(input); Serial.print(" =>"); Serial.println(output);
              
        if (input>150 && input<200){//If the Bot is falling
         
        if (output>0) //Falling towards front
        Forward(); //Rotate the wheels forward
        else if (output<0) //Falling towards back
        Reverse(); //Rotate the wheels backward
        }
        else //If Bot not falling
        Stop(); //Hold the wheels still
    }

三个电机函数是纯手工 PWM。注意 Reverse 里把 output 乘了 -1,因为 analogWrite 只吃正数,负值传进去行为不可预期,这一步漏了车只会往一个方向冲。

void Forward() //Code to rotate the wheel forward
{
    analogWrite(6,output);
    analogWrite(9,0);
    analogWrite(10,output);
    analogWrite(11,0);
    Serial.print("F"); //Debugging information
}
void Reverse() //Code to rotate the wheel Backward 
{
    analogWrite(6,0);
    analogWrite(9,output*-1);
    analogWrite(10,0);
    analogWrite(11,output*-1);
   Serial.print("R");
}
void Stop() //Code to stop both the wheels
{
    analogWrite(6,0);
    analogWrite(9,0);
    analogWrite(10,0);
    analogWrite(11,0);
    Serial.print("S");
}

四、调参:先 P,再 D,最后 I

顺序不能乱。Kp 从 0 开始慢慢加,加到车开始围绕平衡点来回摆;这时候加 Kd 把摆动压住;最后才加 Ki 消掉长时间站偏。作者的起点是 Kp 21、Kd 0.8、Ki 140,他自己也讲没有两台车参数一样,照抄只是拿个量级。

调的时候 USB 线别拔,串口 115200 波特率开着,看的就是 input => output 那一行。车立住时 output 接近 0;往前倒 output 变正,串口跟着打 F;往后倒 output 变负,打 R;没在倒就打 S。这三个字母比看车更快,方向反没反一眼就知道。

自平衡小车 Arduino 串口监视器 PID 调试读数

串口这一屏就是调参的全部依据:左边角度,右边 PID 输出,末尾的 F、R、S 是电机状态

三个参数给过头的表现很好区分:Kp 大了会高频抖,车像通电的按摩棒;Kd 大了会迟钝发飘,推一下要晃半天才回来;Ki 大了会越摆越猛,最后直接拍地。真要省事,先把 setpoint 校准到正负 1 度以内,再动 PID。

五、调不稳时,先回查这几处

作者在问答里把翻车原因归了几类,按排查成本从低到高排:

  • 轮子打滑、胎面太硬,先换轮子,别急着改代码
  • 重心太高或电池偏在一侧,PID 还没反应过来车已经倒了
  • 供电不足,插 USB 能跑、上电池就抽,多半是电池放电能力不够或者地没共好
  • 传感器没标定,偏移量还留着别人的值

另外两个点容易被忽略。一是车体刚性,板材一受力就扭,等于给 IMU 喂了一个假信号,这时候 PID 越调越乱。二是 setpoint 抄了别人的 176,而你自己那台的机械零位可能差好几度,车永远差一点才站直。

整套东西拆开看没有玄学:一个六轴传感器告诉你歪了多少,一个 PID 把这笔误差换算成 PWM,两个减速电机负责把轮子往歪的方向推。真正费时间的只有两件事——把重心和轮子弄扎实,把 Kp、Kd、Ki 一个个加上去。作者说 8 到 12 小时,我判断前两小时在装配,剩下全泡在调参上,这个比例基本属实。

如果你的目标是"先让它站起来",直流减速电机这套方案够用;如果想让它在斜坡上也不飘、被推一下能自己找回来,那就得往步进或者带编码器的方案走,代价是驱动和代码复杂度都上一个台阶。


作者:舵机老炮
链接:https://www.aiddithome.com/p/7b1091f935f2a.html
本作版权归作者所有,未经允许请勿转载。若存在违规行为,您可以点击 “举报”。


登录 后发表评论
4条评论
胶佬憨憨
1楼 · 2026-10-31 20:15:14

setpoint 176 这个数我一开始也想不通,后来才反应过来是装配误差,传感器贴歪两度,竖直就不是 180 了。

调参到凌晨
2楼 · 2026-10-31 20:15:14

L298N 那四路全接 PWM 脚这点容易忽略,我第一次图省事接了普通 IO,结果只能全速跑,压根调不了速。

隔壁工位的好奇宝
3楼 · 2026-10-31 20:15:14

调参顺序讲得清楚。我当初上来先动 Ki,车越调越猛,最后直接往前扑地,绕了一大圈才回到先 P 后 D。

老张装机
4楼 · 2026-10-31 20:15:13

去年也照这个思路搞过一台,最后卡在供电上:插 USB 一切都好,一拔线换 7.4V 电池就开始抽搐,查了半天是电池放电能力不够,电机一端瞬间拉低电压,主控跟着复位。后来在电机电源端并了个 470uF 才好。作者说轮子打滑这点太真实了,我一开始那对塑料轮子根本抓不住地,PID 怎么调都在原地抖,换了橡胶胎才谈得上调参。