FOC Reference 08 Implementation

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 control pipeline: PWM counter, ADC trigger, ISR, shadow registers, gate switching and the machine's response. next period PWM counter centre of the period ADC trigger conversion time ISR Clarke, Park, PI, SVPWM shadow registers held until the next reload gates switch the voltage finally appears machine responds and is sampled next period total ≈ 1.5 periods phase margin = 90° − ω c ·T
Every stage costs phase margin and none is optional. The shadow registers are the easiest to forget — the duty computed this period does not take effect until the next reload, and unlike an angle error that delay cannot be predicted away.

The chain

Between a current existing in the machine and a corrected voltage reaching it, several things must happen in order:

  1. The timer reaches its trigger point and starts the ADC.
  2. The ADC samples and converts.
  3. The ISR runs: transforms, PI, decoupling, modulator.
  4. The results are written to the compare registers.
  5. Those registers are shadowed — they do not take effect until the timer reaches its next reload.
  6. 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.

Hardware
Tuning
Top: the chain over two PWM periods, with the transport delay marked from the sampling instant to the centre of the resulting voltage. Bottom: the analytic phase margin (line) against overshoot measured by running the simulator (points). Nothing makes the two agree — watch where the points blow up.

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 wantedBandwidthAs 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:

BandwidthPredicted PMMeasured behaviour
500 Hz77°settles flat, 0.2% overshoot
1000 Hz63°settles flat, 1.9%
1500 Hz50°settles, 20.7%
2000 Hz36°settles, 46.4%
3000 Hzsettles, barely
3200 Hzringing
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 scaling50–100 cycles
Clarke + Park100–200 cycles (dominated by sin/cos)
Two PI updates + decoupling100 cycles
Inverse Park + modulator150–250 cycles
Observer, if sensorless300–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.