Timing and synchronisation
The current loop page derived its gains from a continuous-time plant, got a clean first-order response, and then admitted that the achievable bandwidth is capped at “about a tenth of the PWM frequency” without saying why.
This page says why. The answer is a single number — the interval between sampling a current and applying the voltage that results from it — and it turns out to explain the ceiling exactly.
The chain
Between a current existing in the machine and a corrected voltage reaching it, several things must happen in order:
- The timer reaches its trigger point and starts the ADC.
- The ADC samples and converts.
- The ISR runs: transforms, PI, decoupling, modulator.
- The results are written to the compare registers.
- Those registers are shadowed — they do not take effect until the timer reaches its next reload.
- The new duty is applied across a whole period, and its average lands in the middle of it.
Steps 5 and 6 are the ones people forget, and together they dominate. For single-update PWM with a computation that comfortably fits, they contribute one full period plus a half period, giving the familiar 1.5 periods — 75 µs at 20 kHz.
On the bench Step 5 is not optional and must not be defeated. Compare registers are shadowed so that a duty update cannot land part-way through a pulse and produce a runt or, worse, a shoot-through. Writing them asynchronously “to save a period” is a recognised way to destroy a bridge.
What a delay costs
A pure delay has unity gain and a phase of . It costs nothing at DC and progressively more with frequency — precisely the wrong shape for a control loop, because it is invisible where you can measure it and fatal where you cannot.
Recall what the current loop looks like after pole-zero cancellation: the open loop is a pure integrator, . That crosses unity gain with exactly of phase, so it starts with 90° of phase margin and is unconditionally stable. Everything that eats into that margin is the delay:
The stability of a well-tuned current loop is decided entirely by its timing, never by its gains. That is worth sitting with: you cannot tune your way out of a delay problem, and a loop that rings is more often a timing problem than a gain problem.
Where the rule of thumb comes from
Set and solve for the bandwidth that leaves a given margin:
| Phase margin wanted | Bandwidth | As a fraction of |
|---|---|---|
| 60° | 1111 Hz | |
| 45° | 1667 Hz | |
| 36° | 2000 Hz | |
| 0° (unstable) | 3333 Hz |
So the familiar is not arbitrary — it is exactly a 36° phase margin. That is stable, and it is marginal.
On the bench Marginal enough that quoting the rule without the caveat oversells it. Running the simulator at 2 kHz with the full 1.5-period delay gives 46% overshoot on a current step. It settles, and it is stable, and most people would not accept it. At — a 63° margin — the same step overshoots by 1.9%.
Treat as the point past which you are definitely in trouble, not as a design target.
The prediction is testable
The formula above is algebra on an idealised open loop. The simulator knows nothing about it: it integrates the machine, runs the modulator, and pushes voltages through a shadow-register pipeline. If the two agree, the algebra is describing something real.
Sweeping bandwidth at 20 kHz with a 1.5-period delay:
| Bandwidth | Predicted PM | Measured behaviour |
|---|---|---|
| 500 Hz | 77° | settles flat, 0.2% overshoot |
| 1000 Hz | 63° | settles flat, 1.9% |
| 1500 Hz | 50° | settles, 20.7% |
| 2000 Hz | 36° | settles, 46.4% |
| 3000 Hz | 9° | settles, barely |
| 3200 Hz | 4° | ringing |
| 3400 Hz | −2° | sustained oscillation |
The predicted instability is at 3333 Hz. The measured boundary lies between 3200 and 3400 Hz — agreement to about 2%.
The two routes share one input and nothing else: both are told the same transport delay, as they must be, since otherwise they would be describing different drives. From there they part company completely. One evaluates on an idealised open loop. The other integrates the machine, runs the modulator, pushes voltages through a shadow-register pipeline and measures the overshoot that comes out. Nothing forces those to land in the same place.
On the bench
This chapter improved the model. Until now the simulator applied each computed
voltage during the same period it was computed, so it carried only the half
period of duty averaging and looked considerably more stable than hardware — at
3600 Hz it showed 2% overshoot where the real loop oscillates. Adding the
shadow-register pipeline (voltageDelayPeriods) fixed it, and the agreement
above is the evidence.
Buying the delay back
Double-update PWM updates the compare registers at both the reload and the peak. The delay halves to 0.75 periods, and the available bandwidth doubles for the same phase margin. The price is that the control ISR runs twice as often, so the cycle budget halves.
Reducing the computation helps only if it is what pushes you past an update point. Below that threshold, shaving microseconds off the ISR buys nothing at all — the delay is set by waiting for the reload, not by the arithmetic. Speed it up from 12 µs to 6 µs at 20 kHz and the delay is unchanged at 1.5 periods.
Where computation time matters is the cliff. Let it exceed one interval and the update slips an entire period: the delay jumps from 1.5 to 2.5 periods, and the stability limit falls by 40%. Drag the compute slider past the interval in the figure to see the ISR band turn red.
Raising the PWM frequency shortens the delay proportionally, since it is measured in periods. It costs switching losses, and it shrinks the current measurement window discussed on the sensing page — the same trade-off from the other side.
Angle prediction is separate
There is a second consequence of the same delay, and it is a different problem with a different fix.
By the time the voltage lands, the rotor has moved by . The output transform should use the angle the rotor will have, not the one it had when the current was sampled:
At 75 µs of delay this is 3.2° at 1800 rpm and 10.8° at 6000 rpm on the reference machine — a first-order error that grows with speed, which is exactly the signature of a drive that is well-behaved on the bench and disappointing in the application.
Unlike phase margin, this one is compensable, and cheaply: one multiply and an add. Do it.
On the bench Two consequences, one delay, and it is worth keeping them apart. The phase margin loss limits bandwidth and cannot be predicted away. The angle error is a rotation and is removed entirely by prediction. Implementing the second does nothing for the first — a drive with perfect angle prediction still goes unstable at the same bandwidth.
The ISR budget
At 20 kHz single-update on a 170 MHz core there are 8500 cycles per update. Double-update halves that to 4250. What has to fit:
| Rough cost | |
|---|---|
| ADC read and scaling | 50–100 cycles |
| Clarke + Park | 100–200 cycles (dominated by sin/cos) |
| Two PI updates + decoupling | 100 cycles |
| Inverse Park + modulator | 150–250 cycles |
| Observer, if sensorless | 300–800 cycles |
A sensored loop lands comfortably inside 1000 cycles; sensorless roughly doubles it. Neither is close to 8500, which is why current-loop timing is rarely compute-bound on a modern part — and why the delay budget is dominated by waiting for the reload, not by doing the work.
The sin/cos pair is usually the largest single item. Compute them once
per update and pass the values to both transforms rather than calling the
trigonometry twice.
In code
/* The trigger chain is configured in hardware and never touched by software.
* A sample scheduled by an interrupt is a sample taken at the wrong time. */
timer_set_adc_trigger(TIM_TRGO_UPDATE); /* ADC starts at the reload */
timer_set_preload(ENABLE); /* compare regs are shadowed */
void adc_complete_isr(void)
{
const float theta = encoder_angle();
const float omega = encoder_speed();
/* Predict forward to where the rotor will be when this lands. */
const float theta_out = theta + omega * DELAY_SECONDS;
float s_in, c_in, s_out, c_out;
sincosf(theta, &s_in, &c_in); /* once, for the input transform */
sincosf(theta_out, &s_out, &c_out); /* once, for the output transform */
foc_step(s_in, c_in, s_out, c_out);
}
What to take away
- The delay from sampling to applied voltage is 1.5 PWM periods for single-update PWM. Shadowed compare registers plus duty averaging, not computation, are what dominate it.
- A well-tuned current loop starts with 90° of phase margin and spends it on delay: . Stability is a timing property, not a gain property.
- is a 36° phase margin, and it overshoots by 46%. Prefer unless you need the speed.
- Double-update PWM halves the delay and doubles the usable bandwidth, at twice the ISR rate.
- Making the ISR faster buys nothing unless it was overrunning an update interval — at which point it buys a great deal.
- Predict the angle forward by . It removes a speed-dependent rotation, and it does nothing whatsoever for stability.
This closes the loop the site opened with: three sinusoids, two transforms, a PI controller, six transistors, and a hard limit set by how long it all takes.