Encoder calibration fails for T-Motor U10 Plus

I want to use the T-Motor U10 Plus (170KV, 36N40P) with ODrive. Unfortunately, encoder calibration consistently fails. I am currently using the new ODrive GUI and configure the motor to 170kv and 20 pole pairs. Calibration of the motor suceeds, but encoder calibration keeps failing (ATM102-V). The encoder is known to work properly, if I attach it to a D6374 everything works perfectly.

The U10 Plus is mounted to a harmonic drive, so it experiences some load, but that is barely noticeable. Even if I allow the housing of the motor to spin freely without any mechanical resistance, the calibration does not look as smooth as it does for many other motors, e.g. the D6374.

Any tips what to try?

Hi Adrian,

Since your motor has 20 pole pairs, can you set odrvX.axisN.encoder.config.calib_scan_distance to something like 42 * Pi and try to calibrate again? By default, it is 16 * Pi, which is enough for just over a full revolution of movement with a 7 pole pair motor like the D6374.

Hi Patrick,

thanks a million, that was the hint I was looking for!

I also had to increase calib_range for the calbration to succeed. To get started, I just set it to 5 times the default to make it work. I did not look into details yet, but probably there is some systematic functional dependency that would help to circumvent experimentation. That btw. makes me wonder why the firmware currently does not derive calib_scan_distance from the number of pole pairs on its own?

No problem! Glad to hear that it works!

We could do that, but in general the various firmware classes (like Motor, Encoder) are designed to not depend heavily on each other. This allows for more flexibility. It’s true that all of them are interrelated by virtue of controlling a motor, but conceptually they exist independently. However, changing parameters like calib_scan_distance based on motor parameters is a good idea, and is a feature that will show up in the GUI.

The development idea now is that the ODrive parameters are an API that the user need not interact with directly. Instead, pesky real-world details like calib_scan_range depending on pole pairs, etc, will be handled by higher level software like the GUI to be easier to use. There’s a lot of work to do on that front, but I figured this point warranted explanation.

1 Like

I’m new to this forum. My implementation requires me to also change the parameters for calib_range and encoder.config.calib_scan_distance. Your response to Adrian implies that these parameters may be possible to change without modifying the encoder.hpp file and recompiling/flashing. If true, then how is that done? Is it only via the GUI which I havn’t been able to get to work, yet. I’ve tried odrv0.axis0.encoder.config.calib_scan_distance = 42 * 3.14 but that throws an error.

Is is definitely possible to change those parameters with odrivetool, the GUI is not necessary for it. What errors do you get?

Thanks for your reply. It was a typo error on my part (had an _ in place of a .)
I have the motor passing full calibration.

But, I could use some advice on configuration for my implementation.

I’m using an eBike brushless hub motor (~1 KW maybe 14KV) with a 4000 cpr encoder and a 10:1 multiplying timing belt gear for 40000 cpr. I would like to control a telescope with this which means that it needs very low RPM. I would like to track the earth’s rotation which means the motor will move very slowly at 0.004 deg/sec or 0.00007 rad/sec. Max slew speed would be around 20 deg/sec.

I’ve experimented with different control and trajectory modes and can’t seem to find anything that works. Typically, I get a very fast movement and overspeed errors. There is some load on the motor from friction components and it seems to not generate enough early torque to overcome this.

Can you recommend a general configuration that would shortcut some of my experimentation? (FYI, my background is Electrical Engineering for 35 yrs)

You will likely need the anticogging algorithm for this. Check out this video Activating Anti-Cogging on the ODrive Robotics Controller (smooth brushless motor control) - YouTube

Unfortunately, I am not able to get the motor working enough to do the anti-cogging function.

I’m able to pass motor and encoder calibrations, but when I go to set a position, the motor will not move (or sometimes moves after a delay). When it doesn’t move, my 22V DC supply shows the current steadily increasing to around 4 A before shutting off the motor with a DC_BUS_UNDER_VOLTAGE error.

After a complete confuration erase here are the settings I use:

