Part 2 turned three AC phase currents into two DC numbers by rotating the frame with the rotor. That rotation needs θ, the rotor's electrical angle, every control tick. An encoder or hall sensors can supply it. Without one, the drive has to infer it, and the information is there: a spinning magnet induces a voltage in the windings, and that voltage points along the rotor.
The rotor's voltage gives it away
Each winding sees the voltage the drive applies, minus the resistive drop, minus the change in its own flux linkage. In the stationary αβ frame:
The last term is the rotor magnet's flux, a vector of fixed length ψ pointing along the rotor. Rearranging gives an estimator that needs only things the drive already knows — the voltage it commanded, the current it measured, and the motor'sR and L:
This is the voltage-model flux observer. Two practical changes make it usable. A pure integrator drifts on any DC offset, so it is made slightly leaky — it forgets with a time constant 1/leak — and the phase lead and gain loss that leak introduces are compensated analytically. And instead of taking atan2 raw, aphase-locked loop tracks the angle: a PI drivessin(θ − θ̂) to zero, and the PI's integrator is the speed estimate. That filters noise and gives speed for free.
Why low speed is hard
The useful signal is the back-EMF, ω·ψ. It shrinks to nothing as the rotor slows. Everything else in the integral — an error in R, the voltage the inverter loses to dead time, sense offsets — does not shrink. Below some speed the errors are bigger than the signal, and the estimate drifts away from the rotor.
The direction of the error matters as much as its size. An error voltage that lines up with the current (a wrong resistance, or dead time, which on a small motor behaves like extra resistance) integrates into a flux error that lies along the rotor's own flux. It inflates the estimated magnitude and barely moves the angle. Try it below with the real parameters of the MMC's first bench motor.
Flux observer explorer
Steady state, motor 1: ψ = 0.937 mWb, R = 0.885 Ω, L = 30 µHTop: the rotor frame, with the true rotor flux along the horizontal axis (teal) and the observer's estimate (blue). Bottom: the same two errors across speed for the current settings; the marker is the speed selected above. The red line at 0.35 is the MMC's stall threshold — a stall is detected when the estimated flux falls below it.
With the defaults, at 40 rad/s the estimated flux reads about 1.5× the true value while the angle stays within a fraction of a degree. That is why sensorless FOC can work well on an uncompensated bridge, and also why a stall detector that watches the flux magnitude goes blind exactly where stalls happen. Set the dead-time slider to 0, which is what measuring and compensating it achieves, and both errors collapse. Now drop the speed below the leak frequency: the leak compensation is clamped there, and the angle error grows.
Starting a motor you can't see
At standstill there is no back-EMF at all, so the observer has nothing to work with. The MMC starts in I-f mode: it drives a current vector of fixed size around at a ramping frequency, open-loop, and the rotor follows it like a magnet being dragged. Once the speed is well above the observer's floor, the drive angle is blended from the forced angle to the observer's over a fixed time, the speed loop takes over, and the startup current turns into whatever torque the load needs.
The handoff is where things go wrong. The rotor hangs behind the forced angle by however much torque it needs, so the blend changes the torque. On a light rotor the blend can make it outrun the forced frame entirely; one real bug was a wrap-around in the blend's angle gap once that gap passed π, which reversed the torque in a single tick.
On the STM32G474 bench, the observer's angle matched the true rotor angle to σ ≈ 0.11 rad and the sensorless speed loop held 600 rad/s. On the second board, a motor with hall sensors let the observer be scored against a real angle: after fixing a one-tick voltage timing error, it sits within 0.04 rad of the halls above 200 rad/s electrical, and starts cleanly 10 times out of 10 in both directions.
Everything so far has been math. Part 5 is about how the same code runs against a simulated motor in CI and on two different microcontrollers, and what the simulator catches and misses.