Spinout on hoverboard motors

I have been tweaking the tuning and parameters for a couple days, but keep getting a Spinout error. Running 2 8" hoverboard motors on Odrive 3.6, firmware and odrivetools updated to 0.5.2. Power is from 2 Milwaukee LiPo batteries in series for about 40V. This is on a lawnmower frame with casters in front and motors on rear.
Have completed initial motor and encoder calibration, and subsequent tuning.

My tuning values:
odrv0.axis0.controller.config.pos_gain = 1.5
odrv0.axis0.controller.vel_gain = 5.0
odrv0.axis0.controller.config.vel_integrator_gain = 50

I see in the release notes for 0.5.2 that there are these new parameters, but I don’t know where to begin changing them:
mechanical_power_bandwidth, electrical_power_bandwidth, spinout_electrical_power_threshold, spinout_mechanical_power_threshold added to controller.config for spinout detection.

The problem arises when driving on bumpy lawn and one of the motors loses traction and spins.

This is the error I’m getting:

dump_errors(odrv0)
system: no error
axis0
  axis: no error
  motor: Error(s):
    MOTOR_ERROR_UNKNOWN_TORQUE
    MOTOR_ERROR_UNKNOWN_VOLTAGE_COMMAND
  sensorless_estimator: no error
  encoder: no error
  controller: Error(s):
    CONTROLLER_ERROR_SPINOUT_DETECTED
axis1
  axis: no error
  motor: Error(s):
    MOTOR_ERROR_UNKNOWN_TORQUE
    MOTOR_ERROR_UNKNOWN_VOLTAGE_COMMAND
  sensorless_estimator: no error
  encoder: no error
  controller: Error(s):
    CONTROLLER_ERROR_SPINOUT_DETECTED

and here is my axis0 config:

odrv0.axis0
Out[8]: 
acim_estimator:
  config: ...
  phase_offset: 0.0 (float)
  rotor_flux: 0.0 (float)
  slip_vel: 0.0 (float)
  stator_phase: 2.0783329010009766 (float)
  stator_phase_vel: 0.0 (float)
config:
  calibration_lockin: ...
  can: ...
  dir_gpio_pin: 2 (uint16)
  enable_sensorless_mode: False (bool)
  enable_step_dir: False (bool)
  enable_watchdog: False (bool)
  general_lockin: ...
  sensorless_ramp: ...
  startup_closed_loop_control: True (bool)
  startup_encoder_index_search: False (bool)
  startup_encoder_offset_calibration: False (bool)
  startup_homing: False (bool)
  startup_motor_calibration: False (bool)
  step_dir_always_on: False (bool)
  step_gpio_pin: 1 (uint16)
  watchdog_timeout: 0.0 (float)
controller:
  anticogging_valid: False (bool)
  autotuning: ...
  config: ...
  electrical_power: -12.752242088317871 (float)
  error: 128 (uint8)
  input_pos: 0.0 (float)
  input_torque: 0.0 (float)
  input_vel: 0.01676177978515625 (float)
  last_error_time: 144.9705047607422 (float)
  mechanical_power: -2.788583944006386e-43 (float)
  move_incremental(obj: object_ref, displacement: float, from_input_pos: bool)
  pos_setpoint: 0.0 (float)
  start_anticogging_calibration(obj: object_ref)
  torque_setpoint: 0.0 (float)
  trajectory_done: True (bool)
  vel_integrator_torque: 12.80757999420166 (float)
  vel_setpoint: 0.01676177978515625 (float)
current_state: 1 (uint8)
encoder:
  calib_scan_response: 0.0 (float)
  config: ...
  count_in_cpr: 56 (int32)
  delta_pos_cpr_counts: -5.605193857299268e-45 (float)
  error: 0 (uint16)
  hall_state: 2 (uint8)
  index_found: False (bool)
  interpolation: 0.5 (float)
  is_ready: True (bool)
  phase: 2.0783329010009766 (float)
  pos_abs: 0 (int32)
  pos_circular: 0.6330385804176331 (float)
  pos_cpr_counts: 56.975059509277344 (float)
  pos_estimate: -3.3669183254241943 (float)
  pos_estimate_counts: -303.02264404296875 (float)
  set_linear_count(obj: object_ref, count: int32)
  shadow_count: -304 (int32)
  spi_error_rate: 0.0 (float)
  vel_estimate: 0.0 (float)
  vel_estimate_counts: 0.0 (float)
