Too little torque at max current under torque control mode

I am using an ODrive Pro with a CubeMars AKE60-8 motor under pass-through control using an Arduino script to prescribe a sinusoidal torque profile oscillating between 0 and a variable max torque (T_max) with period 1s < T < 10s.

Motor Specs: https://www.cubemars.com/product/ake60-8-kv80-quasi-direct-drive-actuator.html
Peak Current: 12 A
Peak Torque: 1.56 Nm (excluding the built-in 8:1 planetary gearbox)
Kv = 80 rpm/V
Kt = 0.125 Nm/A

I am using the ODrive GUI to display the measured and setpoint currents (Iq_measured and Iq_setpoint), as well as estimated and measured torques, while simultaneously measuring the output torque using an external Futek torque sensor. All testing is performed in a quasi-static state, with very low motor speeds.

While the theoretical torque (T = Kt * I) calculated from the measured current maps very well to the torque measured using the Futek sensor up to about 7-8A of motor current, requesting torques any higher than that exhibits a very steep and nonlinear increase in current draw. The motor output torque is maxing out around 1 Nm (65% of peak rated torque). Any torque requests to the motor higher than produce an Iq_measured current peaking just below the rated peak current of 12A, which is also the max current setting used for the ODrive configuration.

I have made sure to disable the enable_torque_mode_vel_limit and it makes no difference in the measured current reading or resulting torque measurements.

Huh, that’s very very interesting! What’s your bus voltage, and what encoder are you using? Could you take a quick plot of torque_setpoint vs Iq_measured and the Futek torque reading?

We are using the ODrive Pro onboard encoder with a bus voltage of 31V.

Here are the torque_setpoint and Iq_measured plots. For the torque_setpoint values, I’d only expect ~9.5A peak?

As well as the Futek reading. A couple notes here. We have a 33.8:1 transmission ratio (including the integrated CubeMars planetary drive), so I’ve corrected the Futek readings to account for what should be coming from the motor. In this particular trial, the measured torques were even lower, so I’m not ruling out some degree of mechanical error or loss here. I will work on getting a more representative plot up.

Interesting – can you share the whole ODrive config? Using ODrivetool, you can run odrivetool backup-config config.json then paste the resulting config.json here; that would help a lot!

