Control: Making It Do What You Want
Robotics Fundamentals
Course 1 · Chapter 7 · Control: Making It Do What You Want
Robotics Fundamentals 6 ended on an idea: a robot that only commands its motors is trusting that the world is exactly as it was when the commands were written, and a robot that measures what its motors actually did can correct itself. This chapter is about the correcting. Given a target (a speed, a heading, a position) and a measurement (the encoder speed or gyro heading of Robotics Fundamentals 6), a controller decides what to send to the motor next. We start from the simplest possible controller, on or off, and see why it chatters. We then build up the famous PID controller one term at a time (proportional, integral, derivative), simulating a motor speed loop step by step by hand so that every number can be checked. We look at why a position loop rings (the reason robots wobble), what a delay in the loop does to the largest gain you can use, how to tune a controller by hand and with the Ziegler-Nichols method (and what that method gets wrong), and finish with cascaded loops, feedforward, and two classic PID jobs: following a line and holding a heading.
The controller gains, motor constants, delays and noise figures in this chapter are illustrative or typical values chosen so that the arithmetic is easy to follow, not data for a particular motor. The formulas (the first-order step response, the steady-state error of proportional control, the discrete PID, the damping ratio, natural frequency and overshoot of a standard second-order system, and the Ziegler-Nichols table) are standard. Every simulation table in this chapter was recomputed step by step for the model stated next to it. The code is deliberately short, hardware-neutral Python-style pseudocode; real board code is the job of Programming for Robotics.
1. Open loop, closed loop, and the error
A plant is whatever the controller is trying to control: here, a motor with a wheel on it. The controller's output u goes into the plant (for a motor, a voltage, set by a PWM duty cycle) and the plant's response y comes out (a speed or an angle). There are two ways to arrange the loop.
The sign convention matters and is the same everywhere in this chapter: error = setpoint − measurement. A positive error means the output is below the target, so the controller must push it up; a negative error means it is above, so the controller must pull it down. Get this sign backwards and the controller pushes the robot away from the target: it runs away at full power instead of settling.
The plant: a motor as a first-order system
Robotics Fundamentals 3 gave the steady-state motor model, V = kω + IR and τ = kI (Basic Electronics 9). That model tells you where the speed ends up. To control the motor we also need to know how quickly it gets there, and the missing ingredient is the inertia J of everything the motor has to accelerate (rotor, gears and wheel, as seen from the motor shaft). Putting J dω/dt = kI − Tload together with the electrical equation (and ignoring the winding inductance, whose time constant is far shorter than the mechanical one for the small motors in this course) gives a standard result: the speed approaches its final value like a charging capacitor (compare the RC circuit of Basic Electronics 4).
Control engineers call this a first-order lag: a command that jumps instantly produces a speed that climbs smoothly, taking about 4τm (0.8 s here) to settle to within 2 %. For the simulations below we step the model forward in small, equal time steps dt, using the simplest method (Euler):
Open loop: why it is not enough
To hold 60 rad/s open loop, calculate the voltage from the model and send it: u = r / K + d = 60 / 20 + 1 = 4.0 V. That is a perfectly good controller on the day the model is right. The model is never right for long.
Open loop is not useless: for a stepper motor driving a light load, or a quick rough move, it is simple, cheap and needs no sensor. A good controller usually combines both: an open-loop guess for the bulk of the command (feedforward, section 11) and a closed loop to clean up what the guess gets wrong.
2. Bang-bang control and hysteresis
The simplest closed-loop controller is a switch: if the speed is below the target, turn the motor fully on; otherwise turn it off. This is bang-bang (or on-off) control, and it is what a household thermostat does.
The cure is hysteresis: two thresholds instead of one. Switch on only when the measurement falls below r − h, and off only when it rises above r + h; between the thresholds, keep doing whatever you were doing. The output now switches at a rate set by the plant, not by the loop speed, at the price of a ripple of ±h in the result.
Bang-bang control is a good fit when the plant is slow and the result need not be precise: heaters, a battery charger, a fan, a pump filling a tank, or an obstacle avoider that simply turns on or off (Robotics Fundamentals 8). It is a poor fit for steering a wheel or arm smoothly, because the ripple and the shock of full power to full reverse are exactly what we do not want. For those the controller should give an output that is proportional to how far off the robot is.
3. Proportional control
A proportional (P) controller sets the output in proportion to the error: u = Kp e. A big error gets a big push, a small error a gentle one, and at zero error the output is zero. This is what makes it smooth: the push shrinks as the robot approaches the target, so there is no hard switching.
Use the motor model with a proportional gain Kp = 0.2 V per rad/s of error, a target of 60 rad/s, the 1 V load, a supply limit of 6 V, and a 20 ms step (dt = 0.02 s). At each step: e = 60 − ω, u = min(6, 0.2 e), then the Euler update of section 1.
Steady-state error
The speed does not reach 60. It settles where the controller's output is just enough to hold the speed against the load, and with a purely proportional controller that output is only produced by an error. At rest in the steady state ω = K(u − d) and u = Kp e, with e = r − ω; solving gives the steady-state error:
A steady-state error is not a bug in the code; it is a property of proportional control with a load. A motor holding a heavy robot on a slope, a heater holding a room warm in winter, an arm holding up its own weight: each needs a non-zero output to stay where it is, and a P controller can only supply that by leaving an error. That is the job of the next term.
4. Integral control
The integral term adds up the error over time: I = Ki ∫ e dt, in discrete form I = I + Ki e dt at every step. While any error remains, the sum keeps growing and pushes the output harder, so the error cannot stay non-zero in the steady state: the integral stops changing only when e = 0. It has learned the load. A PI controller is u = Kp e + I.
Same motor, same target, now with Ki = 3 V per (rad/s · s) in addition to Kp = 0.2. The loop also uses the anti-windup rule explained below; the integral is not allowed to grow while the output is saturated.
Integral windup and anti-windup clamping
The integral term has a weakness. Suppose the output is already at its limit (6 V here), so the robot cannot go any faster, yet the error is large for a long time (a big step, a blocked wheel, a hill too steep). A naive integrator keeps adding up the error even though the extra output cannot be delivered. The integral winds up to a huge value, like a spring being wound far beyond what the mechanism can use. When the speed finally reaches the target, the error drops to zero, but the integral is still enormous, and the controller keeps driving at full power until the error has been negative for long enough to unwind it. The result is a big overshoot. Here is the same loop without protection:
The cure is anti-windup. The simplest and most common form is clamping (also called conditional integration): do not let the integral grow when the output is saturated and the error would push it further into saturation. That is exactly what the PI table above did in rows 0 to 4. Two other common forms: clamp the integral itself to a sensible range (for example, never more than the supply voltage), or back-calculate (feed the amount by which the output was clipped back into the integral so it unwinds). Any robot with output limits (which is every robot) needs one of these. Windup also appears whenever the plant is stuck: a robot pressing against a wall while the position controller keeps asking for more.
5. Derivative control
The derivative term responds to the rate of change of the error: Kd de/dt. In discrete form, Kd (e − eprevious) / dt. It reacts to where the error is heading: if the error is shrinking quickly (the robot is rushing toward the target) the derivative is negative and subtracts from the output, braking early, so the robot arrives gently instead of overshooting. It acts like damping: a shock absorber, or a hand that steadies a swinging pendulum. Section 7 quantifies this. It does nothing about a steady error (the derivative of a constant is zero), so it is a partner to P, not a replacement.
Two problems: noise and kick
Differentiating a signal amplifies everything fast in it, including noise. A measurement that jumps by one count of the encoder or one step of the ADC produces a large derivative. And if the setpoint is changed suddenly, the error steps with it, and the derivative of a step is, in principle, infinite.
Two standard fixes. Derivative on measurement: since the setpoint r does not change between steps, de/dt = dr/dt − dy/dt reduces to −dy/dt except at the moment the setpoint moves. Compute the derivative term as −Kd (y − yprevious) / dt and the kick disappears completely (the measurement cannot jump when the target changes). And filter the derivative with a first-order filter (Robotics Fundamentals 5) so noise is smoothed before it is multiplied by Kd; commonly the filter time constant is set at a small fraction of Kd/Kp. Better still, use a sensor that measures the rate directly: a gyro gives the turn rate, and an encoder with a good speed estimate gives velocity, so the “derivative” of a heading or position is measured, not differentiated (section 12).
6. The PID controller in discrete time
Adding the three terms gives the PID controller. In continuous form, and in the form the robot's program computes at every tick:
Choosing dt, and limiting the output
- The loop must be much faster than the plant. A common rule is at least 10 updates per time constant of the closed loop you want. For the 0.2 s motor, 20 ms is the largest step worth using, and 10 ms is better; a small robot's speed loop typically runs between 50 Hz and 1 kHz (a rule of thumb, approximate). Euler stepping of the plant, as in this chapter, also needs dt well below τm.
- But not faster than the measurement. A speed measured from encoder counts has a quantisation error that grows as the interval shrinks (Robotics Fundamentals 6 section 2: 2.6 % at 20 ms, 0.5 % at 100 ms for the 0.3 m/s example). The derivative term makes it worse (previous section). There is no use running the loop faster than new information arrives.
- Use the real dt. If the loop time varies (jitter), compute dt from a timer every tick, as with the gyro of Robotics Fundamentals 6; a program that assumes 20 ms but runs at 21 ms makes Ki and Kd wrong by 5 %.
- Always limit the output to what the hardware can deliver (the supply voltage, or ±100 % duty cycle), and tie the integral to that limit (anti-windup). For a motor, the command is converted to a PWM duty cycle: duty = u / Vsupply. Real motors also have a deadband (a minimum voltage below which they do not turn because of friction) that is sometimes handled by adding a small constant offset; the integral term also takes care of some of it.
The PID as pseudocode, and a plant to test it on
Hardware-neutral Python-style pseudocode of the controller of the previous section, with derivative on measurement and clamping anti-windup, and a three-line simulation of the first-order motor so that the controller can be tried on a laptop before it ever sees a motor:
Running this reproduces the PI table of section 4 row by row, which is the best test of a PID class: before a robot ever uses it, check it against a plant whose answer you can calculate by hand.
7. Position control: second-order behaviour, overshoot and oscillation
So far the quantity controlled was speed. Controlling a position (the angle of an arm joint, the distance driven) is harder in an important way: the motor integrates speed into position. Even with no integral term in the controller, the plant itself contains one. The motor of section 1 becomes θ′ = ω, τm ω′ + ω = K u (no load, for clarity), which is a second-order system, and a second-order system can ring.
Close the loop with a P controller, u = Kp (r − θ). Substituting gives the standard form of a second-order system, a mass on a spring with some damping:
Worked example with the motor of section 1 (K = 20 rad/s per volt, τm = 0.2 s), a 1 rad step, and no load:
Why robots oscillate
Read the damping formula again: ζ = 1 / (2√(K Kp τm)). As the gain Kp rises, the damping falls. The loop gets a stronger spring and the same weak damping, so it rings. This is the single most important fact about tuning and it explains most wobbling robots:
- Gain too high. The controller pushes harder than the plant's own damping can absorb. Overshoot grows, then the swings take longer to die, and finally they do not die at all.
- Not enough damping. Friction alone is weak damping, which is why a position loop with only P is easy to make ring. The remedy is the derivative term (the PD row above): it supplies the damping that the plant lacks, so a high gain (fast response) can be kept without the ringing.
- Delay. A late measurement means the controller reacts to where the robot was, so its correction arrives out of step with the motion (next section).
- Backlash and friction. The loose fit in a gearbox (Robotics Fundamentals 3) is a dead zone in which the motor moves but the output does not, so the loop sees no response and pushes harder, then overshoots when the gears engage. An integral term plus static friction (stiction) causes hunting: the integral slowly builds up until the joint breaks free, overshoots, and the cycle begins again.
- Integral too large (section 4): it overshoots by design and, when large, produces a slow oscillation of its own.
8. Stability, delay and gain, in plain words
A control loop is stable if any disturbance dies away, and unstable if it grows. Think of steering a boat by watching its wake: if you are slow to react, you correct for a course error that is already gone, and your correction makes the next error larger. Every real loop has some delay (time between something happening and the controller acting on it): the sensor filter of Robotics Fundamentals 5, the interval over which speed is measured (Robotics Fundamentals 6), the time to compute, a communications link, the motor's own lag. A delay turns a correction that should oppose the error into one that arrives a half-swing late and adds to it. At a frequency where the delay shifts the correction by half a cycle, the loop is stable only if the correction is smaller than the error it was trying to fix; above a certain gain, called the ultimate gain Ku, each swing is bigger than the last.
Take the speed loop of section 1 and add a delay of three samples (60 ms: sensor filtering and command latency, a plausible value for a small robot, approximate). With P control and a 60 rad/s target the loop behaves as follows (each row is a simulation, 20 ms step, no load, no output limit):
The practical lessons: use the fastest, least-delayed measurement you can (a gyro rate rather than a heading difference, an encoder rate with a short interval); do not filter harder than you must (Robotics Fundamentals 5), because a filter is a delay; run the loop at a steady, fast rate; and stay well below the ultimate gain. A loop at half of Ku is not “half as stable”; it is comfortably stable, with a moderate overshoot, and has margin for the motor, battery and floor to change.
9. Tuning
Tuning means choosing Kp, Ki and Kd so the response is fast without being unstable. There is a model-based way (calculate them from the plant, as in sections 3 and 7 where ζ and ωn followed from K and τm) and several experimental ways, because the real plant is never as clean as the model.
Manual tuning, in order
- Make it safe first. Wheels off the ground or a slow, limited speed, an output limit set low, and a stop switch to hand (Robotics Fundamentals 10 covers safe testing). A badly tuned loop can swing a heavy arm or run a robot off a table.
- Set Ki = Kd = 0. Raise Kp in steps, applying a step in the target each time, until the response is fast and starts to overshoot or ring, then back off to somewhere between a half and two thirds of that value.
- Add Kd (for position or heading loops, where it helps most) until the ringing is controlled and the overshoot is small. Stop when the output starts to look noisy or buzzes. If the loop is a plain speed loop, with a lag but no ringing, Kd is often not needed at all.
- Add Ki, small at first, until the steady-state error is removed in a time that suits the task; too much gives overshoot and slow oscillation. Test with the load changed, and with a big step to see windup.
- Re-test everything at the extremes: low battery, high load, full speed. Choose gains that are good enough everywhere, not best at one operating point.
The Ziegler-Nichols ultimate gain method
The classic experimental recipe (Ziegler and Nichols, 1942) needs no model. Using only a P controller, raise Kp until the loop produces a steady, sustained oscillation (neither growing nor dying). The gain at that point is the ultimate gain Ku; the period of the oscillation is the ultimate period Pu. The standard table converts the two numbers into controller settings (quoted from memory of the original table; check it before relying on it):
What Ziegler-Nichols gets wrong
- It is aggressive. The table aims for a response in which each swing is about a quarter the size of the last. That is fast but rings, as the 54 % overshoot above shows; most robots want something gentler. Treat the result as a first guess, then reduce Kp and Ki until the behaviour is acceptable. The variants of the table that claim “some” or “no” overshoot are, again from memory, less aggressive but still overshoot here.
- It requires driving the system to the edge of instability. That is awkward with a delicate or heavy mechanism: a robot arm swinging at its ultimate gain can slam into its stops, and in a saturated actuator the “sustained oscillation” is clipped and ill-defined. Measure with low output limits and a stop switch to hand.
- The measurements are fuzzy. Finding the exact gain of a steady oscillation is hard on a real robot with noise, backlash and friction, and a 10 % error in Ku or Pu carries straight into the gains.
- It assumes a plant with a delay and a lag. A pure position loop (a plant with an integrator, section 7) has no ultimate gain at all without delay, only the delay of the sampling and sensor, and the table was built for process-control plants rather than motors.
- It optimises the wrong thing for robots. It was tuned for rejecting disturbances, not for following a changing setpoint smoothly, which is what most robot motion needs.
- Other methods exist: relay auto-tuning (the controller switches between two outputs and measures the resulting oscillation, which is gentler than driving the loop unstable), the “reaction curve” method (measure the step response of the plant and fit a delay and a time constant), and model-based design as in sections 3 and 7. Many motor-control libraries include an auto-tune routine of the first kind.
10. Cascaded loops
A position controller does not have to command the motor voltage directly. A common and effective structure is a cascade: an inner loop controls speed (fast, at 50 to 1000 Hz, using the encoder rate), and an outer loop controls position (slower), where the outer controller's output is not a voltage but a speed setpoint for the inner loop.
Using the inner PI controller of section 4 (Kp = 0.2, Ki = 3, 6 V limit, 1 V load, which settles within 2 % in about 0.26 s) and an outer proportional gain Kpos in rad/s of speed target per radian of error, a 2 rad step of position gives:
11. Feedforward
A feedback controller reacts only after an error has appeared. If you already know roughly what output a target needs, add it directly and let the feedback correct only the remainder. This is feedforward: u = uff + uPID. For the motor, the model gives the first guess uff = r / K (plus an estimate of the load, if known); for a moving target it also includes terms for speed and acceleration. In an arm, the feedforward often carries the weight of the arm against gravity (Robotics Fundamentals 9).
12. PID examples: line following and heading hold
Line follower
The line sensor array of Robotics Fundamentals 5 produces a single number x: the position of the line in millimetres, positive to the right. The target is x = 0 (line under the centre), so the error is e = 0 − x = −x. The controller's output u is a turn rate in rad/s, counter-clockwise positive (the convention of Robotics Fundamentals 4), and it is turned into wheel speeds with the differential-drive relation vR = v + uL/2, vL = v − uL/2. A line to the right gives x > 0, so e < 0, so u < 0: a clockwise turn, toward the line.
Heading hold
To drive straight (or turn to a bearing) the target is a heading and the measurement is the gyro-based heading of Robotics Fundamentals 6. The error must be wrapped (section 9 of that chapter), or a target of −170 degrees and a heading of +175 degrees would give a 345 degree error and a robot that spins the long way round. The controller's derivative can come straight from the gyro: the heading's rate of change is the gyro rate ωgyro, so the derivative term is −Kd ωgyro, with no differentiation of a noisy angle at all.
13. Challenges
Quick reference
- Error = setpoint − measurement. Open loop trusts the model; closed loop measures and corrects. Open loop at 4.0 V gives 60 rad/s only while the battery and load are as modelled.
- Motor model: τm ω′ + ω = K (u − d); K = 1/k; τm = J R / k2; d = R T / k; step response K (u − d)(1 − e−t/τ): 63.2 %, 86.5 %, 95.0 %, 98.2 % after 1, 2, 3, 4 τ. Euler: ω += (dt/τ)(K (u − d) − ω).
- Bang-bang: full on or off; chatters. Hysteresis ±h gives a ripple of ±h at a rate set by the plant: t = τ ln((final − start)/(final − end)).
- P: u = Kp e; leaves ess = (r + K d)/(1 + K Kp) with a load; larger Kp shrinks it but never removes it, and makes the loop ring.
- I: I += Ki e dt; removes the steady-state error (it learns the load: uss = r/K + d). Windup: saturation plus integrating gives a big overshoot (35.8 % against 0.3 % in the example); fix by clamping the integral when saturated.
- D: −Kd (y − yprev)/dt (on measurement, to avoid kick); adds damping, amplifies noise, so filter it or use a gyro or speed measurement directly.
- Discrete PID: u = Kp e + I + D, limited to the output range; Ki = Kp/Ti, Kd = Kp Td; choose dt at least 10 per closed-loop time constant, but no faster than the measurement; use the real dt.
- Second order (position loop): ωn = √(K Kp / τm); ζ = 1 / (2 √(K Kp τm)); overshoot = exp(−πζ/√(1 − ζ2)); tpeak = π / (ωn √(1 − ζ2)); settling about 4 / (ζ ωn); ζ = 0.7 gives 4.6 %. With PD: 2 ζ ωn = (1 + K Kd)/τm. Higher Kp means lower ζ: robots oscillate when gain is high and damping low.
- Stability: delay (sensor, filter, computation, loop rate) sets an ultimate gain Ku above which each swing grows; stay well below it. Without any extra delay, Euler at dt needs Kp < (2τm/dt − 1)/K.
- Tuning: Kp up to just under ringing, then Kd for damping, then a little Ki, then test at the extremes. Ziegler-Nichols: P 0.5 Ku; PI 0.45 Ku, Pu/1.2; PID 0.6 Ku, Pu/2, Pu/8; fast but aggressive (about 54 % overshoot here), needs a near-unstable test, a first guess only.
- Cascade: fast inner speed loop, slower outer position loop whose output is a speed target; the outer loop must be clearly slower. Feedforward: u = uff + PID, with uff = r / K; it does most of the work and the feedback trims it.
- Line follower: e = −x, output a turn rate u, vR = v + uL/2, vL = v − uL/2. Heading hold: e = wrap(target − heading), u = Kp e − Kd × gyro rate.
Next, Robotics Fundamentals 8 steps up from how a robot moves to what it decides to do. Behaviour and decision-making: reactive behaviours, state machines, line following, obstacle avoidance and wall following built on the controllers of this chapter, and a first look at simple planning, mapping and localisation (using the landmarks and the frames of Robotics Fundamentals 6) so that a robot can choose where to go as well as get there.