← BlogBlog

A second motor changes the story

Last time the headline was the MS6 auto-tune profiler: measure the motor, fit its parameters on the host, write the gains back over the wire, and watch the sensorless controller improve on its own numbers. All of that was real — but it was real on exactly one motor. A profiler that only works on the motor it was built around isn’t a profiler, it’s a lookup table with extra steps. So the last stretch of work was about making it survive contact with a motor it had never seen. It didn’t survive cleanly, which is the whole point — it broke in the places that were actually load-bearing, and one of the breakages turned into the most interesting finding of the project so far.

Handing it a motor it’s never seen

The second motor is deliberately nothing like the first. Where the original bench motor has 7 pole pairs, a ~31 µs electrical time constant, and a tiny flux, this one is a 4-pole machine (2 pole pairs) with an electrical time constant around 360 µs — roughly 13× longer — and about 21× the flux linkage. Physically bigger, electrically slower, a completely different operating envelope. If the profiler’s math was secretly overfit to the first motor, this is where it would show.

Three things broke, and they were all the same kind of bug: constants that should have been parameters. The sensorless startup’s handoff speed, the frequency-ramp slew rate, and the current limit were all compile-time values sized for the first motor. On a machine with 21× the flux, the old default sweep speeds were literally physically unreachable — the back-EMF hits the voltage ceiling first. So those three became runtime parameters, live-settable over the protocol like everything else the profiler tunes.

With that done, closed-loop sensorless ran on the second motor on its own measured numbers, first attempt: it starts cleanly through the I-f ramp, hands off to the observer near 150 rad/s electrical, holds, live-retargets, and takes a commanded speed step. Here’s the actual capture — electrical speed estimate on top, q-axis current underneath, streamed off the hardware at 1 kHz:

Sensorless startup + speed step, second motor

Real capture · mmc-proto @ 1 kHz · the profiler's own fitted gains
I-f start 0 150 300 +1A -1A ω̂ electrical estimate (rad/s) i_q (A) 0s 2s 4s 6s 8s

The orange band is the open-loop I-f startup; the rotor is caught and handed to the flux observer near 150 rad/s, holds there, then the commanded target steps up and the loop tracks it. The current strip shows torque current rising to accelerate and settling back to the small value that just holds against friction. A separate 150→300 step lands at about 2% overshoot.

The friction on this larger motor, driven through a current-limited shield at a fraction of its rated current, is high enough that its accel run also gave up an inertia estimate — about 25 µN·m·s², which lines up with the datasheet plus the coupling to the bench fixture. The profiler measured a motor it had never seen, and the controller ran on those measurements. That’s the thing MS6 was actually supposed to prove, and now it’s proven twice, on two motors that share almost nothing.

The motor was hiding a trick

Here’s the finding I didn’t expect. One of the profiler’s stages is a saliency probe: it parks the rotor, excites the d-axis with a square wave stepped across a set of paired electrical angles, and records the q-axis current transient. That q-axis signal is a null channel — if the motor’s inductance is the same in every direction (Ld = Lq, a perfectly round rotor), it should be zero. It exists only if the motor is salient: if its inductance depends on rotor angle.

Surface-magnet motors like these bench motors are “supposed” to be round. The probe says otherwise. On the first bench motor it measures a saliency ratio Lq/Ld ≈ 1.16 — a 16% difference, well clear of the noise floor, and every internal consistency check on the fit is clean. It’s most likely saturation-induced rather than geometric, but it’s real and it’s measurable.

Why care? Saliency is information you can track at zero speed. The whole reason sensorless control needs an I-f startup ramp — that orange band in the plot — is that the flux observer is blind when the motor is barely moving; there’s no back-EMF to observe. But a salient rotor’s inductance signature rotates with the rotor, so if you can measure it, you can find the rotor angle standing still. A gate I’d assumed was closed for this class of motor — zero-speed position sensing — is at least physically open. There are real caveats: it needs a confirmation run (re-clamp the rotor and re-measure — genuine saliency rotates with the rotor, a measurement artifact wouldn’t), and actually productizing standstill sensing needs current-derivative sampling during the active PWM vectors that this hardware doesn’t make easy. But the probe that would drive it now exists and returns a real number. For a project whose north star is “sensorless-first,” finding hidden saliency in the bench motor is the kind of accident worth chasing.

Making it something you’d actually use

The other half of this stretch was much less glamorous and probably more valuable: turning a set of scripts into something usable at a bench without a cheat sheet.

  • Parameters survive a power cycle now. They used to live only in RAM, so every reflash or unplug reverted the device to firmware defaults — and re-entering a motor’s measured numbers by hand, several times a session, got old fast. There’s now a small persistence layer that writes a CRC-checked parameter blob to the last page of flash. It’s defensive by construction: on boot it range-validates every value before accepting it, so a corrupt or stale save can never brick startup — it just falls back to defaults. And because the erase-and-write lands in a different flash bank than the running code, the 20 kHz control loop never even pauses while it happens.
  • The profiler runs from the browser. The same measure → fit → apply sequence that was a chain of command-line invocations is now a card in the local control panel: tick the stages you want, hit run, watch the log stream, review, apply. The Python fits shell out and stream back automatically.
  • It speaks in units a multimeter agrees with. The resistance the profiler measures is the whole drive path — winding plus shunt plus the switches — which is the right number for the control loop but 5–6× what you’d read at the motor terminals. Both the tools and the panel now show the “at the motor” estimate beside the control value, and you can go the other way too: type in what your meter reads at the terminals and it back-converts to the internal parameters.

None of that is a new capability. It’s the difference between a demo and a tool, and it’s where a surprising amount of the real time goes.

Where this leaves the series

With the profiler proven on a second motor and the sensorless stack running closed-loop on both, MS6 — auto-tune — is effectively done. That moves the project onto MS7: an encoder as a second angle source, auto-calibrated against the observer, with a position loop stacked on top. That’s the missing piece for anything that has to hold a place rather than a speed — which is to say, every robot joint. The saliency thread runs alongside it as the more speculative path to the same destination from the other direction: holding position with no sensor at all.

More as it happens. The live firmware readout from the last post is still a good snapshot of what’s actually on the chip today.