Just past "Getting Started"


I can get a motor to spin, so this doesn’t qualify as “getting started” but I’m still quite a ways from “actually using”. :slight_smile:
I’m re-purposing a cordless brushless power tool, specifically a Milwaukee 18V FUEL Mud Mixer (Milwaukee 2810-20). It’s a nice small package with an integrated planetary (52:1) gearset. Prior to disassembly I measured the output shaft at over 500 RPM (26,000 at the motor) and under near stall (measured 40NM), it was drawing 90amps from the battery pack. It has Hall sensors, but adding an encoder would be…very complicated. (tool only for about $150 US retail) Ultimately I’m looking to drive a leadscrew so I need trajectory/position control.

I mostly followed the setup for Hoverboard wheelmotors, adjusting polepairs to two (four magnets on the rotor) and encoder.config.cpr to 12. I had some trouble getting the encoder offset calibration to run, but I was able to get past it after setting motor.config.calibration_current much higher (40A) - this motor has a very high cogging torque.

At this point I have several issues:
I can put it in velocity mode and spin it at setpoints up to about 2000, beyond that it shuts down and hex(odrv0.axis0.error) gives me ‘0x60’. and odrv0.axis0.motor gives me an error 0x0400. At a setpoint of 2000 the unit is drawing about 2.5 amps from the battery pack.

I attempted to experiment with trajectory control, but odrv0.axis0.controller.config.control_mode = CTRL_MODE_TRAJECTORY_CONTROL gives me the error: NameError: name ‘CTRL_MODE_TRAJECTORY_CONTROL’ is not defined. What am I missing?

I’ve attempted to experiment with position control. I’ve had some limited success, but have at least two issues. I can only send it relatively short moves (under about 1000 counts) - longer moves seem to try to spin too fast and give me the same errors as above. And after it gets to destination, it “hunts”. Moving ± about 50 counts past the setpoint every few seconds. If I had to guess, I’d say that this is a combination of high inertial load and the cogging torque and could be improved with tuning, but I would have thought there should be some deadband?

The error 0x0400 seems to be ERROR_CURRENT_SENSE_SATURATION, which I think has something to do with the setting odrv0.axis0.motor.config.requested_current_range that was adjusted in the setup, but I don’t see any guidance of which way to adjust it based on what criteria or acceptable range?

Any help would be appreciated.


and just in case it helps…

In [93]: odrv0.axis0.motor
error = 0x0400 (int)
armed_state = 0 (int)
is_calibrated = True (bool)
current_meas_phB = 0.06314337253570557 (float)
current_meas_phC = -0.03106069564819336 (float)
DC_calib_phB = -1.210925817489624 (float)
DC_calib_phC = -1.49945068359375 (float)
phase_current_rev_gain = 0.012500000186264515 (float)
  p_gain = 0.0018225724343210459 (float)
  i_gain = 3.1034533977508545 (float)
  v_current_control_integral_d = -2.7737841606140137 (float)
  v_current_control_integral_q = -3.541830062866211 (float)
  Ibus = 6.324885845184326 (float)
  final_v_alpha = -4.412751197814941 (float)
  final_v_beta = -0.4657534062862396 (float)
  Iq_setpoint = 30.375 (float)
  Iq_measured = 3.320199966430664 (float)
  max_allowed_current = 30.375 (float)
  overcurrent_trip_level = 33.75 (float)
  drv_fault = 0 (int)
  TIMING_LOG_ADC_CB_I = 2526 (int)
  TIMING_LOG_ADC_CB_DC = 13062 (int)
  TIMING_LOG_MEAS_R = 0 (int)
  TIMING_LOG_MEAS_L = 0 (int)
  pre_calibrated = True (bool)
  pole_pairs = 2 (int)
  calibration_current = 40.0 (float)
  resistance_calib_max_voltage = 3.0 (float)
  phase_inductance = 1.822572448872961e-05 (float)
  phase_resistance = 0.03103453479707241 (float)
  direction = -1 (int)
  motor_type = 0 (int)
  current_lim = 50.0 (float)
  requested_current_range = 5.0 (float)
  current_control_bandwidth = 100.0 (float)


In [94]: odrv0.axis0.controller
error = 0x0000 (int)
pos_setpoint = -1.9750028848648071 (float)
vel_setpoint = 0.0 (float)
vel_integrator_current = 2.004138708114624 (float)
current_setpoint = 0.0 (float)
vel_ramp_target = 0.0 (float)
vel_ramp_enable = False (bool)
  control_mode = 2 (int)
  pos_gain = 1.0 (float)
  vel_gain = 0.019999999552965164 (float)
  vel_integrator_gain = 0.10000000149011612 (float)
  vel_limit = 10000.0 (float)
  vel_limit_tolerance = 1.2000000476837158 (float)
  vel_ramp_rate = 10000.0 (float)
  setpoints_in_cpr = False (bool)
set_pos_setpoint(pos_setpoint: float, vel_feed_forward: float, current_feed_forward: float)
set_vel_setpoint(vel_setpoint: float, current_feed_forward: float)
set_current_setpoint(current_setpoint: float)
move_to_pos(goal_point: float)


