Sensorless handoff never produces real torque on low-inductance FPV motor (ODrive Pro, 0.6.6 & 0.6.11) — telemetry shows lock, motor is actually stalled

Hardware: ODrive Pro, firmware tested on both 0.6.6 and 0.6.11 (same issue on both)
Motor: T-Motor Velox 3.0 2306, 1950KV, 7 pole pairs (12N14P), no encoder — sensorless only
Power: 24V DC supply, regen clamp on the bus
Calibrated values: phase_resistance ≈ 0.084Ω, phase_inductance ≈ 31.4µH (calibration_current=40A, resistance_calib_max_voltage=7V)

I can get the open-loop sensorless ramp to run cleanly at any speed from ~10 turns/s up to 50+ turns/s. The problem is specifically at (or shortly after) the open-loop→closed-loop handoff: the axis either disarms immediately (CURRENT_LIMIT_VIOLATION or DC_BUS_OVER_REGEN_CURRENT) or, with very low vel_gain, survives indefinitely in CLOSED_LOOP_CONTROL state with disarm_reason staying 0 — but the motor is not actually spinning. I confirmed this by hand: with the drive reporting a smooth ~600 RPM tracked speed (driven and estimated columns agreeing within a few percent), the rotor is physically stalled.

This means the telemetry (motor.foc.phase_vel and motor.sensorless_estimator.phase_vel) was self-consistent but decoupled from reality — likely because at near-zero vel_gain the controller just follows the estimator’s output, and the estimator was extrapolating from the controller’s commanded frequency, with no actual back-EMF correction happening. I want to flag this because it means any automated check based on these fields alone (e.g. “does estimated velocity match target”) could give a false positive while the motor is actually stalled and heating.

What I’ve tried (all fail the same way, or produce the false-lock behavior above)

  • torque_constant: swept from 8.27/1950 (rated-KV) up to 2.5x that value. Higher values measurably reduced estimator noise/oscillation during the ramp, consistent with the Kt-sensitivity discussed in this thread, but never resulted in genuine sustained rotation past handoff.
  • sensorless_observer_gain: 500 (default), 200, 50
  • sensorless_pll_bandwidth: 1000 (default), 500, 300, 150
  • current_control_bandwidth: 1000 (default), 300 (closer to the motor’s own L/R electrical bandwidth of ~427Hz)
  • sensorless_ramp target speed: 10 to 50+ turns/s (well above the ~1-2V back-EMF floor for this KV)
  • vel_gain / vel_integrator_gain: swept from defaults down to vel_gain=0.0001, vel_integrator_gain=0 (per the fix in this thread) — this is what produced the “false lock” (stalled motor, clean-looking telemetry) described above
  • Reverted sensorless_pm_flux_linkage to automatic (left at 0.0/not manually overridden) per your advice in the thread linked above
  • bEMF_FF_enable / wL_FF_enable: tried both on and off
  • Confirmed calibration is accurate: current soft/hard max raised well above anything we hit (80/100A), dc_max_positive/negative_current given generous headroom

Questions

  1. Is this motor (31µH inductance, 84mΩ resistance) simply outside the range the sensorless estimator can reliably handle on ODrive Pro? I found several independent sources describing motors in this inductance range as a known-hard case for general FOC sensorless control due to current ripple at achievable PWM frequencies — wondering if that’s the actual ceiling here rather than a tuning issue.

  2. Is there a way to directly verify, from the API, whether the sensorless estimator has a genuine lock vs. is just echoing the commanded ramp value?

  3. Any other sensorless-specific parameters worth trying that I haven’t found via the Python API surface?

Happy to share full config dumps / data logs for any specific test if useful.

Hi!

A few notes:

  • The main problem is the handoff speed. Minimum sensorless speed is typically whatever gives about 1-2V of back-EMF, which at 1950 RPM/V is ~2,000-4,000 RPM. At 600 RPM this motor only makes ~0.3V, so the estimator never has anything real to lock onto. Which explains the trips at the handoff and the “stalled but tracking” state
  • sensorless_ramp.vel is in electrical rev/s, so if you set 10-50 there, that’s only ~90-430 RPM on 7 pole pairs. I’d set it to 1950/60*7 = ~230 minimum, ideally 450-599 (599 is the max, ~5,100 RPM). Once it’s handed off, INPUT_MODE_VEL_RAMP gets you to higher speeds.
  • vel_limit (turns/s) needs to be above sensorless_ramp.vel / pole_pairs, e.g. 100 for a 599 handoff, otherwise the setpoint gets clamped right at the handoff. The sensorless docs have a stray 2π in that line, sorry about that.
  • Put torque_constant back to 8.27/1950 = 0.00424 (or measure the KV with a scope). With sensorless_pm_flux_linkage at 0, the estimator takes its flux linkage from torque_constant, so 2.5x on Kt is 2.5x on its back-EMF model.
  • The default vel_gain is sized for much bigger motors, and on this one it’s ~40A per turn/s of error. 0.0001 is ~0.2A at 10 turns/s of error, so the rotor stopping there was expected. Super back of the napkin, I’d start around vel_gain = 0.0015, vel_integrator_gain = 0.007 (the docs’ D5065 values scaled by torque constant) and tune from there.
  • Everything else I think you should set back to defaults (sensorless_observer_gain and sensorless_pll_bandwidth at 1000, current_control_bandwidth at 1000 (it doesn’t need to match the motor’s L/R, the current gains are calculated from R and L for you), and bEMF_FF_enable / wL_FF_enable off until the handoff works, since both run off the estimator’s velocity).

Is this motor (31µH inductance, 84mΩ resistance) simply outside the range the sensorless estimator can reliably handle on ODrive Pro?

No issues at all, I’d only start worrying about ripple below ~5-10µH.

Is there a way to directly verify, from the API, whether the sensorless estimator has a genuine lock vs. is just echoing the commanded ramp value?

Not from the drive’s own signals.

For bring-up, you can use an external reference, like a hall sensor, an optical tach, or tape on the bell + your phone’s slow-mo (please don’t check by hand at 5,000 RPM :slight_smile: ). In operation, my recommendation is host-side – Iq well outside what that speed should need for your load for ~1s → IDLE, wait for spin-down, relaunch.

Any other sensorless-specific parameters worth trying that I haven’t found via the Python API surface?

None come to mind! the ones that matter here are the ramp velocity, vel_limit, torque_constant and the velocity gains.

One question - what speed range do you actually need, and what’s the load?