error: 0 (uint32)
is_homed: False (bool)
last_drv_fault: 0 (uint32)
max_endstop:
  config: ...
  endstop_state: False (bool)
mechanical_brake:
  config: ...
  engage(obj: object_ref)
  release(obj: object_ref)
min_endstop:
  config: ...
  endstop_state: False (bool)
motor:
  DC_calib_phA: -0.9716607928276062 (float)
  DC_calib_phB: 0.6928533911705017 (float)
  DC_calib_phC: 0.27865874767303467 (float)
  I_bus: 0.0 (float)
  config: ...
  current_control: ...
  current_meas_phA: 0.97132807970047 (float)
  current_meas_phB: -0.6932101845741272 (float)
  current_meas_phC: -0.2786383628845215 (float)
  effective_current_lim: 25.0 (float)
  error: 4563402752 (uint64)
  fet_thermistor: ...
  is_armed: False (bool)
  is_calibrated: True (bool)
  last_error_time: 144.91799926757812 (float)
  max_allowed_current: 30.375 (float)
  max_dc_calib: 3.0375001430511475 (float)
  motor_thermistor: ...
  n_evt_current_measurement: 1931844 (uint32)
  n_evt_pwm_update: 1931852 (uint32)
  phase_current_rev_gain: 0.012500000186264515 (float)
requested_state: 0 (uint8)
sensorless_estimator:
  config: ...
  error: 0 (uint8)
  phase: -1.6794039011001587 (float)
  phase_vel: -8.6188294517342e-05 (float)
  pll_pos: -1.6794037818908691 (float)
  vel_estimate: -3.286079845565837e-06 (float)
step_dir_active: False (bool)
steps: 0 (int64)
task_times:
  acim_estimator_update: ...
  can_heartbeat: ...
  controller_update: ...
  current_controller_update: ...
  current_sense: ...
  dc_calib: ...
  encoder_update: ...
  endstop_update: ...
  motor_update: ...
  open_loop_controller_update: ...
  pwm_update: ...
  sensorless_estimator_update: ...
  thermistor_update: ...
trap_traj:
  config: ...
watchdog_feed(obj: object_ref)

Starting to dig into this, here is my controller config:

odrv0.axis0.controller
Out[5]: 
anticogging_valid: False (bool)
autotuning:
  frequency: 0.0 (float)
  pos_amplitude: 0.0 (float)
  pos_phase: 0.0 (float)
  torque_amplitude: 0.0 (float)
  torque_phase: 0.0 (float)
  vel_amplitude: 0.0 (float)
  vel_phase: 0.0 (float)
config:
  anticogging: ...
  axis_to_mirror: 255 (uint8)
  circular_setpoint_range: 1.0 (float)
  circular_setpoints: False (bool)
  control_mode: 2 (uint8)
  electrical_power_bandwidth: 20.0 (float)
  enable_current_mode_vel_limit: True (bool)
  enable_gain_scheduling: False (bool)
  enable_overspeed_error: True (bool)
  enable_vel_limit: True (bool)
  gain_scheduling_width: 10.0 (float)
  homing_speed: 0.25 (float)
  inertia: 0.0 (float)
  input_filter_bandwidth: 2.0 (float)
  input_mode: 1 (uint8)
  load_encoder_axis: 0 (uint8)
  mechanical_power_bandwidth: 20.0 (float)
  mirror_ratio: 1.0 (float)
  pos_gain: 1.5 (float)
  spinout_electrical_power_threshold: 10.0 (float)
  spinout_mechanical_power_threshold: -10.0 (float)
  steps_per_circular_range: 1024 (int32)
  torque_mirror_ratio: 0.0 (float)
  torque_ramp_rate: 0.009999999776482582 (float)
  vel_gain: 5.0 (float)
  vel_integrator_gain: 50.0 (float)
  vel_limit: 10.0 (float)
  vel_limit_tolerance: 1.2000000476837158 (float)
  vel_ramp_rate: 1.0 (float)
electrical_power: -0.03492157906293869 (float)
error: 0 (uint8)
input_pos: 0.0 (float)
input_torque: 0.0 (float)
input_vel: 0.0 (float)
last_error_time: 0.0 (float)
mechanical_power: 0.0 (float)
move_incremental(obj: object_ref, displacement: float, from_input_pos: bool)
pos_setpoint: 0.0 (float)
start_anticogging_calibration(obj: object_ref)
torque_setpoint: 0.0 (float)
trajectory_done: True (bool)
vel_integrator_torque: 0.0 (float)
vel_setpoint: 0.0 (float)

