Esc to close · to navigate · to open

SP21

Mechatronics

Closed-loop control of physical systems: sensor → controller → actuator → plant → sensor — and the math (PID, kinematics) that closes the loop reliably.

Concepts learned

Tech

  • C
  • Arduino
  • MATLAB
  • Python

Hero demo PID Step Response

Walk me through this step by step
  1. Picture cruise control on a hill. You set 60 mph, the road tilts up, and the car slows to 55. Something has to notice that gap and push the throttle harder — and back it off again before you overshoot 60 down the far side. PID is the three-line rule a controller follows to do that smoothly, and the same rule shows up in a shower thermostat, a drone holding altitude, and a 3D printer keeping its hot end at 210 °C.

  2. Everything starts with one number: the error. That is just where you want to be minus where you are. If the setpoint is 60 and you are at 55, the error is +5. If you overshoot to 62, the error is −2. The controller’s only job each tick is to look at the error and decide how hard to push the actuator. Everything else is bookkeeping on top of that.

  3. The first idea is the obvious one: push proportional to how wrong you are right now. That is the P term: u_P = K_p \cdot e. With K_p = 2 and an error of 0.5, you command an output of 1.0. Bigger error, harder push. Tiny error, tiny push. It is the same instinct as easing the steering wheel proportional to how far off the lane you are.

  4. Load the P only (offset) preset and watch what goes wrong. The output rises, the plant catches up, the error shrinks — and then it parks slightly below the setpoint and stays there. The push needed to hold against the hill is non-zero, but P only pushes when there is error, so it settles at whatever small error produces exactly the holding push it needs. That stubborn gap is called steady-state offset.

  5. The fix is to remember. The I term keeps a running total of every error you have seen, scaled by how long you saw it: u_I = K_i \int e \, dt. If the error sits at +0.1 for a full second with K_i = 1, the integral grows by 0.1 and adds 0.1 to the command. As long as any error remains, the integral keeps creeping up and pushing harder, until the error is genuinely zero.

  6. Switch to PI (no offset, oscillates). The offset vanishes — but the trace ripples up and down through the setpoint before settling. Memory is a double-edged sword: by the time the plant reaches the target, the integral has already built up enough push to sail past it, and now it has to unwind the other way. That is the I term overcorrecting, and it is why pure PI rings.

  7. The D term is the brake. It looks at how fast the error is changing — u_D = K_d \cdot de/dt — and pushes against rapid change. If the error was 0.5 one tick ago and is 0.3 now (with dt = 0.1 and K_d = 0.5), then de/dt = -2 and the D term contributes −1. You are closing the gap fast, so D quietly says “ease off.”

  8. Load Tuned PID: K_p = 2,\ K_i = 1,\ K_d = 0.5. P drives the bulk of the response, I erases the leftover offset, D damps the overshoot before it happens. The trace climbs to the setpoint, kisses it, and stays. That is the whole recipe: u(t) = K_p e + K_i \int e \, dt + K_d \, de/dt, three terms each fixing what the previous one cannot.

  9. One more failure mode worth seeing by hand. Suppose the actuator saturates — the throttle is already at 100% but the integral keeps accumulating error. When you finally reach the setpoint, the integral is enormous and refuses to let go, so you overshoot badly while it unwinds. That is integral wind-up. The fix in this demo is to freeze the integral whenever the output hits its clamp, so memory only grows while the controller still has authority to act.

  10. Try it yourself.

  11. So that is PID: one rule that turns “where am I vs where do I want to be” into a smooth push on an actuator. The very next demo, the DC motor model, is the plant a PID loop most often drives — paired with this controller, you can see how the motor’s time constant sets the ceiling on how aggressively you can tune. The “Closed-loop pole placement for a PI-controlled second-order plant” featured solution then generalises the same intuition into pole placement: when you know the plant, you can solve for the gains instead of tuning by feel.

