Service: Motor Alignment
| Field | Value |
|---|---|
| Title | Service: Motor Alignment |
| Type | design |
| Status | draft |
| Version | 0.1.0 |
| Component | service-alignment |
| 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: - Calibrating the encoder zero-offset so that the encoder frame and the motor’s electrical rotor frame are aligned (position = 0 in the encoder corresponds to electrical angle = 0° in the rotor) - Applying an open-loop DC voltage vector at electrical angle 0° via the three-phase inverter to force the rotor to a known magnetic position - Detecting rotor settlement by monitoring encoder position change over consecutive samples - Reporting the calibrated encoder offset (in radians) via a single completion callback on success, or reporting failure if the rotor does not settle within the allowed time - Stopping the inverter cleanly after the procedure completes, whether successfully or not - Enforcing mutual exclusion: silently ignoring re-entry while an alignment is already in progress
Is NOT responsible for: - Persisting the returned encoder offset to NVM — that is the caller’s decision - Running during normal FOC operation — the FOC loop must be stopped by the caller before alignment begins - Measuring or interpreting motor electrical parameters (R, L, pole pairs) - Providing any feedback during the alignment beyond the single completion callback
Component Details
Preconditions and Configuration
Before invoking alignment, the caller must have:
- Stopped the normal FOC loop so the three-phase inverter is not claimed by a current control loop.
- Provided the number of pole pairs for the motor under test.
- Constructed an
AlignmentConfigspecifying:- test voltage percent — fraction of DC bus voltage (0–100 %) applied to the d-axis during the procedure
- sampling frequency — the rate at which encoder samples are taken and settlement is checked (Hz)
- max samples — the total number of sampling ticks before the procedure times out
- settled threshold — the maximum absolute encoder position change (radians) that qualifies as “not moving”
- settled count — the number of consecutive below-threshold samples required to declare settlement
These parameters are tuned by the application engineer for the specific motor and mechanical load. A typical configuration applies 10–20 % of bus voltage and requires 20 consecutive samples within 0.001 rad of one another before declaring settlement.
Open-Loop Voltage Application
The alignment service drives the inverter directly in open-loop mode, bypassing all current feedback. It synthesises a stationary voltage vector by computing the inverse Clarke and inverse Park transforms with:
- d-axis voltage = test_voltage_percent / 100 (normalised to DC bus)
- q-axis voltage = 0 (no torque component)
- electrical angle = 0°
The resulting three-phase duty cycles are submitted to the inverter each sampling tick for the duration of the procedure. Because the q-axis component is zero, the resulting stator field is purely aligned with the d-axis at 0°. A surface PMSM or BLDC rotor will rotate to align its permanent-magnet d-axis with this field and remain there.
Settlement Detection
Settlement detection is a simple sliding window state machine. At each sampling tick (controlled by the inverter’s ADC interrupt callback):
- The encoder position is read.
- The absolute difference between the current and previous position sample is computed.
- If this difference is below
settledThreshold, the consecutive-settlement counter is incremented; otherwise the counter is reset to zero. - If the counter reaches
settledCount, the rotor is declared settled. - If the total sample count reaches
maxSampleswithout settlement, a timeout is declared. - If any phase current in a sample exceeds
ThreePhaseInverter::MaxCurrentSupported(), the injection is aborted immediately and the timeout path is taken. Alignment drives an open-loop voltage with no current regulation, so this per-sample check is the only software limit on the resulting current.
Sampling is gated on the pending completion rather than on the inverter’s callback slot being reclaimed: reassigning that slot from inside its own invocation would destroy the closure being executed, and the over-current path fails from inside it. A sample arriving after the procedure ended is discarded before it reaches anything.
Abort() is the caller-facing form of the same shutdown.
It stops the alignment voltage and drops the pending completion
without invoking it, so a fault raised by the state
machine cannot be overwritten by an alignment result that arrives after
it.
Every decision above is taken in the sampling interrupt, and so is
everything that must happen at that instant: the inverter is stopped
there and, on success, the encoder zeroed there. The completion is not.
The caller stores calibration data and changes state from it, which is
event-loop work and far longer than a control period, so the outcome is
queued and delivered from the event loop. Until then the run is still in
progress: samples are discarded, a second ForceAlignment is
rejected, and Abort() drops the queued outcome. The outcome
carries the run it belongs to, so one that an abort overtook cannot
complete a run started after it. The step timeout already runs on the
event loop and completes the run directly, unless an outcome is already
queued, which it then leaves to be delivered.
The encoder position at the moment of settlement declaration becomes the calibration offset. This offset represents the mechanical angle the rotor adopts when the electrical d-axis is at 0°; subtracting it from any subsequent encoder reading yields the corrected angle for the closed-loop FOC controller.
The offset is in mechanical radians, which is the
unit Encoder::Set and Encoder::SetZero
consume. It is not scaled by pole pairs; scaling it would make it an
electrical angle and misorient the field.
The service applies the offset itself, by calling
Encoder::SetZero() at the settled instant while the rotor
is still held at electrical zero. It does not wait for the caller to
re-apply the reported value, because the rotor drifts as soon as the
bridge is released. The reported value is therefore a record of where
the encoder read before it was zeroed, and the calibration record
persisted to NVM is diagnostic: an incremental encoder loses its
reference across a reset, so the state machine does not re-apply a
stored alignment at boot and calibration must be re-run to orient the
field.
State Machine
The service follows a four-state machine:
While in the Aligning state, any further call to
ForceAlignment is silently ignored — the service will not
restart or nest. Only one alignment may be in progress at any time.
Transitions out of the Aligning state always stop the
inverter before invoking the callback, so the caller’s callback receives
control only after the hardware is in a safe, stopped state.
Completion Callback Semantics
The onDone callback accepts a single
std::optional<Radians> argument:
std::optionalwith a value: alignment succeeded; the contained value is the encoder offset in mechanical radians, already applied to the encoder by the service.- Empty
std::optional: alignment failed (timeout); the caller should not use any offset and may retry.
The callback fires exactly once per ForceAlignment
invocation, from the event loop and never from the sampling interrupt.
The callback is reset (using infra::AutoResetFunction
semantics) after firing so that stale references cannot cause a second
invocation.
Interfaces
Provided
| Interface | Purpose | Contract |
|---|---|---|
ForceAlignment(polePairs, AlignmentConfig, onDone) |
Starts the alignment procedure using the supplied configuration;
reports the calibrated encoder offset (or failure) via
onDone when complete |
Silently ignored if already aligning; onDone fires
exactly once, from the event loop; inverter is stopped before
onDone is called |
Required
| Interface | Purpose | Contract |
|---|---|---|
ThreePhaseInverter |
Open-loop voltage application during alignment; source of ADC sampling callbacks that drive the settlement state machine | Must not be concurrently claimed by any other controller during the alignment procedure |
Encoder |
Reads the current rotor mechanical angle at each sampling tick so that settlement can be detected and the offset can be captured | Must be initialised and tracking position before
ForceAlignment is called |