BLDC Motor Control with FOC Guide
Field-Oriented Control, Clarke/Park Transforms & Sensorless Startup
1. Introduction — Why FOC for BLDC Motors
Brushless DC (BLDC) motors are the workhorse of modern motion control — from e-bikes and drones to robotics, HVAC blowers, and EV traction. Field-Oriented Control (FOC), also called Vector Control, treats the three-phase motor as a decoupled DC-like system: it independently controls torque (q-axis) and flux/field (d-axis), enabling smooth, efficient, low-noise operation across the entire speed range.
Compared to six-step trapezoidal commutation, FOC delivers 5–15% higher efficiency, dramatically lower acoustic noise (no commutation torque ripple), and full torque control at standstill. With modern Cortex-M4/M7 MCUs (e.g., STM32G4, STM32F3, or dedicated motor-control SoCs), FOC runs comfortably in real time at PWM frequencies of 16–64 kHz. This guide walks through the math, implementation, tuning, and sensorless startup of an FOC BLDC drive.
2. Commutation Strategies — Comparison
2.1 Trapezoidal vs Sinusoidal vs FOC
| Aspect | Trapezoidal (6-step) | Sinusoidal | FOC (Vector) |
|---|---|---|---|
| Implementation | Simplest (hall commutation) | Moderate (sine PWM table) | Complex (transforms + 2 PI loops) |
| Torque ripple | High (~14% every 60°) | Low-moderate | Very low (~2%) |
| Acoustic noise | High | Low | Lowest |
| Efficiency | Good | Better | Best (5–15% over 6-step) |
| Speed range | 0–100% (limited by ripple) | Good (needs rotor position) | Full, incl. standstill torque |
| Position sensor need | Hall (60° resolution) | Encoder/resolver | None (sensorless) to encoder |
| MCU load | Very low | Low | Moderate-high (but ok on M4 @ 16 kHz) |
3. The Math — Clarke and Park Transforms
3.1 Clarke Transform (3-phase → 2-phase stationary αβ)
The Clarke transform projects the three phase currents onto two orthogonal axes (α, β), removing the redundant third axis (since ia + ib + ic = 0):
iα = (2/3) × [ia − (1/2)·ib − (1/2)·ic]
iβ = (2/3) × [(√3/2)·ib − (√3/2)·ic]
Since ic = −ia − ib (star winding, no neutral):
iα = ia
iβ = (1/√3)·ia + (2/√3)·ib
(The amplitude-invariant form uses factor 2/3; power-invariant uses √(2/3). Motor-control libraries commonly use the amplitude-invariant form above.)
3.2 Park Transform (αβ → rotating dq)
The Park transform rotates the stationary αβ vector into the rotor-flux-aligned dq reference frame using the electrical angle θe:
id = iα·cos(θe) + iβ·sin(θe)
iq = −iα·sin(θe) + iβ·cos(θe)
Inverse Park (to generate Vα, Vβ from Vd, Vq):
Vα = Vd·cos(θe) − Vq·sin(θe)
Vβ = Vd·sin(θe) + Vq·cos(θe)
Torque equation (surface PMSM): Te = (3/2) × p × [λpm·iq + (Ld − Lq)·id·iq]
For surface-mounted PM (SPMSM, Ld = Lq): Te ∝ iq — torque is directly proportional to q-axis current. This is the heart of FOC: control iq to control torque, control id = 0 to maximize torque-per-amp (MTPA for SPMSM).
4. FOC Control Architecture
4.1 Block Diagram
Speed ref ──► [Speed PI] ──► iq_ref ──► [Current PI (q)] ──► Vq ──┐
▲ id_ref=0 ─► [Current PI (d)] ──► Vd ──┼─► [Inv Park] ─► [SVPWM] ─► Inverter
│ ▲ │
ω_est ◄─ [Observer] ◄──── θ_e ◄──────────────────────────────────────┘
(sensorless) or encoder/hall reading
▲
i_a, i_b (ADC) ─► [Clarke] ─► [Park] ─► id, iq ◄──┘
The current loop runs at the PWM/ISR rate (16–64 kHz); the speed loop typically runs at 1–5 kHz (every N ISRs). The outer position loop (if present) runs at 0.5–1 kHz.
4.2 Worked Example — Current PI Tuning
Motor parameters: Rs = 0.5 Ω, Ls = 150 µH (surface PMSM), current-loop period Ts = 50 µs (20 kHz).
Desired current-loop bandwidth: fBW = 1 kHz (typical; ≤ fsw/10 = 2 kHz)
Required PI gains (using pole-zero cancellation):
Kp = Ls × ωBW = 150µ × 2π×1000 = 0.942 V/A
Ki = Rs × ωBW = 0.5 × 6283 = 3142 V/(A·s)
Discrete implementation (Tustin): Ki_disc = Ki × Ts = 3142 × 50µ = 0.157 V/A
PI output: Vq(k) = Kp·e(k) + Ki_disc·Σe(k)
4.3 Speed PI Tuning
Use symmetrical optimum method for speed loop when mechanical time constant τm is large:
Load inertia J = 5e-5 kg·m², pole pairs p = 4, flux λpm = 0.03 Wb.
Kp_speed ≈ J × ωBW_speed / (1.5 × p × λpm)
= 5e-5 × 628 / (1.5 × 4 × 0.03) = 0.0314/0.18 ≈ 0.175 (N·m)/(rad/s)
Then tune in simulation first (e.g., with ST Motor Profiler / FOC models), then verify with step response and reduce gain 30% for stability margin.
5. SVPWM (Space Vector PWM) Implementation
Space Vector PWM generates the three-phase duty cycles that produce the desired rotating voltage vector with 15% higher DC-bus utilization than sinusoidal PWM and lower THD:
// SVPWM — center-aligned. Input: Valpha, Vbeta, Vdc. Output: Ta,Tb,Tc (0..1)
void svpwm(float Valpha, float Vbeta, float Vdc, float *Ta, float *Tb, float *Tc) {
// 1. Determine sector from angle and magnitude
float V1 = Vbeta;
float V2 = 0.5f * (-Vbeta + 1.7320508f * Valpha);
float V3 = 0.5f * (-Vbeta - 1.7320508f * Valpha);
int sector;
if (V1 > 0) sector = (V2 > 0) ? 1 : 3;
else sector = (V2 > 0) ? 5 : 7;
// refine: use V3 and V2 signs to pick between 2/6 etc. (sector table lookup)
// 2. Normalize to DC bus, apply per-sector voltage-time equations
// (T1, T2 = active vector times; T0 = zero vector time)
float Vref = sqrtf(Valpha*Valpha + Vbeta*Vbeta);
float t0 = 1.0f - (2.0f * Vref / Vdc); // zero-vector time (scaled)
// 3. Distribute into phase duty cycles per sector (switching table)
// then convert to compare register values for TIM1 (center-aligned)
// *Ta = ...; *Tb = ...; *Tc = ...;
// 4. Apply min/max clamping to keep 0..1 (avoid overmodulation)
}
In practice, production code uses precomputed sector look-up tables and compares the “maximum centered” form (SVGENDQ in TI motor libraries / MC_SVPWM in ST MC SDK). The key deliverable is three duty ratios updated in the ISR, written to the timer’s CCR1/2/3 with center-aligned PWM and complementary outputs with dead time.
6. Rotor Position Sensing Options
| Method | Resolution | Cost | Standstill Torque | Use Case |
|---|---|---|---|---|
| Hall sensors (3×) | 60° elec. | $ | No (need interpolation) | Low-cost, low-noise-immune apps |
| Quadrature encoder | 4096 CPR (typ) | $$ | Yes | Robotics, servos, precise position |
| Resolver | 12–16 bit | $$$ | Yes | Automotive, harsh environment |
| Sensorless (BEMF observer) | Continuous | Free (SW) | No (needs startup) | Fans, pumps, most consumer drives |
6.1 Sensorless Observers — BEMF, Sliding Mode, Luenberger
| Observer | Principle | Robustness | Min Speed | Tuning Effort |
|---|---|---|---|---|
| Back-EMF (direct) | Measure phase voltage, extract BEMF zero-crossing | Low (sensitive to noise) | ~10% rated | Low |
| Sliding-Mode Observer (SMO) | Discontinuous switching control drives estimation error to sliding surface | High (parameter-insensitive) | ~5% rated | Medium (chattering filtering) |
| Luenberger (state) | Linear state-space observer with gains from motor model | Medium (model-dependent) | ~5-8% rated | High (needs accurate R, L, flux) |
| Model Reference Adaptive (MRAS) | Adaptive model converges to reference plant | Medium-high | ~5% rated | High (adaptation gains) |
7. Sensorless Startup — Open-Loop → Closed-Loop Handoff
7.1 Why Sensorless FOC Can’t Start from Standstill
At standstill, the rotor has no measurable BEMF — the observer has nothing to lock onto. Therefore sensorless drives must use an open-loop forced-commutation startup sequence, then hand off to closed-loop FOC once BEMF is observable.
7.2 Startup Sequence (3 phases)
| Phase | Action | Duration | Key Parameter |
|---|---|---|---|
| 1. Alignment | Apply fixed voltage vector (θ = 0) for Talign | 100–500 ms | Alignment current ~ rated (holds rotor) |
| 2. Open-loop ramp | Sweep θ from 0 at increasing speed; V grows with speed (BEMF compensation) | 100–400 ms | Ramp rate (accel) ~ 0.1–1 pu/s |
| 3. Handoff (synchronization) | Compare open-loop θ vs observer θ; when error < 10° for N samples, switch | Instant (1 ISR) | θ error threshold + hold time |
7.3 Worked Example — Startup Profile for a Fan
Phase 1 — Alignment: Apply id = 1 A (rated), θ = 0 for 250 ms. Rotor aligns to d-axis.
Phase 2 — Open-loop ramp: Start θ=0 at electrical 5 Hz, ramp +10 Hz/100 ms to 30 Hz (15% of rated speed) over 250 ms.
Open-loop voltage: V = Istart·(Rs + j·ω·Ls) + ω·λpm (feed-forward compensation).
At 30 Hz: V ≈ 1×(0.5 + j·188.5×150µ) + 188.5×0.03 ≈ 0.5 + j0.028 + 5.65 ≈ 6.15 V phase.
Phase 3 — Handoff: The SMO observer runs in parallel from Phase 2. When |θobs − θol| 1.5× rated → abort and retry alignment.
Anti-defeat: If handoff fails 3 times (load stalled or reversed), stop and set fault flag (e.g., propeller obstruction).
8. Common Pitfalls
8.1 Wrong Current Sensing Timing (ADC Sampling Window)
Problem: Sampling phase current at the wrong point in the PWM cycle. With center-aligned PWM, the three low-side (or shunt) currents are only simultaneously measurable during the zero-vector interval. Sampling during active vectors captures a single-phase current — with a single-shunt topology this requires reconstruction, and getting it wrong produces garbage Park outputs and unstable control.
Fix: Trigger ADC conversion at the exact center of the PWM period (when all low-side switches are ON, i.e., the zero-vector) via the timer’s TRGO. Add a ~1 µs settling delay after the switch turn-on. For dual-shunt topology, ensure the sample window fits within the minimum zero-vector time; for very low duty cycles, reduce PWM frequency or add phase-shift sampling.
8.2 Zero-Speed Observer Lock — Sign of Startup Problem
Problem: At low speed, the BEMF is tiny (proportional to ω). The observer output becomes noisy, and the handoff condition is never met — the motor just hums and stops.
Fix: Don’t expect sensorless observation below ~5% rated speed. Use the open-loop ramp to reach observable speed before handoff. Keep the open-loop current high enough to hold the rotor against load. For applications requiring full torque at zero speed (e.g., some robotics), you must use a position sensor or HF injection (below ~10% speed, inject a high-frequency carrier and demodulate saliency).
8.3 Insufficient Dead Time / Shoot-Through
Problem: Setting dead time to 0 to “save time” or pushing too high a PWM frequency. The MOSFET/IGBT gate drivers need time to turn off the top device before the bottom turns on. If dead time is too small, shoot-through current destroys the bridge (or at best trips the overcurrent comparator every cycle).
Fix: Use 100–500 ns dead time (check your power switch’s td(on)/td(off) and gate-driver propagation delay). Use complementary PWM with programmable dead-time in the timer (TIM1/TIM8 BDTR register on STM32). Verify with a current probe that there’s no overlap on the bridge legs before running at full voltage.
9. Frequently Asked Questions
- Q: Why is my FOC motor noisier than the 6-step version?
- FOC should be quieter — if it’s noisier, check: (1) dead time too large causing commutation notches; (2) current-loop bandwidth too high amplifying measurement noise; (3) PWM frequency too low (below 16 kHz, motor whine is audible — raise to 20 kHz+); (4) the SMO chattering filter is leaking high-frequency noise into the angle estimate — increase the low-pass cutoff or reduce SMO gain. Also verify your ADC sampling is synchronized (see pitfall 8.1).
- Q: What’s the difference between MTPA and id=0 control?
- For surface PM motors (Ld = Lq), torque ∝ iq, so setting id = 0 maximizes torque per amp (MTPA = MTPA-at-zero-d for SPMSM). For interior PM (IPM, Ld < Lq), the reluctance torque term (Ld − Lq)·id·iq means a negative id increases torque — MTPA control then injects a calculated negative id for the same torque at lower current, improving efficiency above base speed. Field Weakening (id < 0) is applied beyond base speed to extend the speed range by reducing the flux linkage opposing the terminal voltage.
- Q: How do I choose the PWM/current-loop frequency?
- Guidelines: (1) current loop at 8× electrical frequency or higher (for a 200 Hz-electrical motor, ≥ 16 kHz); (2) PWM frequency above the audible range (≥ 20 kHz) for consumer products; (3) current-loop bandwidth ≈ f_pwm/10 (2 kHz bandwidth at 20 kHz PWM); (4) consider switching losses — MOSFET Coss and gate charge losses grow linearly with f_sw; 20 kHz is a good balance. On MCUs with a dedicated motor-control timer (STM32G4 TIM1/8), 32–64 kHz is feasible and improves current-loop quality for low-inductance motors.
- Q: Can I run sensorless FOC for a motor that must start under load?
- Yes, but with a load the open-loop ramp must push enough current to overcome the load torque during alignment and acceleration. The alignment phase holds the rotor against the load — if the load torque exceeds the alignment torque (3/2·p·λpm·i_align), the rotor won’t lock and startup will fail. Solutions: raise alignment current to 1.5–2× rated momentarily, use a longer ramp with a torque-proportional current profile, or for truly high standstill torque requirements (e.g., elevator, robotic arm), use encoder or Hall feedback instead of sensorless.
- Q: What does the “electrical angle” and pole pairs mean for my code?
- The electrical angle θe advances p times faster than the mechanical angle: θe = p × θm. All FOC math operates in the electrical domain — Clarke/Park use θe, SVPWM generates the electrical voltage vector. With a quadrature encoder you multiply the mechanical position by p (and account for encoder resolution) to get θe. The speed loop works in mechanical radians/sec (ωm) — the motor’s speed rating (RPM) is mechanical. Getting p wrong makes the motor run at the wrong speed and the observer diverge.
References
- STMicroelectronics: “STM32 MC SDK 6.x” documentation & Motor Control Fundamentals (AN1162, AN4118)
- Texas Instruments: “Field Orientated Control of 3-Phase AC-Motors” (BPRA073) and “Sensorless FOC of PMSM” (SPRABQ0)
- Microchip AN1078: “Sensorless Field Oriented Control of a PMSM”
- P. Vas, “Sensorless Vector and Direct Torque Control,” Oxford University Press, 1998.
- P. Krause, “Analysis of Electric Machinery and Drive Systems,” 3rd Edition, Wiley, 2013.