\text{error} = 1.000u = 0.00

PID controller with gains Kp=2.00, Ki=1.00, Kd=0.50 tracking a setpoint of 1.00 on a first-order plant with time constant τ=1.00s. Watch how the measurement approaches the setpoint and how the controller output responds to error.

2.00

Reacts to current error. Bigger = stiffer response, but can oscillate.

1.00

Eliminates steady-state offset by accumulating past error.

0.50

Damps response by reacting to the rate of change of error.

1.00

Target value the plant should track.

1.00

First-order plant lag in seconds. Larger τ = slower plant.

t = 0.0s

Reflection

Pre-interview preview. Akwasi’s first-person reflection on this course is pending — track at issue #49. The robot video reel (drive_straight, turn_an_angle, wall_follow, maze_navigation) lands with the v8 interview alongside the reflection.

DC motor model

First-order DC motor response to a step voltage with adjustable back-EMF and inertia.

\omega_{ss} = 12.00 \text{ rad/s}\tau_m = 0.30 \text{ s}t_{settling} = 1.17 \text{ s}

Step response of DC motor (preset "Small hobby") to 6.0 V. Steady-state speed ω_ss = 12.00 rad/s, time constant τ_m = 0.30 s, settles to 2% in 1.17 s.

6.0 V

Step input voltage applied to the armature. Negative drives reverse.

2.0

Steady-state gain (rad/s per V): ω_ss = Km · V.

0.30 s

Mechanical time constant — larger = slower rise.

2.5 s

Time window for the plotted trajectory.

V = 6.0 V

Bode plot builder

Compose poles and zeros; live magnitude and phase Bode plots.

Walk me through this step by step
  1. You turn the bass knob on a stereo and the low notes boost. Turn the treble knob and the highs come up. A Bode plot is the picture of exactly what every knob does — how much each frequency is amplified, and how much it is delayed. Every audio filter, every control loop, every mechanical resonance has one, and once you can read it you can tune any of them.

  2. Pick the simplest example: a low-pass filter with a single pole at \omega = 10 rad/s, transfer function H(s) = 1/(1 + s/10). Forget the algebra for a moment — we will plug three frequencies in and watch what comes out. One well below the corner, one at the corner, one well above it. Those three points already pin down the shape of the entire curve.

  3. At \omega = 1, well below the corner, |H| \approx 1 — the input passes through unchanged, 0 dB. At \omega = 10, right at the corner, |H| = 1/\sqrt{2} \approx 0.707, or −3 dB. At \omega = 100, a decade above, |H| \approx 0.1, which is −20 dB. Push another decade to \omega = 1000 and you get −40 dB. The roll-off settles into a clean 20 dB per decade — every factor of ten in frequency, the output shrinks by a factor of ten.

  4. Here is the aha. On log-log axes the magnitude of 1/(1 + s/\omega_c) becomes flat at 0 dB below the corner and a straight line of slope −20 dB/decade above it — two asymptotes joined by a 3 dB hiccup right at the corner. Stack more poles or add zeros and you just stack more straight-line segments on top of each other. The Bode plot turns multiplying complex transfer functions into adding piecewise-linear pieces, which is why generations of engineers sketched them by hand on graph paper without a calculator.

  5. Magnitude is only half the story. The phase plot tracks how much the output lags behind the input at each frequency. Our low-pass starts at 0° lag well below the corner, hits −45° right at \omega_c, and asymptotes to −90° far above it. Each extra pole adds another 90° of lag in the same staircase pattern, while zeros pull phase the other way. That accumulated lag is what eventually bites you in a feedback loop.

  6. The general recipe: every pole subtracts 20 dB/decade and 90° of phase past its corner; every zero adds the same back. A lead compensator (s+1)/(s+10) plants a zero at \omega = 1 and a pole at \omega = 10, so between those frequencies the magnitude climbs at +20 dB/decade and the phase bumps positive — a phase boost exactly where the loop needs one. A lag compensator does the mirror image. Once you see the asymptotes, you can design either by hand.

  7. Two terms you will meet immediately: phase margin and gain margin. Phase margin is just how much extra phase lag the loop can absorb before it tips into oscillation, measured at the frequency where the open-loop magnitude crosses 0 dB. Gain margin is the mirror — how much extra gain before the same thing — measured where the phase hits −180°. A healthy design wants roughly 30°–60° of phase margin and 6–12 dB of gain margin, and the Bode plot makes both numbers something you literally read off the axes.

  8. Try it yourself.

  9. So that is the Bode plot: poles add lag and shrink high frequencies, zeros add lead and boost them, and log-log axes make the whole thing a sketch instead of a calculation. Paired with the PID controller demo above, this is exactly how you tune those gains in practice — plot the loop’s open-loop response, slide K_p until the 0 dB crossover lands where you want, then check that you still have 45° of phase margin. The featured solution “Bode plot of a first-order low-pass — break frequency and asymptotes” below walks the same first-order example through the asymptote arithmetic, so the −20 dB/decade and −45°-at-corner numbers you just played with become things you can derive on paper.