odrv0.axis0.encoder.config.cpr = 40000
odrv0.axis0.encoder.config.calib_scan_distance = 150.72
odrv0.axis0.encoder.config.calib_range = 0.050
odrv0.axis0.motor.config.pole_pairs = 24
odrv0.axis0.motor.config.calibration_current = 20
odrv0.axis0.motor.config.resistance_calib_max_voltage = 5.0
odrv0.axis0.motor.config.current_lim = 30
odrv0.config.brake_resistance = 1
odrv0.axis0.motor.config.torque_constant = .69

Here are the status’s:

odrv0.axis0.controller.input_pos = 0

In [41]: odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL

In [42]: odrv0.axis0.controller.input_pos = 1

In [43]: odrv0.axis0.controller.input_pos = 0

In [44]: dump_errors(odrv0,1)
axis0
axis: Error(s):
AXIS_ERROR_DC_BUS_UNDER_VOLTAGE
motor: no error
fet_thermistor: no error
motor_thermistor: no error
encoder: no error
controller: no error
axis1
axis: Error(s):
AXIS_ERROR_DC_BUS_UNDER_VOLTAGE
motor: no error
fet_thermistor: no error
motor_thermistor: no error
encoder: no error
controller: no error

In [45]: odrv0.axis0.controller
Out[45]:
error = 0x0000 (int)
input_pos = 0.0 (float)
input_vel = 0.0 (float)
input_torque = 0.0 (float)
pos_setpoint = 0.0 (float)
vel_setpoint = 0.0 (float)
torque_setpoint = 0.0 (float)
trajectory_done = True (bool)
vel_integrator_torque = -16.312145233154297 (float)
anticogging_valid = False (bool)
config:
gain_scheduling_width = 10.0 (float)
enable_vel_limit = True (bool)
enable_current_mode_vel_limit = True (bool)
enable_gain_scheduling = False (bool)
enable_overspeed_error = True (bool)
control_mode = 3 (int)
input_mode = 1 (int)
pos_gain = 20.0 (float)
vel_gain = 0.1666666716337204 (float)
vel_integrator_gain = 0.3333333432674408 (float)
vel_limit = 2.0 (float)
vel_limit_tolerance = 1.2000000476837158 (float)
vel_ramp_rate = 1.0 (float)
torque_ramp_rate = 0.009999999776482582 (float)
circular_setpoints = False (bool)
circular_setpoint_range = 1.0 (float)
homing_speed = 0.25 (float)
inertia = 0.0 (float)
axis_to_mirror = 255 (int)
mirror_ratio = 1.0 (float)
load_encoder_axis = 0 (int)
input_filter_bandwidth = 2.0 (float)
anticogging: …
move_incremental(displacement: float, from_input_pos: bool)
start_anticogging_calibration()

