交互式模拟器中级

自平衡机器人模拟器:倒立摆 PID 控制

在浏览器里让一台两轮机器人保持平衡。把它拖离竖直方向、推它一把,再把级联 PID 的增益调坏,亲眼看清真实的平衡车是怎么倒下的。

类别
控制系统
时长
2–4 小时
平台
浏览器 · Arduino
自平衡机器人模拟器:倒立摆 PID 控制 技术示意图θTILT → PID → DRIVE UNDER IT

01 / 从这里开始

简介

自平衡车就是一个倒立摆:它的质心位于车轴上方,所以静止站立是一种不稳定平衡,只有主动控制回路才能维持。把机身拖倒再松手、推它一把,或者把微分增益调到零,看振荡越来越大,直到它撞上地板。曲线图把倾角和一条临界角度画在一起——一旦越过这个角度,再大的电机扭矩也救不回来。

Live lab / Inverted pendulum & cascaded PID

Self-balancing robot simulator

A two-wheeled robot with its centre of mass above the axle — an inverted pendulum that falls the moment the loop stops. Drag the body over and let go, shove it, or detune the gains and watch exactly how it fails.

Browser native
A self-balancing robot lab. A two-wheeled robot stands on a floor with its mass above the axle. The control loop reads the tilt angle and drives the wheels to stay under the body. A rolling chart above the robot plots tilt over the last few seconds against the fall threshold. Use the controls below to shove it, drag it off balance, change the PID gains, or switch the loop off and watch it fall.

Drag the robot to lean it over and let go — the loop takes it from wherever you leave it. The dashed line is the tilt target the outer loop is asking for; the bar at the axle is the motor command. The trace above plots tilt against the ±35° fall threshold.

  • Measured tilt
  • Tilt target (outer loop)
  • Motor command
  • Fall threshold
Simulation

Ready. Press Start, then drag the robot over and let go.

Inner loop — tilt to motors
Outer loop & hardware
Tilt
0.0°
Tilt target
0.0°
Motor
0%
Position
0 cm
Upright for
0.0 s
Worst tilt
0.0°

Keyboard: focus the robot, then use Space to run/pause, S to shove it, L to switch the loop on or off, R to reset, and F for full screen.

控件

开始、暂停和重置用来运行这个回路。推像手指那样施加一次冲量,而环开关会同时禁用两个控制器,好让你看清裸露的被控对象是怎么倒下去的。

最直接的控制方式就是机器人本身:拖动机身到任意角度然后松手。你留下的那个位姿就是控制器接手的地方——没有重新布防这一步,而且增益滑块会立刻作用于正在运行的仿真。在它站住的时候把微分增益调到零,它就在同一秒里开始摇晃。

调参菜单会加载四个预设:一个能成功,以及三个分别以真实平衡车失败的三种方式失败的预设。

把画布聚焦之后,用空格、S、L、R 和 F 可以从键盘上使用同样的控制。

原理

让一台机器人用两个轮子站住,意味着要把一个倒立摆扶正。质心位于车轴上方,所以竖直是一个平衡点,其意义和一支立在笔尖上的铅笔一样:数学上真实存在,物理上没有持续修正就完全没戏。把它倾斜任何角度,重力都会产生一个让它倾得更远的扭矩。倾得越远,倒得越快。

机器人对抗这个扭矩只有一条办法——它可以移动车轴。轮子向前加速会把底盘推回质心下方,从而把机身转回竖直。这就是为什么这个修正一开始感觉是反的:要止住向前倒,你就必须向前开,就像你把手放在一把正在平衡的扫帚下面,而不是拿开它。

两幅并排的示意图。左边是一台两轮机器人偏离竖直方向前倾 theta 角,并有一个箭头表示重力让它倾得更远。右边是同一台机器人,一个箭头表示轮子朝着与倾斜相同的方向向前开,另一个弧形箭头表示机身从先前前倾的位置摆回竖直。
倒下是输入,不是故障:θ 是控制器读取的量,而把轮子开到倾斜的下方是抵消它的唯一办法。 下载 SVG

这就给机器人设下了一个硬极限。大约超过 35° 之后,所需的水平加速度会超过轮子在机身撞上地板之前所能提供的量,再多的增益也无济于事。曲线图上的虚线标出的就是这条边界。

算法

控制器是级联的——两个 PID 回路,一个喂给另一个。