points = 81bandwidth ≈ 1.12e0 rad/s

Bode plot of the First-order LP (τ=1) transfer function — magnitude and phase frequency response from 10^-1 to 10^3 rad/s, sampled at 20 points per decade.

-1

Lowest frequency 10^start (rad/s) on the log axis.

3

Highest frequency 10^end (rad/s) on the log axis.

20

Sample density. More points → smoother curve, more compute.

81 points

Step position control

Step-input position controller with closed-loop tracking visualisation.

Position vs time
Velocity vs time
Direction
Total steps40
Elapsed0.447 s
vMax actual176.6 st/s
0
40
200 st/s
800 st/s²
0
40 steps · elapsed 0.447 s

Sensor fusion — encoder + IMU

Complementary filter combining wheel encoders and a noisy IMU for heading estimation.

-8°30°67°079
Clean tilt · α=0.95 · sample 0 | gyro=0.000 acc=0.024 fused=0.000 truth=0.000 rad
0.95

α near 1 trusts the gyro short-term; 1−α leans on the accelerometer.

0
0
RMS error: vs truth = 0.0066 rad

Drag an end-effector target; solve joint angles for a 2-DOF planar arm.

\text{target} = (0.00, 0.00)\theta_1 = 0.0^\circ\theta_2 = 0.0^\circ\text{reachable: yes}

Two-link planar arm with link lengths L1 = 1.00 and L2 = 1.00, solving inverse kinematics for the elbow-up configuration as the target traces a circle at 0.50 cycles per second.

1.00

Length of the upper arm (shoulder → elbow).

1.00

Length of the forearm (elbow → tip).

0.50

How many target-path cycles to traverse per second.

t = 0.000

Maze pathfinding

BFS, DFS, and A* search on a small grid maze with frontier visualisation.

\text{algo} = \text{BFS}\text{grid} = 10\times10\text{visited} = 79\text{path} = 19\text{found} = \text{yes}
0

0 = BFS shortest path; 1 = A* with Manhattan heuristic.

1

Square grid side length: 5×5, 10×10, or 15×15.

0.25

Probability that an interior cell is a wall.

42

PRNG seed for the deterministic random maze.

visited 79 cells · path len 19

State machine — decision_making

Step through the robot’s behaviour FSM transitions, lifted from decision_making.ino.

\text{state: SEARCHING}\text{input: tick}\text{step: 0 / 6}

Find wall, then follow: feeding 6 sensor inputs to the robot FSM. Current state: SEARCHING after 0 steps.

600 ms

How long the FSM waits between sensor inputs.

step 0 / 6

What’s coming

The author’s written reflection and the robot video reel land alongside the v8 interview. The demos and featured problems above are wired and playable today.