In [109]: odrv0
vbus_voltage = 20.126293182373047 (float)
serial_number = 2084378C3548 (int)
hw_version_major = 3 (int)
hw_version_minor = 5 (int)
hw_version_variant = 24 (int)
fw_version_major = 0 (int)
fw_version_minor = 4 (int)
fw_version_revision = 7 (int)
fw_version_unreleased = 0 (int)
user_config_loaded = True (bool)
brake_resistor_armed = True (bool)
  uptime = 139304 (int)
  min_heap_space = 14264 (int)
  min_stack_space_axis0 = 7852 (int)
  min_stack_space_axis1 = 7860 (int)
  min_stack_space_comms = 3136 (int)
  min_stack_space_usb = 1332 (int)
  min_stack_space_uart = 3964 (int)
  min_stack_space_usb_irq = 1820 (int)
  min_stack_space_startup = 556 (int)
  usb: ...
  i2c: ...
  brake_resistance = 0.4699999988079071 (float)
  enable_uart = True (bool)
  enable_i2c_instead_of_can = False (bool)
  enable_ascii_protocol_on_usb = True (bool)
  dc_bus_undervoltage_trip_level = 8.0 (float)
  dc_bus_overvoltage_trip_level = 25.920001983642578 (float)
  gpio1_pwm_mapping: ...
  gpio2_pwm_mapping: ...
  gpio3_pwm_mapping: ...
  gpio4_pwm_mapping: ...
  error = 0x0020 (int)
  step_dir_active = False (bool)
  current_state = 1 (int)
  requested_state = 0 (int)
  loop_counter = 1101765 (int)
  config: ...
  motor: ...
  controller: ...
  encoder: ...
  sensorless_estimator: ...
  trap_traj: ...
  error = 0x0000 (int)
  step_dir_active = False (bool)
  current_state = 1 (int)
  requested_state = 0 (int)
  loop_counter = 1101812 (int)
  config: ...
  motor: ...
  controller: ...
  encoder: ...
  sensorless_estimator: ...
  trap_traj: ...
  node_id = 0 (int)
  TxMailboxCompleteCallbackCnt = 0 (int)
  TxMailboxAbortCallbackCnt = 0 (int)
  received_msg_cnt = 0 (int)
  received_ack = 0 (int)
  unexpected_errors = 0 (int)
  unhandled_messages = 0 (int)
test_property = 0 (int)
test_function(delta: int)
get_oscilloscope_val(index: int)
get_adc_voltage(gpio: int)


A bit more digging found this thread:

Which got me past one small part, but other issues still stand.


I think you are getting ERROR_CURRENT_SENSE_SATURATION due to a current controller instability issue: can you please try the solution presented here?

As for the hunting, I think you can’t expect to hold an exact encoder (hall) count with such high cogging and such low resolution: so I would simply disable the velocity integrator completely. That should stop all (slow mode) hunting. controller.config.vel_integrator_gain = 0.
Though 50 counts seems a bit excessive for electromagnetic based cogging… still try without integrator first, and add it after you have tuned the rest.


On the attached image, pos_estimate is in blue and Iq_measured is in orange. You can see that current ramps up for about three seconds before it can break loose, then overshoots the commanded position (zero in this case) . I’ve not made any serious attempt at tuning at this point, I’m sure it can be better.

As suggested, I attempted to set odrv0.axis0.controller.config.vel_integrator_gain = 0. Then it wouldn’t make small moves at all. (under a couple hundred counts) and on larger moves it would stop ~150 counts before destination.

As to my speed limit issue, I’ve read through your linked post, and I’m game to try it. Is there a precompiled hex available? - I’ve yet to stand up a dev environment for this processor on my Win10 box. (pointers for that would be welcome as well)


Maybe I missed something but I went to the link and I still can’t understand how you fixed this problem

“I attempted to experiment with trajectory control, but odrv0.axis0.controller.config.control_mode = CTRL_MODE_TRAJECTORY_CONTROL gives me the error: NameError: name ‘CTRL_MODE_TRAJECTORY_CONTROL’ is not defined. What am I missing?”

I appreciate the help


“Fixed” is probably too strong a word. :slight_smile:
What I realized reading through that was that you don’t “put it into trajectory mode.” Rather, with it in position control mode you call the move_to_pos() function.

At the moment, I can get my motor to move with the following three commands:
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
odrv0.axis0.controller.config.control_mode = CTRL_MODE_POSITION_CONTROL

Where 624 hall counts is one output shaft rotation of my motor /gearbox unit.

What has also been really helpful is understanding that I can set the maximum motor speed with
odrv0.axis0.controller.config.vel_limit = 3000

But the trapezoidal trajectory planner has a separate speed limit (drive uses smaller of the two)

You can also limit the acc/dec with

After I achieve destination I send this to shut down the unit and stop hunting
odrv0.axis0.requested_state = AXIS_STATE_IDLE

(probably not a long term solution in my case)

There’s more, but that’s the crib notes I wish I had found on day one.

What I don’t understand at the moment, is that all of this works though the odrivetool, but I’ve started using the ASCII protocol though the UART - I can move the motor, but it does not use the trajectory planner, just max speed (which trips current limits).
The command I’m using at the moment is
P 0 624 0 0


Ok, really mad at myself at the moment. Totally read this page last week:

Somehow I completely missed the paragraph that suggests using the “t” command for trajectory moves.

No problem at all to send
t 0 624
and the motor moves one turn while respecting the trap_traj limits I’ve set.