FOC Mathematical Transforms
| Field | Value |
|---|---|
| Title | FOC Mathematical Transforms |
| Type | design |
| Status | draft |
| Version | 0.1.0 |
| Component | foc-transforms |
| Date | 2026-04-07 |
IMPORTANT — Implementation-blind document: This document describes behavior, structure, and responsibilities WITHOUT referencing code. No code blocks using programming languages (C++, C, Python, CMake, shell, etc.) are allowed. Use Mermaid diagrams to express behavior instead. Prose descriptions of algorithms are encouraged; source-level details are not.
Diagrams: All visuals must be either a Mermaid fenced code block (
```mermaid) or ASCII art inline in the document. External image references using Markdown image syntax are not allowed.
Responsibilities
Is responsible for: - Converting three-phase stator currents to a two-phase stationary frame (Clarke transform) - Converting between the stationary αβ frame and the rotor-synchronous dq frame (Park transform) - Providing a composite Clarke → Park (and inverse) operation that evaluates trigonometric functions exactly once per call - Converting a two-component voltage vector in the αβ frame to three-phase PWM duty cycles (Space Vector Modulation) - Supplying fast sine and cosine approximations via a pre-computed lookup table for use in the FOC hot path
Is NOT responsible for: - Reading or writing hardware peripherals (ADC, PWM, encoder) - Managing PID controllers or setpoints - Determining rotor position — electrical angle must be supplied by the caller - Any control strategy decisions (torque, speed, or position setpoints) - Scaling voltage vectors to physical units — inputs and outputs are normalised fractions relative to the DC bus
Component Details
Clarke Transform — Three-Phase to Stationary αβ Frame
The Clarke transform is a geometric projection that reduces a balanced three-phase system to an equivalent two-axis representation. It requires no knowledge of rotor position.
Forward transform (currents 3-phase → αβ):
The amplitude-invariant Clarke transform uses all three measured phases, so it stays correct when the currents are not perfectly balanced:
Iα = (2/3)·(Ia - (Ib + Ic)/2)
Iβ = (Ib - Ic) / √3
When the system is balanced (Ia + Ib + Ic = 0)
this reduces to the familiar two-sensor form Iα = Ia,
Iβ = (Ia + 2·Ib)/√3. The implementation uses the
three-phase form unconditionally.
All three phase currents are measured.
AdcPhaseCurrentMeasurement converts three ADC samples and
delivers them as a PhaseCurrents triple, so no phase is
reconstructed from the balance constraint.
Inverse transform (voltages αβ → 3-phase):
Reconstructs the three-phase voltage references from Vα and Vβ, using the same geometric relationships in reverse. The output of the inverse Clarke is fed into the Space Vector Modulator.
Invariants: - No balance assumption is required on the input; a nonzero common-mode current appears only in the zero-sequence component, which the two-axis output discards. - The transform is purely linear and involves no state — every call is independent.
Park Transform — Stationary αβ to Rotor-Synchronous dq Frame
The Park transform rotates the stationary αβ frame to align with the rotor magnetic field. The result is a coordinate frame that is stationary relative to the rotor, so DC steady-state values represent constant torque and flux components.
Forward transform (αβ → dq):
Id = Iα·cos(θe) + Iβ·sin(θe)
Iq = −Iα·sin(θe) + Iβ·cos(θe)
The d-axis (direct) current component is aligned with the rotor flux. For a surface-permanent-magnet synchronous motor (SPMSM), the d-axis setpoint is held at zero to maximise torque per ampere. The q-axis (quadrature) current is proportional to electromagnetic torque.
Inverse transform (dq → αβ):
Vα = Vd·cos(θe) − Vq·sin(θe)
Vβ = Vd·sin(θe) + Vq·cos(θe)
The inverse Park transform is applied after the PID controllers produce voltage demands Vd and Vq, converting them back to the stationary frame for SVM processing.
Electrical angle:
The electrical angle θe is obtained by multiplying the mechanical rotor angle θm by the motor’s pole-pair count P:
θe = θm · P
The caller is responsible for supplying the correct θe.
Invariants: - The same θe (and therefore the same cos/sin pair) must be used for both the forward Park in the current-sensing path and the inverse Park in the voltage-output path within a single control cycle. - The transform is stateless — no memory of previous cycles.
Composite ClarkePark — Chained Transform with Single Trigonometric Evaluation
The ClarkePark composite combines the forward Clarke and forward Park transforms (or their inverses) into a single operation. The motivation is efficiency: cos(θe) and sin(θe) are computed exactly once and reused for both the Park rotation in the forward direction and the inverse Park rotation.
The forward composite takes (Ia, Ib, θe) and produces (Id, Iq)
directly.
The inverse composite takes (Vd, Vq, θe), performs inverse Park to (Vα,
Vβ), then inverse Clarke to (Va, Vb, Vc), producing all three phase
voltages in one call.
This is the form used by all inner-control-loop implementations in this project.
Space Vector Modulation — αβ Voltage Vector to PWM Duty Cycles
Space Vector Modulation (SVM) maps a two-component voltage reference vector in the αβ plane to three symmetrical PWM duty cycles. The implementation is sector-free: it reaches the same duty cycles as classical sector-and-dwell-time SVM by injecting a common-mode offset, which is algebraically equivalent and has no branch on sector index.
Inverse Clarke:
The reference is first expanded to three phase voltages,
vA = Vα, vB = -Vα/2 + (√3/2)·Vβ,
vC = -Vα/2 - (√3/2)·Vβ.
Common-mode (min-max) injection:
The offset
vCommon = -(max(vA,vB,vC) + min(vA,vB,vC)) / 2 is added to
all three phases. Because it is identical on every phase it cancels in
every line-to-line voltage, so it changes no motor current while
centring the waveform. This is what recovers the linear range that
classical SVM obtains from symmetric null-vector splitting: a factor
2/√3 ≈ 1.155 more than sinusoidal PWM reaches on the same bus. In the
per-unit convention used here — inputs relative to V_dc/√3
— that extended range is the unit circle, which is why the
modulation limiter clamps on |V| ≤ 1 rather than on 2/√3.
Duty cycle output:
Each offset phase voltage is scaled by 1/√3, biased by 0.5 to centre
it in the PWM period, and clamped:
d = clamp(v/√3 + 0.5, 0, 1). A reference of unit magnitude
therefore spans exactly [0, 1].
Constraints: - Input Vα, Vβ are per-unit relative to
V_dc/√3 (dimensionless, inside the unit circle for linear
operation). - Output duty cycles are in the range [0.0, 1.0]; values
outside this range are clamped, which is how over-modulation is handled.
- Being branch-free in the sector, the output is continuous across every
sector boundary by construction. - SVM does not accept angles directly —
it operates only on the (Vα, Vβ) pair.
Fast Trigonometry — Lookup-Table Sine and Cosine
Direct hardware transcendental functions (sin,
cos) introduce variable latency and are unsuitable for a 20
kHz ISR. A 512-entry pre-computed lookup table (LUT) covering one full
period [0, 2π) provides both sine and cosine with bounded,
cycle-predictable evaluation time.
LUT characteristics: - 512 entries, each a 32-bit
floating-point value - Stored in ROM (read-only, constexpr)
at a 16-byte memory alignment boundary - Total ROM footprint: 512 × 4
bytes = 2 048 bytes (2 KB) - Covers exactly one full period
Range handling: the angle is not normalised
to [0, 2π) before indexing. It is scaled by 512/2π, truncated toward
negative infinity, and the resulting index is masked with
size - 1, so the wrap is a bitwise AND on the index rather
than a modulo on the angle. This is why there is no fmod in
the hot path. The consequence to know is the domain limit it leaves: the
intermediate is an int32_t, so the result is meaningless
for angles beyond roughly 2.6 × 10⁷ radians. Electrical angle reaches
the transforms already wrapped, so this bound is not approached in the
control loop.
Cosine reuses the sine table — there is no separate cosine table — but it is not an index offset. It adds π/2 to the angle and performs the whole lookup again, so a cosine costs a full sine plus one addition, and evaluating both for the same angle does the range reduction twice.
Interpolation: Linear interpolation between adjacent LUT entries provides accuracy sufficient for FOC applications; the quantisation error is below the noise floor of 12-bit ADC current sensing.
Interfaces
Provided
| Interface | Purpose | Contract |
|---|---|---|
| Clarke — Forward | Converts (Ia, Ib, Ic) to (Iα, Iβ) | All three phase currents are measured; no phase is reconstructed. The amplitude-invariant form remains valid for unbalanced phases. |
| Clarke — Inverse | Converts (Vα, Vβ) to (Va, Vb, Vc) | Produces all three phase voltages. The sum of outputs is zero. |
| Park — Forward | Converts (Iα, Iβ, θe) to (Id, Iq) | Caller supplies pre-computed or LUT-evaluated cos(θe) and sin(θe). Stateless. |
| Park — Inverse | Converts (Vd, Vq, θe) to (Vα, Vβ) | Uses the same θe as the forward Park in the same control cycle. |
| ClarkePark — Forward | Converts (Ia, Ib, θe) to (Id, Iq) in one call | Computes cos/sin once; result is identical to chaining Clarke then Park separately. |
| ClarkePark — Inverse | Converts (Vd, Vq, θe) to (Va, Vb, Vc) in one call | Chains inverse Park → inverse Clarke; computes cos/sin once. Output is three phase voltages, not (Vα, Vβ). |
| SpaceVectorModulation — Generate | Converts (Vα, Vβ) to three duty cycles | Inputs must be per-unit relative to V_dc/√3. Outputs
are always in [0.0, 1.0]. Common-mode injection is internal; there is no
sector branch. |
| FastTrigonometry — Sine | Returns an approximation of sin(θ) | θ is normalised to [0, 2π) internally. ROM LUT; no floating-point transcendental at runtime. |
| FastTrigonometry — Cosine | Returns an approximation of cos(θ) | Derived from the sine LUT via a quarter-period offset. Same ROM, no additional storage. |
Required
| Interface | Purpose | Contract |
|---|---|---|
| None | All transforms are pure mathematical algorithms with no external dependencies. | — |
Data Model
| Entity | Field | Type / Unit | Range | Notes |
|---|---|---|---|---|
| Three-phase current | Ia, Ib | Ampere (float) | ± rated current | Ic derived; not stored |
| Stationary frame | Iα, Iβ | Ampere (float) | ± rated current | Output of Clarke forward |
| Synchronous frame | Id, Iq | Ampere (float) | ± rated current | Output of Park forward |
| Voltage demand | Vd, Vq | Dimensionless (float) | [−1, +1] | Normalised to DC bus |
| Stationary voltage | Vα, Vβ | Dimensionless (float) | [−1, +1] | Output of inverse Park / input to SVM |
| Phase duty cycles | Da, Db, Dc | Dimensionless (float) | [0.0, 1.0] | Clamped; 0 = always low, 1 = always high |
| Electrical angle | θe | Radians (float) | [0, 2π) | θm × pole_pairs; normalised before LUT indexing |
| LUT | Sine table | float[512] | [−1.0, +1.0] | constexpr; 2 KB ROM; 16-byte aligned |
Block Diagram
Constraints & Limitations
| Constraint | Value / Description |
|---|---|
| Three-sensor current topology | All three phase currents are measured directly. The amplitude-invariant Clarke form is valid regardless of phase balance. |
| LUT ROM footprint | 2 048 bytes (2 KB) of read-only memory. |
| LUT accuracy | Approximation error is bounded and below the noise floor of 12-bit ADC current sensing. |
| SVM input normalisation | Vα, Vβ must be dimensionless fractions in [−1, +1] relative to the DC bus, not physical volt values. |
| SVM output saturation | Duty cycles are clamped to [0.0, 1.0]. Over-modulation is silently saturated, not flagged. |
| Electrical angle responsibility | The caller is responsible for computing θe = θm × pole_pairs before invoking any Park operation. |
| Stateless transforms | No transform retains state between calls. Every call is independent. |
| Hot-path constraint | All transforms must execute within the cycle budget of the 20 kHz FOC interrupt. |