一个框图。标为“位置变倾角”的外层 PID 块把一个倾角目标送入标为“倾角变电机”的内层 PID 块,内层把 PWM 送给标为“机器人”的块。测得的倾角以 200 赫兹从机器人返回内层回路,轮子位置以 20 赫兹返回外层回路。
是两个回路,不是一个。外层决定要倾多远,内层让这个倾斜真正发生。先调内层是唯一行得通的顺序。 下载 SVG

内层回路跑得快,负责保持一个倾角。它把测得的角度与目标作比较,并指令电机扭矩:

error   = measured_angle − target_angle
motor   = Kp·error + Ki·∫error + Kd·d(measured)/dt

注意误差是测量值减目标值,而不是教科书上的目标减测量。要接住一次向前倒,就必须把轮子向前开,所以一个正的倾角必须产生一个正的电机指令。这个符号是第一台平衡车一开机就把自己拍到地板上的最常见原因。

外层回路跑得慢,负责保持一个位置。它无法直接指令电机——电机已经被内层占用了——所以它改为给内层回路指令一个倾角目标:

target_angle = Kp_pos·(desired_position − position) − Kd_pos·velocity

当它落在想要的位置后面时,就要求一个小的前倾;内层回路随后驱动轮子保持这个倾角,机器人就向前移动。这个倾角被刻意限制在几度之内。一个被允许要求大角度的外层回路,会递给内层一个它保持不住的目标,于是两个回路一起垮掉。

积分项在这里比在大多数回路里都更重要,因为正是它吸收了 IMU 认为的竖直与真实竖直之间的差值。它也是为什么积分项必须在电机饱和时停止累积——否则它会在一次自己无力阻止的跌倒过程中饱和,然后在恢复阶段反过来与控制器作对。

源代码

// Inner loop: tilt → motors. Runs every 5 ms off a timer, not in loop().
float angleError = angle - tiltTarget;          // measured minus target
angleIntegral   = constrain(angleIntegral + angleError * dt, -3, 3);
float command   = Kp * angleError
                + Ki * angleIntegral
                + Kd * gyroRate;                // rate straight from the gyro

command = constrain(command, -255, 255);
if (fabs(angle) > FALL_ANGLE) command = 0;      // past saving; cut the motors
driveMotors(command, command);

// Outer loop: position → tilt target. Runs ten times slower, and may only
// ever ask for a small lean.
if (++slowTick >= 10) {
  slowTick = 0;
  float positionError = 0 - wheelPosition;
  tiltTarget = constrain(KpPos * positionError - KdPos * wheelVelocity,
                         -0.12, 0.12);          // radians, about ±7°
}

有两处细节可以直接从这个模拟器搬到实物上。微分项要取自陀螺仪角速度,而不要通过对角度做差分得到:陀螺仪直接测量这个角速度,而且没有微分运算会放大的那种噪声。另外,超过恢复角度之后要切断电机,而不是任由控制器继续硬撑——这既保护了减速箱,也让故障变得明显而不是狂暴。

硬件

传感器是 MPU6050,它同时包含加速度计和陀螺仪,因为单独任何一个都不可用。 加速度计知道哪个方向是下的,但会被机器人自身的运动淹没;陀螺仪能干净地跟踪 旋转,却在几秒内就漂移。互补滤波把两者融合 成一个既稳定又快捷的角度。

电机需要足够低的减速比来提供扭矩,又需要足够快来接住一次跌倒—— N20 减速电机在 200 RPM 左右是常见选择,由 TB6612FNG 驱动;当回路每秒要求几千次小修正时,它的低压降就很要紧。

在自平衡车项目里把它真正做出来; 如果 IMU 对你来说是新东西,就从倾角读取教程开始。

假设与硬件差异

这个模型是一个 2D 倒立摆,只有一个倾转轴、刚性连杆、没有轮子打滑, IMU 报告真实角度和角速度,只带上你调出来的漂移和噪声。真实的平衡车 会感到随电池电压变化的电机扭矩、编码器量化、IMU 安装偏移和温度漂移, 而且它还可能朝侧面倒——这是一个自由度,而本实验台把它完全去掉了。 这里展示的级联 PID 结构和陀螺角速度微分可以直接迁移,但安装配平、 滤波整定和跌倒角度截止值都必须在实物底盘上重新找出来。

电路图

