High Torque quasi-quasi direct drive Power Supply Questions

Hi! New to the forum and Odrive. I’m working on a robotics application that will require pretty high torque. I purchased the M8325s kit with s1 and I’m blown away with the quality and interface. I read that the motor at 40A continuous current can support 3.31 NM.

My motor design is similar to the MIT mini cheetah (QDD) but with a 1:25 gear ratio through a two stage planetary (1:5) gear drive (sso not really quasi at that ratio, but maybe quasi quasi). So far so good! I want to start pushing the design (25 * 3NM = 75NM), but I’ve realized that I’ll cap out on current. I’m currently using the 48V 600W (12.5A) Mean-Well power supply. Given that it will cap at about a quarter of the continuous current capability of the M8325s I’m guessing I’ll need to upgrade. Are there any recommendations for pushing the M8325s? What was used to test the 40A spec?

My ideas were: find a used go-kart, bike, or server battery?
Gang a few smaller power supplies together with Diode ORing circuits?

Would love thoughts! Thanks!

Glad to hear the ODrive is working out for you!

Note that power supply current != motor current: Things in Motion: How to select the right power source for a hobby BLDC (PMSM) motor. For instance, if your motor’s at zero speed, then 12.5A at 48V (600W) from the power supply corresponds to 130A at the motor! You can set an active bus current or power limit via the active power limit feature – so you can take full advantage of your supply’s power output no matter the operating point. Note also the 600W Mean-Well we supply is designed to peak at higher currents: https://www.meanwell.com/webapp/product/search.aspx?prod=LRS-600N2

The 40A limit on the M8325s is the maximum continuous current the motor can take in free air, however it can definitely go much higher in transients. Note though that at 48V, the S1 has about a 40A current limit, see here: ODrive S1 Datasheet — ODrive Documentation 0.6.10 documentation

One option would be to run the system at a lower voltage, anywhere under 34-36V or so will allow 80A continuous from the S1, at the expense of maximum motor speed (see here for a calculator/graph: ODrive Motors — ODrive Documentation 0.6.10 documentation).

Hi solomondg, this was incredibly helpful! Thank you for clarifying. No longer worried about the power supply being my constraint.

I finally got some time to work on the project and got a couple CNC parts back to push the actuator to its limit. If the M8325s is able to do about 3NM at stall torque, that plus my gear ratio should be an actuator stall torque of 75NM? I have it lifting cinder blocks with a fish scale attached on a 2 foot pole. It seems to be stalling at closer to 54NM. Nothing is bending or breaking, just seems to be hitting its stall torque. The tool seems to be reading out 32 Amps at stall. Any thoughts as to why it seems to be leaving 10amps/20NM on the table?

For configuration parameters I set everything pretty default. The motor limit is set to 50A. No limits are set on the supply. My temp limit is 130C. The control mode is “ramped velocity control.” When conducting this test I was using position control. When using torque control, strangely it capped at 20A. Would love thoughts and advice. Thanks!

Hi!

When using torque control, strangely it capped at 20A

In torque control mode, with torque mode velocity limit enabled, the ODrive uses vel_gain to derate the torque versus speed. With high torque motors, this can cause a torque limit even at standstill – see here for more information: Controller — ODrive Documentation 0.6.11 documentation

I’d recommend either tuning your vel_gain, or disabling enable_torque_mode_vel_limit – note that the latter can have safety considerations (as it removes the ODrive’s ability to limit the speed of your motor), so I’d strongly recommend just tuning your vel_gain.

I finally got some time to work on the project and got a couple CNC parts back to push the actuator to its limit. If the M8325s is able to do about 3NM at stall torque, that plus my gear ratio should be an actuator stall torque of 75NM? I have it lifting cinder blocks with a fish scale attached on a 2 foot pole. It seems to be stalling at closer to 54NM. Nothing is bending or breaking, just seems to be hitting its stall torque. The tool seems to be reading out 32 Amps at stall. Any thoughts as to why it seems to be leaving 10amps/20NM on the table?

Okay, so I’m just going to reiterate your system config as I understand it, correct me if anything’s wrong:

  • You’re using M8325s, which has a default 50A current limit in the GUI
  • You’re using a S1 + 48V supply. Given the S1’s voltage-current derating, we can assume a 40A worst-case maximum current.
  • You’re using the S1’s onboard encoder, with the S1 mounted to the M8325s
  • You have the M8325s connected to the 1:25 planetary, with a 2’ pole, a heavy load, and the maximum torque you’re able to get from the motor is (seemingly) about 2.16Nm.