{
  "can.config.baud_rate": 0,
  "can.config.data_baud_rate": 10000000,
  "can.config.tx_brs": 0,
  "can.config.protocol": 0,
  "config.enable_uart_a": true,
  "config.uart_a_baudrate": 19200,
  "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": 48.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": 4,
  "config.gpio13_mode": 4,
  "config.gpio14_mode": 17,
  "config.gpio15_mode": 17,
  "config.gpio16_mode": 17,
  "config.gpio17_mode": 17,
  "config.gpio18_mode": 17,
  "config.gpio8_pwm_mapping.endpoint": null,
  "config.gpio8_pwm_mapping.min": 0.0,
  "config.gpio8_pwm_mapping.max": 0.0,
  "config.gpio9_pwm_mapping.endpoint": null,
  "config.gpio9_pwm_mapping.min": 0.0,
  "config.gpio9_pwm_mapping.max": 0.0,
  "config.gpio15_analog_mapping.endpoint": null,
  "config.gpio15_analog_mapping.min": 0.0,
  "config.gpio15_analog_mapping.max": 0.0,
  "config.gpio16_analog_mapping.endpoint": null,
  "config.gpio16_analog_mapping.min": 0.0,
  "config.gpio16_analog_mapping.max": 0.0,
  "config.inverter0.current_soft_max": 100.0,
  "config.inverter0.current_hard_max": 150.0,
  "config.inverter0.temp_limit_lower": 83.95999908447266,
  "config.inverter0.temp_limit_upper": 103.11000061035156,
  "config.inverter0.mod_magn_max": 0.8651593923568726,
  "config.inverter0.shunt_conductance": 1999.9998779296875,
  "config.inverter0.drv_config": 9029553772700800,
  "config.odrv_fan.upper": 80.0,
  "config.odrv_fan.lower": 70.0,
  "config.odrv_fan.enabled": false,
  "config.motor_fan.upper": 80.0,
  "config.motor_fan.lower": 70.0,
  "config.motor_fan.enabled": false,
  "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": 9,
  "axis0.config.error_gpio_pin": 10,
  "axis0.config.enable_error_gpio": false,
  "axis0.config.calibration_lockin.current": 3.5999999046325684,
  "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": 14,
  "axis0.config.motor.phase_resistance": 0.29999279975891113,
  "axis0.config.motor.phase_inductance": 0.00023341772612184286,
  "axis0.config.motor.phase_resistance_valid": true,
  "axis0.config.motor.phase_inductance_valid": true,
  "axis0.config.motor.torque_constant": 0.10337500274181366,
  "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.dI_dt_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": 2.4000000953674316,
  "axis0.config.motor.resistance_calib_max_voltage": 8.0,
  "axis0.config.motor.current_soft_max": 12.0,
  "axis0.config.motor.current_hard_max": 25.600000381469727,
  "axis0.config.motor.current_slew_rate_limit": 10000.0,
  "axis0.config.motor.fw_enable": false,
  "axis0.config.motor.fw_mod_setpoint": 0.7786434292793274,
  "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.config.off_axis_k": 1.0,
  "axis0.config.off_axis_k_commutation": 1.0,
  "axis0.config.harmonic_compensation.calib_vel": 8.0,
  "axis0.config.harmonic_compensation.calib_settling_delay": 2.0,
  "axis0.config.harmonic_compensation.calib_turns": 8,
  "axis0.config.harmonic_compensation.cosx_coef": 0.0,
  "axis0.config.harmonic_compensation.sinx_coef": 0.0,
  "axis0.config.harmonic_compensation.cos2x_coef": 0.0,
  "axis0.config.harmonic_compensation.sin2x_coef": 0.0,
  "axis0.config.harmonic_compensation_commutation.calib_vel": 8.0,
  "axis0.config.harmonic_compensation_commutation.calib_settling_delay": 2.0,
  "axis0.config.harmonic_compensation_commutation.calib_turns": 8,
  "axis0.config.harmonic_compensation_commutation.cosx_coef": 0.0,
  "axis0.config.harmonic_compensation_commutation.sinx_coef": 0.0,
  "axis0.config.harmonic_compensation_commutation.cos2x_coef": 0.0,
  "axis0.config.harmonic_compensation_commutation.sin2x_coef": 0.0,
  "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": 1,
  "axis0.controller.config.input_mode": 3,
  "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": 32.0,
  "axis0.controller.config.vel_limit_tolerance": 1.125,
  "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": 0.5,
  "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": 11,
  "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": 7,
  "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": 14.0,
  "axis0.commutation_mapper.config.offset_valid": true,
  "axis0.commutation_mapper.config.offset": -2.1674230098724365,
  "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": 7,
  "axis0.commutation_mapper.config.use_endstop": false,
  "axis0.interpolator.config.dynamic": true,
  "axis0.motor.motor_thermistor.config.gpio_pin": 3,
  "axis0.motor.motor_thermistor.config.mode": 1,
  "axis0.motor.motor_thermistor.config.r_ref": 0.0,
  "axis0.motor.motor_thermistor.config.t_ref": 25.0,
  "axis0.motor.motor_thermistor.config.beta": 0.0,
  "axis0.motor.motor_thermistor.config.a": 3.9082999229431152,
  "axis0.motor.motor_thermistor.config.b": -0.0005774999735876918,
  "axis0.motor.motor_thermistor.config.temp_limit_lower": 100.0,
  "axis0.motor.motor_thermistor.config.temp_limit_upper": 120.0,
  "axis0.motor.motor_thermistor.config.enabled": false,
  "rs485_encoder_group0.config.mode": 0,
  "rs485_encoder_group1.config.mode": 0,
  "inc_encoder0.config.enabled": false,
  "inc_encoder0.config.filter": 0,
  "inc_encoder0.config.cpr": 8192,
  "inc_encoder1.config.enabled": false,
  "inc_encoder1.config.filter": 0,
  "inc_encoder1.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,
  "hall_encoder1.config.enabled": false,
  "hall_encoder1.config.hall_polarity": 0,
  "hall_encoder1.config.hall_polarity_calibrated": false,
  "hall_encoder1.config.ignore_illegal_hall_state": false,
  "hall_encoder1.config.edges_calibrated": false,
  "hall_encoder1.config.edge0": 0.0,
  "hall_encoder1.config.edge1": 0.0,
  "hall_encoder1.config.edge2": 0.0,
  "hall_encoder1.config.edge3": 0.0,
  "hall_encoder1.config.edge4": 0.0,
  "hall_encoder1.config.edge5": 0.0,
  "spi_encoder0.config.ncs_gpio": 17,
  "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_encoder0.config.biss_c_multiturn_bits": 0,
  "spi_encoder1.config.ncs_gpio": 17,
  "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,
  "spi_encoder1.config.biss_c_multiturn_bits": 0
}

Looks like your torque constant is set to 0.103 – this is correct based on the motor KV; I would trust this over the Cubemars specified torque constant (I don’t know what units they’re using, but there’s something wrong there). This corresponds with the 1.2Nm torque setpoint being correlated with ~11A phase current (1.2/.103 = 11.65).

Since you’re using the ODrive’s onboard encoder, you may want to consider running harmonic compensation. I’d also do a quick check on the magnetic field strength: Designing for Magnetic Encoders — ODrive Documentation 0.6.11 documentation

Not exactly sure what’s up with the Futek readings – that oscillation and nonlinearity looks like it’s maybe gear stiction though?

Okay, thanks This is super helpful. At the risk of revealing my own ignorance, where does the 0.103 value come from? Directly converting KV = 80 rpm/V to Kt = 1/KV produces kt = 0.119 Nm/A. I see .103 is approx sqrt(3)/2 * .119… so is it just representing Kt in terms of a line RMS current rather than a phase-peak current?

I’ll check out the other features you suggest too. Thanks!

The Futek readings here are likely due to the resistance method we are applying to resist the torque in that particular test (lever arm with Dyneema rope), as well as maybe some gear stiction. I’ll be looking into that too.

Appreciate the help!

No worries! The 1/KV vs 8.27/KV is an artifact of doing a bunch of compressed unit transforms – it’s actually Kt = 3/2 * 1/sqrt(3) * 60/2pi * 1/KV == 8.27/KV. More info here: Where does the formula for calculating torque come from? - #2 by madcowswe

The Futek readings here are likely due to the resistance method we are applying to resist the torque in that particular test (lever arm with Dyneema rope), as well as maybe some gear stiction. I’ll be looking into that too.

Definitely makes sense!