一个接线框图。一块 Arduino Nano 通过 SDA A4 和 SCL A5 上的 I2C 向上连接一块 MPU6050 IMU,右侧通过 PWM 和方向信号连接一块 TB6612FNG 双路驱动器。驱动器连接一台带编码器的 N20 电机。编码器的 A 通道通过中断引脚 D2 返回 Arduino,一块标着“仅供电机”的电池接到驱动器的 VM 引脚。
一条 I²C 总线向上接 IMU,四根信号线横向接到驱动器,电机电源与逻辑电源轨分开。 下载 SVG

有两条接线规则决定这台机器人能不能工作。

**电机电池只接驱动器的 VM 引脚,别接别处。**电机电流绝不能流经 Arduino, 而且每一处地——板子、驱动器、IMU、电池负极——都必须连在一起, 否则控制信号没有共同的参考点,故障会悄无声息而不是显而易见。

**编码器从逻辑电源供电,而不是从电机电源轨。**用电机电源给编码器供电, 要么烧掉它,要么让计数里塞满开关噪声。还要把编码器的 A 通道接在一个 支持中断的引脚上;在 loop() 里轮询它会丢计数,而丢计数意味着外层回路 会慢慢跟丢机器人的位置。

把 IMU 装在靠近车轴的地方。装得高会把它在每次修正时看到的线性加速度 成倍放大,这只会让融合问题更难,却不带来任何好处。

硬件清单

元器件

  • Arduino Nano 或 Uno
  • MPU6050 加速度计与陀螺仪
  • TB6612FNG 双路电机驱动
  • 两个 N20 编码器减速电机
  • 2S 锂电池组

探索关联图谱

哪里用到这个模拟器

以这个实验台为基础的项目、学习路线和教程。

继续搭建

下载资源

完成项目时可以参考本页上的这些资料。可下载的项目包只会在其源文件和版本发布之后才会添加。

常见问题

常见问题解答

为什么平衡车需要两个 PID 回路,而不是一个?

因为你想控制的量和你能控制的量不是同一个。电机改变的是轮速,但让机器人不倒的是倾角,而改变倾角的唯一办法,就是把轮子移到机身下方。所以内层回路通过驱动电机来保持某个倾角,外层回路则请求一个小倾角,好让机器人去到你想要的位置。一个回路无法同时做到这两件事:如果直接根据位置误差驱动电机,机器人就会从自己的质心下方加速冲出去,立刻倒下。

为什么我的机器人看起来是平的,却会慢慢开走?

几乎总是安装配平的问题。如果 IMU 偏离真实竖直方向一两度,回路就会忠实地保持那个错误的角度,而保持一个恒定的倾斜,就意味着朝那个方向持续加速,直到没有地方可跑。在这个实验台里调节配平滑块,就能亲眼看到这一过程。在真实硬件上的解决办法是:把机器人扶到真正竖直的位置,测出偏移量并把它减掉,然后让积分项吸收剩下的漂移。

没有积分项也能保持平衡吗?

可以,模拟器也会向你展示这确实可行——直到有什么东西让测量值产生偏置为止。单靠 P 和 D 就能把机器人撑住,因为 D 提供的阻尼能阻止振荡越来越大。它们做不到的是消除稳态偏移:只要存在任何配平误差,机器人就会停在一个很小的永久倾斜上,然后慢慢溜走。把最后那一点误差驱动到零的,正是积分项。

为什么把微分增益设为零,机器人就会倒下?

因为再也没有东西为它提供阻尼了。比例增益只对机器人倒了多远作出反应,可等它修正过来时,机身已经带着速度摆过了竖直位置。没有一个对角度变化快慢作出响应的项,每一次摆动都会比上一次冲得更远,直到某一次超过恢复角度。在这个实验台里把 D 设为零,你就能数出它摆了几下。

为什么机器人向前倾时,轮子会向前开?

这正是整个诀窍所在,也是大多数人第一次尝试时会搞反的符号。要把一把扫帚立在手掌上,你得把手移到正在倒下的那一端下方,而不是移开。机器人也一样:向前倾就意味着要向前开,把轮子放回质心下方。把这个符号弄反,控制器就会主动把机器人推倒,看上去就是一次瞬间而剧烈的跌倒。

这和 PID 控制器模拟器有什么区别?

PID 实验台控制的是一个稳定的被控对象:不去管它,它就只是待在原地,所以回路要做的只是改善它到达目标的快慢和干净程度。这里的被控对象是不稳定的——不去管它,它每次都会倒下。这就把调参从一种优化变成了一项必需,也正因如此,平衡控制需要第二个外层回路,而单回路实验台从来不需要引入它。

延伸阅读

参考资料

权威资料,帮助你越过本模拟器有限的教学模型,进一步深入学习。