Now, when you say “The tool seems to be reading out 32 Amps at stall,” what exactly are you referring to? Do you mean the estimated current based on the force you’re reading on the scale? Or the current shown in the ODrive GUI?

I would go to the inspector tab, and find and graph the following properties:

  • effective_torque_setpoint
  • iq_measured
  • iq_setpoint

If you can send a plot of these while you’re doing that load test, that would be very helpful.

Also make sure torque_soft_min is less than -3Nm (e.g. -5Nm) and torque_soft_max is greater than 3Nm (e.g. 5Nm).

Also look for fet_thermistor.temperature and motor_thermistor.temperature, and make sure both of those are below about 80C – the ODrive may be getting hot and self-limiting.

I’d also note that torque control mode is likely the right choice for this – after either tuning your vel_gain or disabling enable_torque_mode_vel_limit.

Thanks again for the help!

Your understanding of the setup is spot on! If my garage wasn’t such a mess I’d post a picture, but here’s a simple diagram and a photo of my actuator. It’s mounted on an optics table, so it’s pretty sturdy.


I disabled the enable_torque_mode_vel_limit setting as I wasn’t sure how to tune the velocity gain (I imagine more would enable higher torque at risk of instability or the actuator jerking?).

The 32A stall limit is what I measured on the tool from the main screen when controlling from position or torque control. Screenshot below:

Here is a capture of the three variables all in one go. I slowly incremented the position until the rope is taught (around 90 degrees or parallel to the floor), and then increment further to induce the motor into stall torque.



Your understanding of the setup is spot on! If my garage wasn’t such a mess I’d post a picture

Mine is my home office and infinitely worse :wink:

The 32A stall limit is what I measured on the tool from the main screen when controlling from position or torque control. Screenshot below:

Fully understood. If you have ODrivetool installed, could you run odrivetool backup-config config.json and upload the resulting config.json here? That’ll let me see your ODrive’s full parameter dump, helps a lot with debugging.

Haha it’s inevitable. If I get this design working I’ll post a lackaday article or something and make sure to clean up first.

Here’s the JSON! It won’t let me upload so here’s the raw text:

