The last post ended with six-step commutation next and position control waiting on an angle sensor. Both have now happened. Six-step went from forced commutation to a closed-loop sensorless drive that takes over from FOC while the motor is running. Position control became possible when a second board and a motor with hall sensors arrived on the bench. This post covers both, and the porting work in between.
Six-step on the small motor
Six-step (trapezoidal, block) commutation energises two phases at a time and leaves the third floating. The floating phase is where the rotor’s back-EMF can be read: when it crosses the midpoint of the bus, the rotor is 30° electrical from the next commutation. No observer, no Park transform, one PI loop on duty.
It went in steps, simulation first. The simulator was a dq-frame model with no floating terminal, so it gained a per-phase model with trapezoidal back-EMF before any firmware was written. On hardware:
- Forced commutation worked first. The floating phase was not readable at the original ADC sample point, so the sample was moved into the PWM on-time with a second timer trigger.
- Closed-loop commutation from measured zero-crossings worked once the reference was fixed. The midpoint is not Vbus/2 — the bridge’s own drops move it by about 0.7 V — so the reference became the measured midpoint of the two driven terminals.
- A speed loop on the crossing interval regulated to within ±7%, above a sensing floor of about 40 rad/s electrical on the 4-pole motor.
- ISR cost on the G474: six-step closed loop took 2653 cycles worst case against 4061 for the FOC current loop, about 35% less. Most of the difference is the flux observer, which does not run in six-step.
Then it stopped working on the small 7-pole-pair bench motor (30 µH, 0.94 mWb), and three sessions in a row produced three different explanations. The fourth session was a review of every six-step conclusion against the raw captures. Several did not survive:
| symptom | first explanation | what the captures showed |
|---|---|---|
| speed ramps trip overcurrent near the top | commutation transients on a 30 µH winding | the rotor slipping out of step: the open-loop duty feedforward ran out of margin at speed, torque collapsed, and the desync current tripped the limit |
| no zero-crossings at high speed | back-EMF too small to sense | the rotor was not at the commanded speed. A coast-down check found it at 771 rad/s against a 1200 rad/s commutation clock. On a rotor actually at 898 rad/s, the crossings were clean |
| handover desyncs after five sectors | detector tuning | a 250 µs blanking window after each commutation was swallowing early crossings. The demagnetisation it was guarding against lasts about 2.5 µs on this motor. At 30 µs it locked |
The fix that came out of it was to stop starting six-step from standstill on this motor. Sensorless FOC spins it up; six-step then takes over at the observer’s angle, with the zero-cross detector seeded with the observer’s speed and the duty loop preloaded. On the small motor that locked in 6 ms at about 865 rad/s electrical and held with zero faults, drawing 0.03–0.14 A.
The review also tested whether a higher PWM frequency would help. It splits:
| raising fsw | effect |
|---|---|
| current ripple | halves from 20 to 40 kHz |
| current-measurement bias | shrinks |
| noise within a sensing window | shrinks |
| fixed sensing offsets per sector | roughly double from 20 to 40 kHz |
| on-time sensing window | shrinks; minimum usable duty rose from 0.07 to 0.10 |
So higher fsw helps ripple and measurement, and hurts the low-speed back-EMF floor. 40 kHz PWM with a 20 kHz control loop is now the default on the G474.
A second board
The second bench is an STM32F302R8 (Cortex-M4F, 72 MHz, 16 KB RAM, one ADC) on an L6230 inverter shield, driving a motor with hall sensors. Before it could run anything, the firmware had to stop being a G474 program.
Until then, the only portable code was the control core. Everything the firmware does — the drive-mode state machine, probes, protection trips, the parameter table, command handling, telemetry — lived in the G474’s 1763-line main.rs, mixed in with register writes. It now sits in a new no_std crate, mmc-drive, behind a MotorBoard trait: sample the currents, set the duties, enable phases, report a driver fault, read the halls. The G474 crate dropped to about 560 lines of clocks and peripherals. Several hard-coded 20 kHz assumptions — the deadman timeout, the stall timer, the telemetry period and timestamp — became derived from the board’s control rate, and the F302 runs at 10 kHz.
A side benefit: the drive logic now runs in CI against a simulated board, at both 10 and 20 kHz. Before this, CI tested a host-side re-implementation of the drive, not the firmware’s own code.
Bring-up issues worth recording:
- First drive at 0.5 V faulted the driver and put the supply into current limit. The timer was configured correctly, but the PWM pins were in analog mode: embassy’s
PwmPinresets its pin when dropped, andlet _ = PwmPin::new(..)drops it immediately. The pins now live in the board struct. - The R/L probe’s first fit had a 30% spread. The rotor was still swinging in its detent after alignment, and its back-EMF biased the first few edges. The fitter now keeps edges within 5% of the median, and the spread dropped to under 1%.
The motor turned out to be a maxon EC-i 40 from ST’s robotics evaluation kit, which has a datasheet to check the profiler against:
| datasheet | profiler | |
|---|---|---|
| R (phase-to-phase) | 0.853 Ω | 0.835 Ω |
| L (phase-to-phase) | 0.675 mH | 0.712 mH |
| ψ | ≈ 6.5 mWb | 6.645 mWb |
| pole pairs | 7 | not measurable electrically |
Six-step on motor 3
The handover works on this motor too, and here the halls give an independent reference that the small motor never had. Neither controller uses them in this capture; they are only there to score the drive.
FOC hands the rotor to six-step, live
Real capture · motor 3 on the F302 board · 18 V busSensorless FOC spins the motor up and holds 600 rad/s electrical; at 5.4 s the drive switches to six-step at the observer's angle, with no stop and no open-loop ramp. The halls are not used by either controller here — they are only the reference. Over the six-step section the drive's speed reads 0.9956 of the hall speed, and the hall trace is the noisier of the two because its six sectors are not evenly spaced.
Scoring against the halls showed the six-step loop regulating to a speed about 5% higher than the rotor’s actual speed. There were three causes. The divider clipping was ruled out first. The detector assumed each commutation landed exactly half an interval after its crossing, but on a 10 kHz tick it lands up to a tick late, so every interval measured short. Rising and falling crossings are also detected with different delays, so consecutive intervals alternate long and short, and averaging (π/3)/T over that reads high. The detector now uses the time that actually elapsed, and computes speed from the sum of the last six intervals — one electrical revolution.
What the six-step loop thought its speed was
Real captures · same motor, same 600 rad/s targetLeft: the speed came from each 60° crossing interval, assuming every commutation landed exactly when it was scheduled. On a 10 kHz tick it lands up to one tick late, and rising and falling crossings are detected with different delays, so intervals alternate long and short. Right: speed from the summed last six intervals, using the time that actually elapsed. The loop was regulating to 600 rad/s in both cases; on the left the rotor was actually doing about 570.
The halls were also used to clean up sensorless FOC on this motor. Two timing errors in the observer were found by comparing its angle against the halls: it integrated the voltage command one tick before that voltage reached the winding, and on the F302 the compare registers load half a tick later still. With both fixed, the observer sits within 0.04 rad of the hall angle above 200 rad/s electrical. Sensorless startup was tripping overcurrent in the blend from I-f to the observer; the cause was a wrap-around in the blend’s angle gap when this light rotor outran the forced angle. After the fix it started 10 of 10 times, both directions.
Position control on hall sensors
With halls, MS7’s position loop has a sensor — a coarse one. Seven pole pairs give 42 hall states per revolution, 8.6° mechanical each. The question was how far that goes.
A hall-sensored speed loop gives out first. It works at 100 rad/s electrical, stick-slips at 50 and 20, and stalls at 5 and below. At those speeds the hall edges are more than 100 ms apart, and a speed loop on edge timing has nothing to work with between them. The calculator below shows the numbers for any pole count and speed.
Hall sensors at low speed
Interactive · drag the slidersDefaults are motor 3 at the slowest speed it tracked on halls (1 rad/s electrical). Halls give 6 states per electrical revolution, so their resolution is 60°/pole-pairs mechanical regardless of motor size.
So the position loop was built around the fact that the position is unknown between edges. The new HallPosition mode generates a trapezoidal reference, runs a PID on the unwrapped hall position, and commands q-axis current to FOC. Getting it to hold took five changes, in order:
- A plain PID on the interpolated hall angle limit-cycled ±10° in the simulator. With no velocity information between edges there is no damping.
- A tracker now predicts the rotor between edges from the commanded torque and the motor’s inertia, plus a learned load term. It snaps to each edge when one arrives, and if the prediction leaves a sector without an edge, it caps the speed estimate and learns the difference into the load. In simulation it held position at a hall edge to 0.01°.
- On hardware the motor needs about 0.22 A to break loose but only 0.11 A to keep moving. A friction feedforward was added, and the integrator is held off within half a sector of the target, where there is nothing to measure.
- A rotor resting on a hall boundary makes one sensor flicker every few milliseconds. Each flicker read as a speed and kicked the derivative term. A reversal edge is now treated as passing through zero speed, not as a speed measurement.
- Commutating FOC on the tracker’s angle could be 60° off at rest, halving the torque. FOC now commutates on the hall angle (at most 30° off) and the tracker only feeds the position loop.
Position steps on hall sensors alone
Real captures · motor 3 · HallPosition mode, fw 13The position is only known to one hall state — 8.6° mechanical on this 7-pole-pair motor — so the green band is as close as it can be asked to hold. The 90° move settles cleanly. On the 30° move the rotor sits still until the current reaches breakaway (about 0.22 A, twice the running friction), then jumps past the target before the loop pulls it back.
Steps of 90°, −360°, 45°, −45°, 30° and −100° all settle and hold within the hall resolution, in 0.4–2.6 s. Long moves are clean. Short moves overshoot by 30–60°, because the rotor does not move until the current reaches breakaway, and then it moves faster than the loop can stop.
Slow constant-speed moves show the same thing from the other side:
How slow will it go on halls?
Real captures · motor 3 · slow constant-speed movesA slow constant speed is a far target with a low speed limit on the trapezoid. At 10 rad/s el the position follows the ramp. At 1 rad/s el (1.4 rpm) the average speed still tracks to within 5%, but as stick-slip: the rotor sticks, falls behind the ramp (about 22° on average), then jumps forward and sometimes back. At 0.5 rad/s el it never breaks loose in 10 s.
The limit here is the motor’s stiction seen through 8.6° of hall resolution, not the loop. The simulator has no stiction or cogging, so none of the hardware issues in steps 3–5 above showed up there; adding them to the model is on the list.
Where things stand
- MS8, six-step: closed-loop sensorless six-step runs on two motors via a live handover from FOC, with its speed estimate checked against hall sensors. Still open: adaptive blanking, a simulator scenario for the handover, and a standalone start that does not need FOC.
- MS7, position: a first position loop runs on halls and holds to their resolution. Next on halls is a breakaway boost — extra current until the first edge of a move — to cut the short-move overshoot. The motor also carries a 1024-line encoder, not yet wired to this shield; that comes later.
- Portability: the drive logic is board-independent and tested in CI. The G474’s peripheral setup is still register-level and moves onto the HAL the next time that board is on the bench.
The repository has the code, the captures behind every chart above, and the full progress log.