I found this from issue #462 during implementation of Spinout detection:

Incorrect encoder offset can lead to an uncontrolled spinout situation due to inadvertent flipped sign of torque production. Incorrect encoder offset can occur due to missed pulses in an incremental encoder, or general incorrect calibration.

I don’t believe I am missing encoder pulses (am using the built in Hall sensors), as both motors will produce the same error, under the same conditions.

I found this post with same errors:
https://discourse.odriverobotics.com/t/multiple-errors-when-during-trap-moves-with-high-accelerations/7512

He was advised:

You’re accelerating harder than the current controller can keep up. Try increasing motor.config.current_control_bandwidth to 1500 or 2000.

I will try changing my odrv0.axis0.motor.config.current_control_bandwidth and see how that goes, currently = 100.

odrv0.axis0.motor
Out[2]: 
DC_calib_phA: -1.0117568969726562 (float)
DC_calib_phB: 0.7242457866668701 (float)
DC_calib_phC: 0.2878829836845398 (float)
I_bus: 0.0004915696335956454 (float)
config:
  I_bus_hard_max: inf (float)
  I_bus_hard_min: -inf (float)
  I_leak_max: 0.10000000149011612 (float)
  R_wL_FF_enable: False (bool)
  acim_autoflux_attack_gain: 10.0 (float)
  acim_autoflux_decay_gain: 1.0 (float)
  acim_autoflux_enable: False (bool)
  acim_autoflux_min_Id: 10.0 (float)
  acim_gain_min_flux: 10.0 (float)
  bEMF_FF_enable: False (bool)
  calibration_current: 10.0 (float)
  current_control_bandwidth: 100.0 (float)
  current_lim: 25.0 (float)
  current_lim_margin: 8.0 (float)
  dc_calib_tau: 0.20000000298023224 (float)
  inverter_temp_limit_lower: 100.0 (float)
  inverter_temp_limit_upper: 120.0 (float)
  motor_type: 0 (uint8)
  phase_inductance: 0.0003976746229454875 (float)
  phase_resistance: 0.228278249502182 (float)
  pole_pairs: 15 (int32)
  pre_calibrated: True (bool)
  requested_current_range: 25.0 (float)
  resistance_calib_max_voltage: 4.0 (float)
  torque_constant: 0.5168750286102295 (float)
  torque_lim: inf (float)
current_control:
  I_measured_report_filter_k: 1.0 (float)
  Ialpha_measured: -0.4351482391357422 (float)
  Ibeta_measured: 0.050543807446956635 (float)
  Id_measured: 0.5140417814254761 (float)
  Id_setpoint: 0.0 (float)
  Iq_measured: 0.0461774542927742 (float)
  Iq_setpoint: 0.0 (float)
  Vd_setpoint: 0.0 (float)
  Vq_setpoint: 0.0 (float)
  final_v_alpha: -0.06178823113441467 (float)
  final_v_beta: 0.017945382744073868 (float)
  i_gain: 22.82782554626465 (float)
  p_gain: 0.03976746276021004 (float)
  phase: -0.016060352325439453 (float)
  phase_vel: 0.0 (float)
  power: 0.01660437136888504 (float)
  v_current_control_integral_d: -0.07354786247015 (float)
  v_current_control_integral_q: 0.01986502856016159 (float)
current_meas_phA: -0.07055532932281494 (float)
current_meas_phB: -0.14246290922164917 (float)
current_meas_phC: 0.19290730357170105 (float)
effective_current_lim: 25.0 (float)
error: 0 (uint64)
fet_thermistor:
  config: ...
  temperature: 34.1611213684082 (float)
is_armed: True (bool)
is_calibrated: True (bool)
last_error_time: 0.0 (float)
max_allowed_current: 30.375 (float)
max_dc_calib: 3.0375001430511475 (float)
motor_thermistor:
  config: ...
  temperature: 0.0 (float)
n_evt_current_measurement: 663201 (uint32)
n_evt_pwm_update: 663206 (uint32)
phase_current_rev_gain: 0.012500000186264515 (float)
1 Like

I set

odrv0.axis0.motor.config.current_control_bandwidth = 1000

