Force Cart-Pole PID Lab
Control lab · inverted pendulum · force-commanded cart

Force Cart-Pole PID Lab

The same balancing problem, but now the PID output is a force in newtons pushed into a cart with mass. The gains take back their textbook roles. Change them while it runs, push the pole, and compare with the velocity-commanded version.

Balancing
Upright for0.0 s
RMS θ · last 5 s0.00°
Peak |F| · last 5 s0.0 N
Cart x · v0.00 m
Keys: ← → push, Space pause, R reset. Click the stage left or right of the pole to push it that way.

Bode and Nyquist

Loop broken at the controller output. Includes actuator lag, the pure delay, the 20 ms D filter and the half-sample delay of the sampled controller. Updates as you change anything.
Closed loop (Nyquist)–
CCW encirclements of −1–one per unstable open-loop pole
Phase margin–
Gain margin ↑ · ↓–raise · lower all gains
Peak sensitivity Ms–aim below 6 dB

Bode magnitude |L(jω)| (dB)

ωc gain crossoverp unstable pole

Bode phase ∠L(jω)

−180° linesx axis: ω in rad/s

Nyquist L(jω)

ω > 0ω < 0 (mirror)× critical point −1
Log radius squeezes large gains so the area around −1 stays readable. It keeps the unit circle and the encirclement count unchanged.

Control loop, live

Values update in real time. e = θ − θ_ref, so a pole leaning right (θ > 0) pushes the cart right.
cart x, v Centering −(kx·x + kv·v) θ_ref 0.00° Σ − + e P Kp Kp · e I Ki Ki · ∫e dt PID sampled every 10 ms D Kd Kd · dθ/dt (filtered) 0.0 0.0 0.0 Σ 0.0 ±20 N F_cmd Motor τ delay 0 ms F Cart M = 1 kg v a reaction push Pendulum m = 0.2 kg, L = 1 m θ θ + sensor noise + offset θ measured angle sensor

Scope

Last 10 seconds of simulated time

Angle θ (degrees)

θθ_ref

Force on the cart (N)

F appliedF command

PID terms (N)

PID

Cart position (m) and velocity (m/s)

xv

How to tune it

A force goes straight into acceleration, so the pole feels the controller output directly. Gravity makes upright an unstable equilibrium, and the controller has to supply a restoring "spring" plus damping. Linearized around upright, with the cart mass M, pole mass m and length L:

plant M·L·θ̈ = (M + m)·g·θ − F (small angles) PD controller F = Kp·θ + Kd·θ̇ closed loop M·L·θ̈ + Kd·θ̇ + (Kp − (M+m)·g)·θ = 0 mass damper spring with Ki M·L·s³ + Kd·s² + (Kp − (M+m)·g)·s + Ki = 0

PKp is the spring

It must beat gravity: Kp > (M+m)·g = 11.8 N/rad here. Below that the pole falls no matter what else you do. Higher Kp gives a stiffer, faster response and bigger forces.

DKd is the damper

Without it, the pole rocks around upright and motor lag makes the rocking grow. Kd is what kills it. It also passes sensor noise straight into the force.

IKi is mostly a liability

It adds a third pole and eats phase. Stability needs Kd·(Kp − 11.8) > M·L·Ki, shown live as "Largest stable Ki". It cannot fix the problems you would expect it to fix (see below).

How this compares with the velocity version

GainCommanding velocityCommanding force
KpDamperSpring, must exceed (M+m)·g
KiSpring, must exceed g·LOptional, limited by Routh
KdExtra mass, keep smallDamper, required
Unitsm/s per radN per rad

The velocity servo in the first lab was itself a controller that hid the cart's mass. That inner loop integrated your command once, which shifted every gain's job up a derivative. With raw force there is no hidden loop, so PD does the work.

A tuning recipe

  1. Set Ki = 0 and Kd = 0. Raise Kp until the pole stops falling over and starts rocking around upright. That happens just above 11.8; go to about 3× that, say 40.
  2. Raise Kd until a push settles in about one overshoot. For a target ζ, use Kd ≈ 2·ζ·√(M·L·(Kp − 11.8)). With Kp = 40 and ζ = 0.7 that gives Kd ≈ 7.4.
  3. Turn sensor noise up to 0.5° and watch the force trace. The jitter scales with Kd. If it bothers you, lower Kd or filter harder; a real motor heats up on that buzz.
  4. Try Ki last, and watch "Largest stable Ki". Push it near the limit and the pole starts a slow, growing wobble. The "Too much I" preset sits past it.
  5. Stress it. Push harder, raise the motor lag τ, lower the force limit. If F sits flat at the limit in the scope, the gains are asking for more than the motor has.

Reading the scope

What you seeLikely causeTry
Pole falls in the first second, cart chases itKp ≤ 11.8: spring weaker than gravityRaise Kp
Steady rocking that grows over a few secondsToo little Kd, or motor lag eating phaseRaise Kd, lower τ
Slow wobble that builds even with good Kp and KdKi past the Routh limitLower Ki
Pole creeps back slowly, cart moves a lotOverdamped, Kd too large for KpLower Kd or raise Kp
Fuzzy force traceD term amplifying sensor noiseLower Kd
Force flat at the limit after a pushMotor saturationSofter gains or a stronger motor
Cart cruises or accelerates away while the pole looks fineNo position feedback, or a sensor offsetTurn on the centering loop

Why the I term can't save you from an offset

Set the sensor offset to 1°. The controller now holds the measured angle at zero, so the real pole leans 1° the other way. A pole can only stay leaned if the cart keeps accelerating under it, so the cart speeds off and never stops. Adding Ki doesn't help, because from the angle alone the controller cannot tell an offset from a real lean.

The fix is information, not a gain. Turn on the Centering loop: the outer loop sees the cart running away and leans θ_ref until the cart stops, which finds the true upright on its own. The cart settles a little off center, and adding an integrator to the outer loop would remove that last bit. Keep the outer loop much slower than the inner one.

Reading the Bode and Nyquist plots

The plant has an unstable pole p = √((M+m)g/(M·L)) ≈ 3.4 rad/s. That changes the rules you may know from stable plants: the Nyquist curve must circle −1 counter-clockwise once, and a positive phase margin alone proves nothing. Load "Kp below (M+m)g" to see a 60°+ phase margin on a loop that falls over.

What the simulation includes