{
“can.config.baud_rate”: 250000,
“can.config.data_baud_rate”: 10000000,
“can.config.tx_brs”: 0,
“can.config.protocol”: 0,
“config.enable_uart_a”: false,
“config.uart_a_baudrate”: 115200,
“config.usb_cdc_protocol”: 3,
“config.uart0_protocol”: 3,
“config.max_regen_current”: 0.0,
“config.dc_bus_undervoltage_trip_level”: 10.5,
“config.dc_bus_overvoltage_trip_level”: 54.0,
“config.dc_max_positive_current”: Infinity,
“config.dc_max_negative_current”: -Infinity,
“config.user_config_0”: 0,
“config.user_config_1”: 0,
“config.user_config_2”: 0,
“config.user_config_3”: 0,
“config.user_config_4”: 0,
“config.user_config_5”: 0,
“config.user_config_6”: 0,
“config.user_config_7”: 0,
“config.gpio0_mode”: 17,
“config.gpio1_mode”: 17,
“config.gpio2_mode”: 17,
“config.gpio3_mode”: 17,
“config.gpio4_mode”: 17,
“config.gpio5_mode”: 17,
“config.gpio6_mode”: 17,
“config.gpio7_mode”: 17,
“config.gpio8_mode”: 17,
“config.gpio9_mode”: 17,
“config.gpio10_mode”: 17,
“config.gpio11_mode”: 17,
“config.gpio12_mode”: 17,
“config.gpio8_pwm_mapping.endpoint”: null,
“config.gpio8_pwm_mapping.min”: 0.0,
“config.gpio8_pwm_mapping.max”: 0.0,
“config.gpio1_analog_mapping.endpoint”: null,
“config.gpio1_analog_mapping.min”: 0.0,
“config.gpio1_analog_mapping.max”: 0.0,
“config.gpio11_analog_mapping.endpoint”: null,
“config.gpio11_analog_mapping.min”: 0.0,
“config.gpio11_analog_mapping.max”: 0.0,
“config.brake_resistor0.enable”: true,
“config.brake_resistor0.resistance”: 2.0,
“config.brake_resistor0.enable_dc_bus_voltage_feedback”: false,
“config.brake_resistor0.dc_bus_voltage_feedback_ramp_start”: 51.0,
“config.brake_resistor0.dc_bus_voltage_feedback_ramp_end”: 53.0,
“config.inverter0.current_soft_max”: 80.0,
“config.inverter0.current_hard_max”: 96.0,
“config.inverter0.temp_limit_lower”: 83.95999908447266,
“config.inverter0.temp_limit_upper”: 103.11000061035156,
“config.inverter0.mod_magn_max”: 0.6781481504440308,
“config.inverter0.shunt_conductance”: 1999.9998779296875,
“config.inverter0.drv_config”: 9029553697166464,
“axis0.config.startup_max_wait_for_ready”: 3.0,
“axis0.config.startup_motor_calibration”: false,
“axis0.config.startup_encoder_index_search”: false,
“axis0.config.startup_encoder_offset_calibration”: false,
“axis0.config.startup_closed_loop_control”: false,
“axis0.config.startup_homing”: false,
“axis0.config.init_torque”: 0.0,
“axis0.config.init_vel”: 0.0,
“axis0.config.init_pos”: NaN,
“axis0.config.enable_step_dir”: false,
“axis0.config.step_dir_always_on”: false,
“axis0.config.calib_range”: 0.019999999552965164,
“axis0.config.calib_scan_distance”: 8.0,
“axis0.config.calib_scan_vel”: 2.0,
“axis0.config.index_search_at_target_vel_only”: false,
“axis0.config.watchdog_timeout”: 0.0,
“axis0.config.enable_watchdog”: false,
“axis0.config.step_gpio_pin”: 8,
“axis0.config.dir_gpio_pin”: 5,
“axis0.config.error_gpio_pin”: 6,
“axis0.config.enable_error_gpio”: false,
“axis0.config.calibration_lockin.current”: 10.0,
“axis0.config.calibration_lockin.ramp_time”: 0.4000000059604645,
“axis0.config.calibration_lockin.ramp_distance”: 0.5,
“axis0.config.calibration_lockin.accel”: 3.183098793029785,
“axis0.config.calibration_lockin.vel”: 6.36619758605957,
“axis0.config.sensorless_ramp.initial_pos”: 0.0,
“axis0.config.sensorless_ramp.current”: 10.0,
“axis0.config.sensorless_ramp.ramp_time”: 0.4000000059604645,
“axis0.config.sensorless_ramp.ramp_distance”: 0.5,
“axis0.config.sensorless_ramp.accel”: 31.83098793029785,
“axis0.config.sensorless_ramp.vel”: 63.6619758605957,
“axis0.config.sensorless_ramp.finish_distance”: 15.915493965148926,
“axis0.config.sensorless_ramp.finish_on_vel”: true,
“axis0.config.sensorless_ramp.finish_on_distance”: false,
“axis0.config.general_lockin.initial_pos”: 0.0,
“axis0.config.general_lockin.current”: 10.0,
“axis0.config.general_lockin.ramp_time”: 0.4000000059604645,
“axis0.config.general_lockin.ramp_distance”: 3.1415927410125732,
“axis0.config.general_lockin.accel”: 20.0,
“axis0.config.general_lockin.vel”: 40.0,
“axis0.config.general_lockin.finish_distance”: 100.0,
“axis0.config.general_lockin.finish_on_vel”: false,
“axis0.config.general_lockin.finish_on_distance”: false,
“axis0.config.can.node_id”: 63,
“axis0.config.can.version_msg_rate_ms”: 0,
“axis0.config.can.heartbeat_msg_rate_ms”: 100,
“axis0.config.can.encoder_msg_rate_ms”: 10,
“axis0.config.can.iq_msg_rate_ms”: 0,
“axis0.config.can.error_msg_rate_ms”: 0,
“axis0.config.can.temperature_msg_rate_ms”: 0,
“axis0.config.can.bus_voltage_msg_rate_ms”: 0,
“axis0.config.can.torques_msg_rate_ms”: 0,
“axis0.config.can.powers_msg_rate_ms”: 0,
“axis0.config.can.input_vel_scale”: 1000,
“axis0.config.can.input_torque_scale”: 1000,
“axis0.config.load_encoder”: 13,
“axis0.config.commutation_encoder”: 13,
“axis0.config.encoder_bandwidth”: 1000.0,
“axis0.config.commutation_encoder_bandwidth”: NaN,
“axis0.config.I_bus_hard_min”: -Infinity,
“axis0.config.I_bus_hard_max”: Infinity,
“axis0.config.I_bus_soft_min”: -Infinity,
“axis0.config.I_bus_soft_max”: Infinity,
“axis0.config.P_bus_soft_min”: -Infinity,
“axis0.config.P_bus_soft_max”: Infinity,
“axis0.config.torque_soft_min”: -Infinity,
“axis0.config.torque_soft_max”: Infinity,
“axis0.config.motor.motor_type”: 0,
“axis0.config.motor.pole_pairs”: 20,
“axis0.config.motor.phase_resistance”: 0.03200148791074753,
“axis0.config.motor.phase_inductance”: 1.4335515515995212e-05,
“axis0.config.motor.phase_resistance_valid”: true,
“axis0.config.motor.phase_inductance_valid”: true,
“axis0.config.motor.torque_constant”: 0.08269999921321869,
“axis0.config.motor.direction”: -1.0,
“axis0.config.motor.current_control_bandwidth”: 1000.0,
“axis0.config.motor.wL_FF_enable”: false,
“axis0.config.motor.bEMF_FF_enable”: false,
“axis0.config.motor.ff_pm_flux_linkage”: 0.0,
“axis0.config.motor.ff_pm_flux_linkage_valid”: false,
“axis0.config.motor.motor_model_l_d”: 0.0,
“axis0.config.motor.motor_model_l_q”: 0.0,
“axis0.config.motor.motor_model_l_dq_valid”: false,
“axis0.config.motor.calibration_current”: 10.0,
“axis0.config.motor.resistance_calib_max_voltage”: 2.0,
“axis0.config.motor.current_soft_max”: 50.0,
“axis0.config.motor.current_hard_max”: 70.0,
“axis0.config.motor.current_slew_rate_limit”: 10000.0,
“axis0.config.motor.fw_enable”: false,
“axis0.config.motor.fw_mod_setpoint”: 0.6103333234786987,
“axis0.config.motor.fw_fb_bandwidth”: 500.0,
“axis0.config.motor.acim_gain_min_flux”: 10.0,
“axis0.config.motor.acim_autoflux_enable”: false,
“axis0.config.motor.acim_autoflux_min_Id”: 10.0,
“axis0.config.motor.acim_autoflux_attack_gain”: 10.0,
“axis0.config.motor.acim_autoflux_decay_gain”: 1.0,
“axis0.config.motor.acim_nominal_slip_vel”: 2.3399999141693115,
“axis0.config.motor.sensorless_observer_gain”: 1000.0,
“axis0.config.motor.sensorless_pll_bandwidth”: 1000.0,
“axis0.config.motor.sensorless_pm_flux_linkage”: 0.0,
“axis0.config.motor.sensorless_pm_flux_linkage_valid”: false,
“axis0.config.motor.power_torque_report_filter_bandwidth”: 8000.0,
“axis0.config.anticogging.enabled”: false,
“axis0.config.anticogging.max_torque”: 0.15000000596046448,
“axis0.config.anticogging.calib_start_vel”: 1.0,
“axis0.config.anticogging.calib_end_vel”: 0.15000000596046448,
“axis0.config.anticogging.calib_coarse_tuning_duration”: 60.0,
“axis0.config.anticogging.calib_fine_tuning_duration”: 120.0,
“axis0.config.anticogging.calib_fine_dist_scale”: 1.0,
“axis0.config.anticogging.calib_coarse_integrator_gain”: 10.0,
“axis0.config.anticogging.calib_bidirectional”: true,
“axis0.motor.motor_thermistor.config.gpio_pin”: 4,
“axis0.motor.motor_thermistor.config.r_ref”: 10000.0,
“axis0.motor.motor_thermistor.config.t_ref”: 25.0,
“axis0.motor.motor_thermistor.config.beta”: 3435.0,
“axis0.motor.motor_thermistor.config.temp_limit_lower”: 110.0,
“axis0.motor.motor_thermistor.config.temp_limit_upper”: 130.0,
“axis0.motor.motor_thermistor.config.enabled”: true,
“axis0.controller.config.enable_vel_limit”: true,
“axis0.controller.config.enable_torque_mode_vel_limit”: true,
“axis0.controller.config.enable_gain_scheduling”: false,
“axis0.controller.config.gain_scheduling_width”: 0.0010000000474974513,
“axis0.controller.config.enable_overspeed_error”: true,
“axis0.controller.config.control_mode”: 2,
“axis0.controller.config.input_mode”: 2,
“axis0.controller.config.pos_gain”: 20.0,
“axis0.controller.config.vel_gain”: 0.1666666716337204,
“axis0.controller.config.vel_integrator_gain”: 0.3333333432674408,
“axis0.controller.config.vel_integrator_limit”: Infinity,
“axis0.controller.config.vel_limit”: 10.0,
“axis0.controller.config.vel_limit_tolerance”: 1.2000000476837158,
“axis0.controller.config.vel_ramp_rate”: 10.0,
“axis0.controller.config.torque_ramp_rate”: 0.009999999776482582,
“axis0.controller.config.circular_setpoints”: false,
“axis0.controller.config.circular_setpoint_range”: 1.0,
“axis0.controller.config.absolute_setpoints”: false,
“axis0.controller.config.use_commutation_vel”: false,
“axis0.controller.config.use_load_encoder_for_commutation_vel”: false,
“axis0.controller.config.commutation_vel_scale”: 1.0,
“axis0.controller.config.steps_per_circular_range”: 1024,
“axis0.controller.config.homing_speed”: 0.25,
“axis0.controller.config.inertia”: 0.0,
“axis0.controller.config.input_filter_bandwidth”: 20.0,
“axis0.controller.config.spinout_mechanical_power_bandwidth”: 20.0,
“axis0.controller.config.spinout_electrical_power_bandwidth”: 20.0,
“axis0.controller.config.spinout_mechanical_power_threshold”: -10.0,
“axis0.controller.config.spinout_electrical_power_threshold”: 10.0,
“axis0.trap_traj.config.vel_limit”: 2.0,
“axis0.trap_traj.config.accel_limit”: 10.0,
“axis0.trap_traj.config.decel_limit”: 0.5,
“axis0.min_endstop.config.gpio_num”: 0,
“axis0.min_endstop.config.enabled”: false,
“axis0.min_endstop.config.offset”: 0.0,
“axis0.min_endstop.config.is_active_high”: false,
“axis0.min_endstop.config.debounce_ms”: 50,
“axis0.max_endstop.config.gpio_num”: 0,
“axis0.max_endstop.config.enabled”: false,
“axis0.max_endstop.config.offset”: 0.0,
“axis0.max_endstop.config.is_active_high”: false,
“axis0.max_endstop.config.debounce_ms”: 50,
“axis0.enable_pin.config.gpio_num”: 7,
“axis0.enable_pin.config.enabled”: false,
“axis0.enable_pin.config.offset”: 0.0,
“axis0.enable_pin.config.is_active_high”: false,
“axis0.enable_pin.config.debounce_ms”: 50,
“axis0.mechanical_brake.config.gpio_num”: 0,
“axis0.mechanical_brake.config.is_active_low”: true,
“axis0.pos_vel_mapper.config.circular”: false,
“axis0.pos_vel_mapper.config.circular_output_range”: 1.0,
“axis0.pos_vel_mapper.config.scale”: 1.0,
“axis0.pos_vel_mapper.config.offset_valid”: false,
“axis0.pos_vel_mapper.config.offset”: 0.0,
“axis0.pos_vel_mapper.config.approx_init_pos_valid”: false,
“axis0.pos_vel_mapper.config.approx_init_pos”: 0.0,
“axis0.pos_vel_mapper.config.index_offset_valid”: false,
“axis0.pos_vel_mapper.config.index_offset”: 0.0,
“axis0.pos_vel_mapper.config.use_index_gpio”: false,
“axis0.pos_vel_mapper.config.passive_index_search”: false,
“axis0.pos_vel_mapper.config.index_gpio”: 10,
“axis0.pos_vel_mapper.config.use_endstop”: false,
“axis0.commutation_mapper.config.circular”: true,
“axis0.commutation_mapper.config.circular_output_range”: 1.0,
“axis0.commutation_mapper.config.scale”: -20.0,
“axis0.commutation_mapper.config.offset_valid”: true,
“axis0.commutation_mapper.config.offset”: -5.460582733154297,
“axis0.commutation_mapper.config.approx_init_pos_valid”: false,
“axis0.commutation_mapper.config.approx_init_pos”: 0.0,
“axis0.commutation_mapper.config.index_offset_valid”: false,
“axis0.commutation_mapper.config.index_offset”: 0.0,
“axis0.commutation_mapper.config.use_index_gpio”: false,
“axis0.commutation_mapper.config.passive_index_search”: false,
“axis0.commutation_mapper.config.index_gpio”: 10,
“axis0.commutation_mapper.config.use_endstop”: false,
“axis0.interpolator.config.dynamic”: true,
“rs485_encoder_group0.config.mode”: 0,
“rs485_encoder_group1.config.mode”: 0,
“inc_encoder0.config.enabled”: false,
“inc_encoder0.config.cpr”: 8192,
“hall_encoder0.config.enabled”: false,
“hall_encoder0.config.hall_polarity”: 0,
“hall_encoder0.config.hall_polarity_calibrated”: false,
“hall_encoder0.config.ignore_illegal_hall_state”: false,
“hall_encoder0.config.edges_calibrated”: false,
“hall_encoder0.config.edge0”: NaN,
“hall_encoder0.config.edge1”: NaN,
“hall_encoder0.config.edge2”: NaN,
“hall_encoder0.config.edge3”: NaN,
“hall_encoder0.config.edge4”: NaN,
“hall_encoder0.config.edge5”: NaN,
“spi_encoder0.config.ncs_gpio”: 12,
“spi_encoder0.config.mode”: 0,
“spi_encoder0.config.delay”: 0.0,
“spi_encoder0.config.max_error_rate”: 0.004999999888241291,
“spi_encoder0.config.baudrate”: 1687500,
“spi_encoder0.config.biss_c_bits”: 18,
“spi_encoder1.config.ncs_gpio”: 12,
“spi_encoder1.config.mode”: 0,
“spi_encoder1.config.delay”: 0.0,
“spi_encoder1.config.max_error_rate”: 0.004999999888241291,
“spi_encoder1.config.baudrate”: 1687500,
“spi_encoder1.config.biss_c_bits”: 18
}