odrv0.axis0.motor
Out[47]:
error = 0x0000 (int)
armed_state = 0 (int)
is_calibrated = True (bool)
current_meas_phB = 0.21049678325653076 (float)
current_meas_phC = -0.26801440119743347 (float)
DC_calib_phB = -0.653647243976593 (float)
DC_calib_phC = -0.41695636510849 (float)
phase_current_rev_gain = 0.02500000037252903 (float)
effective_current_lim = 30.0 (float)
current_control:
p_gain = 0.1646914929151535 (float)
i_gain = 123.95084381103516 (float)
v_current_control_integral_d = 0.02746220864355564 (float)
v_current_control_integral_q = -3.0534331798553467 (float)
Ibus = 13.604094505310059 (float)
final_v_alpha = 2.9599316120147705 (float)
final_v_beta = -1.0756747722625732 (float)
Id_setpoint = 0.0 (float)
Iq_setpoint = -24.123762130737305 (float)
Iq_measured = -23.482318878173828 (float)
Id_measured = -0.08284255117177963 (float)
I_measured_report_filter_k = 1.0 (float)
max_allowed_current = 60.75 (float)
overcurrent_trip_level = 67.5 (float)
acim_rotor_flux = 0.0 (float)
async_phase_vel = 0.0 (float)
async_phase_offset = 0.0 (float)
gate_driver:
drv_fault = 0 (int)
timing_log:
general = 62788 (int)
adc_cb_i = 2718 (int)
adc_cb_dc = 13050 (int)
meas_r = 7614 (int)
meas_l = 7698 (int)
enc_calib = 8070 (int)
idx_search = 42640 (int)
foc_voltage = 8014 (int)
foc_current = 9178 (int)
spi_start = 29031 (int)
sample_now = 22132 (int)
spi_end = 53207 (int)
config:
pre_calibrated = False (bool)
pole_pairs = 24 (int)
calibration_current = 20.0 (float)
resistance_calib_max_voltage = 5.0 (float)
phase_inductance = 0.00016469148977193981 (float)
phase_resistance = 0.12395083904266357 (float)
torque_constant = 0.6899999976158142 (float)
direction = 1 (int)
motor_type = 0 (int)
current_lim = 30.0 (float)
current_lim_margin = 8.0 (float)
torque_lim = inf (float)
inverter_temp_limit_lower = 100.0 (float)
inverter_temp_limit_upper = 120.0 (float)
requested_current_range = 60.0 (float)
current_control_bandwidth = 1000.0 (float)
acim_slip_velocity = 14.706000328063965 (float)
acim_gain_min_flux = 10.0 (float)
acim_autoflux_min_Id = 10.0 (float)
acim_autoflux_enable = False (bool)
acim_autoflux_attack_gain = 10.0 (float)
acim_autoflux_decay_gain = 1.0 (float)

In [48]: odrv0.axis0.encoder
Out[48]:
error = 0x0000 (int)
is_ready = True (bool)
index_found = False (bool)
shadow_count = 66299 (int)
count_in_cpr = 26299 (int)
interpolation = 0.5 (float)
phase = 1.2095787525177002 (float)
pos_estimate = 1.6574937105178833 (float)
pos_estimate_counts = 66299.75 (float)
pos_cpr = 0.6574937105178833 (float)
pos_cpr_counts = 26299.75 (float)
pos_circular = 0.6575433611869812 (float)
hall_state = 6 (int)
vel_estimate = 0.0 (float)
vel_estimate_counts = 0.0 (float)
calib_scan_response = 40742.0 (float)
pos_abs = 0 (int)
spi_error_rate = 0.0 (float)
config:
mode = 0 (int)
use_index = False (bool)
find_idx_on_lockin_only = False (bool)
abs_spi_cs_gpio_pin = 1 (int)
zero_count_on_find_idx = True (bool)
cpr = 40000 (int)
offset = 85978 (int)
pre_calibrated = False (bool)
offset_float = 0.6505299806594849 (float)
enable_phase_interpolation = True (bool)
bandwidth = 1000.0 (float)
calib_range = 0.05000000074505806 (float)
calib_scan_distance = 150.72000122070312 (float)
calib_scan_omega = 12.566370964050293 (float)
idx_search_unidirectional = False (bool)
ignore_illegal_hall_state = False (bool)
sincos_gpio_pin_sin = 3 (int)
sincos_gpio_pin_cos = 4 (int)
set_linear_count(count: int)

I’ve tried many different settings and modes. The motor acts as if there is not enough torque to overcome the small load. When it does move, I get OVERSPEED errors.

Can you help with some ideas??

What encoder are you using? 40000 is surprising. Over speed means increase your vel limit tolerance, or disable the vel over speed error, just make sure that it’s not spinning out like crazy. Low bus voltage makes no sense though

I am curious as to why you have picked such a powerful motor with high torque ripple, direct-drive, to position a telescope… :stuck_out_tongue: This really is like using a steamroller to crack a nut.

eBike motors are really quite awful for positioning, because they are so coggy. (they use iron slots in the stator, which attract the permanent magnets on the rotor to a varying degree, causing a variable torque offset as a function of position)
I would probably have gone for a small motor with a large gear ratio via a timing belt, and two encoders, one for the load and one for the motor. And ideally, a slotless/ironless motor.