This gave an improvement in torque and acceleration before spinout. Tried going all the way up to 2000 with no real gains.

Still getting these same errors:

dump_errors(odrv0)
system: no error
axis0
  axis: no error
  motor: Error(s):
    MOTOR_ERROR_UNKNOWN_TORQUE
    MOTOR_ERROR_UNKNOWN_VOLTAGE_COMMAND
  sensorless_estimator: no error
  encoder: no error
  controller: Error(s):
    CONTROLLER_ERROR_SPINOUT_DETECTED
axis1
  axis: no error
  motor: Error(s):
    MOTOR_ERROR_UNKNOWN_TORQUE
    MOTOR_ERROR_UNKNOWN_VOLTAGE_COMMAND
  sensorless_estimator: no error
  encoder: no error
  controller: Error(s):
    CONTROLLER_ERROR_SPINOUT_DETECTED

Ah yeah, try tweaking the encoder bandwidth. What’s it set to now?

encoder.config.bandwidth = 100. That must bee the default as I have not changed it.

100 is the setting we recommend for hall effect sensors but the default is 1000

How do I completely disable spin out detection?

1 Like

I have running two 6.5’ hoverboard motors on Odrive 3.6 56V, and had same issue.
When I was using the 0.5.1 firmware, spinout detection was not happened and was able to do drifting with those motors.
But when I upgraded to 0.5.4 firmware, the motor stops working after slip the tire, and error dump was same as first post above.

And I finally find this thread, I had a test to change new parameter, “spinout_electrical_power_threshold” and “spinout_mechanical_power_threshold”.

spinout_electrical_power_threshold was 10 at default , changed to 1000
spinout_mechanical_power_threshold was -10 at default, changed to -1000

after changed those two parameters for each axis, I never see the spinout detection that stop the motor.
I don’t know about how those parameter works(it seems unit is watt), but the motor does not spinout under load, rather it stops with another error. ( MOTOR_ERROR_UNKNOWN_CURRENT_MEASUREMENT)

Same issue here … Can someone explain what are the spinout parameters used for

It’s a safety detection. We calculate the mechanical power and compare it to the electrical power, if it’s too different then we error out. It’s designed to fault the odrive if it loses control of the motor. Changing the spinout parameters could be a safety issue, please only do so carefully.

1 Like

@firtel, if you simply replaced the spinout error with the MOTOR_ERROR_UNKNOWN_CURRENT_MEASUREMENT error, what have you gained?

@Wetmelon, I understand that spinout could be a safet issue and it is great to be able to detect it, but we NEED to be able to change the parameters and continue operation in certain conditions. Almost any parameter could be considered a safety issue.

So, what is the proper way to disable spinout detection, so that operation can continue despite having some spin??

I have mostly resolved my issues just by using bigger tires and a little spring suspension on the lawnmower so the drive wheels do not become airborne over bumps.

This helped me a lot. But it was interesting for me to know the theory behind. So thanks @Wetmelon for the explaination.

@rhauff , When I got MOTOR_ERROR_UNKNOWN_CURRENT_MEASUREMENT, motor will stop until clearing the error. but this error seems only occurs when high load is applied to the motor. The error frequency of MOTOR_ERROR_UNKNOWN_CURRENT_MEASUREMENT is much less than spinout detection error at default spinout detection threshold.

@rosendhr I just looked at the source code controller.cpp, and found if I change this threshold, it will not get into statement that makes ERROR_SPINOUT_DETECTED.
Also spinout detection was not implemented at 0.5.1, So I just wanted to make it behavior like 0.5.1. The reason why I want to use 0.5.4 is to change PID gain from CAN BUS.

Thank you @firtel, i will just do as you suggested and change

spinout_electrical_power_threshold from 10 change to 1000
spinout_mechanical_power_threshold from -10 change to -1000

Set the thresholds really high.

Can you please define “mechanical power”
My application is using motor as a force feedback controller, so it is constantly stalled, while I violently spin it the opposite way it wants to go, in some extreme conditions it is triggering SPINOUT_DETECTED.

Could my counter spinning be confusing spinout detection and it should be disabled or is it a bandwidth thing due to my 40K PPR encoder?

Ps:I have gone all out on reducing noise on the encoder and even installing a low latency differential signal receiver and have been monitoring it with a multi-channel scope, so I am pretty certain it is not an actual spinout nor noise.