S1 derates allowable current based on bus voltage, see here: ODrive S1 Datasheet — ODrive Documentation 0.6.11 documentation

Drop your overvoltage trip threshold to around 50-51V and you should get your full 40A :slight_smile:

If you want to push even more current, grab a lower voltage power supply (24-36V or so) and you can push 80A transients/peaks (just make sure you have some sort of thermal management / heatspreader plate on the S1).

1 Like

This did it! I did a torque test of 82NM on the actuator at around 40A! I’ll try to push it to see where it fails, but so far it’s doing everything I needed. Time to buy more :slight_smile:

The idea of getting a lower voltage power supply is really interesting to me! I’ve vaguely heard of people dropping the bus voltage to apply more torque, but have you heard of anyone doing this with odrives dynamically? For example a 6DOF arm that moves quickly under no load at 48V, but drops to a 36V or 24V high load mode when lifting a heavy object? Seems interesting, could maybe even be done with a power supply controlled over CAN or some sort of switching between supplies?

Thanks!

This did it! I did a torque test of 82NM on the actuator at around 40A! I’ll try to push it to see where it fails, but so far it’s doing everything I needed.

Great to hear!!

I’ve vaguely heard of people dropping the bus voltage to apply more torque

Hmm, this may be something else. The S1’s voltage-current limitations are just due to how we designed the inverter, and isn’t going to be an issue in future ODrive models (nor is it a limitation on the ODrive Pro). Here, dropping the bus voltage just allows for more overall power (48V * 40A limit = ~1.9kW, 36V * 80A limit = ~2.8kW), which you’d have to change the gear ratio to take advantage of. If you were to use an ODrive Pro instead (which can maintain full output current up to nameplate voltage), maximizing power would require using a power supply of said maximum nameplate voltage.