Encoder seems to work, but ERROR_CPR_OUT_OF_RANGE on offset calibration

Hi, I tried to set up my Odrive 3.5 48V with my motor, but I can’t get the internal ABI encoder’s offset calibration to work (encoder.is_ready=False)

Motor calibration and index search works (motor.is_calibrated=true, use_index=True, index_found=True), but I always get encoder.error = ERROR_CPR_OUT_OF_RANGE. I understand this means that given the motor and encoder configurations, encoder counts don’t match with electrical motor rotation.

Counts per rotation are 2500 ppr *4 => cpr = 10000. I verified this from the motor’s datasheet and from manually turning the motor and watching the pulse count reported with odrivetool.
I verified the number of motor pole pairs to be 8 via this method.
I also unmounted the attached 1:25 worm gearbox, but it didn’t help.
The encoder is built-in, so I cannot re-align if that was really the cause (which I doubt since manual turning shows correct counts).
I tried calib_range = 0.05 and calibration_current = 30 as suggested in Encoder error 0x0002 questions

I even built and flashed new firmware from the Connect Hall and incremental encoder topic where hall sensor is used as index pulse. But is has the same ERROR_CPR_OUT_OF_RANGE problem.

Is there anything else I can set or try to make this work?

odrivetool log
In [6]: odrv0.axis0.motor                                                                                
Out[6]:                                                                                                  
error = 0x0000 (int)                                                                                     
armed_state = 0 (int)                                                                                    
is_calibrated = True (bool)                                                                              
current_meas_phB = -0.015051960945129395 (float)                                                         
current_meas_phC = 0.015044689178466797 (float)                                                          
DC_calib_phB = -1.6362192630767822 (float)                                                               
DC_calib_phC = -2.1500930786132812 (float)                                                               
phase_current_rev_gain = 0.02500000037252903 (float)                                                     
thermal_current_lim = 45.87586212158203 (float)                                                          
get_inverter_temp()                                                                                      
current_control:                                                                                         
  p_gain = 0.025488227605819702 (float)                                                                  
  i_gain = 33.564842224121094 (float)                                                                    
  v_current_control_integral_d = -0.7992188930511475 (float)                                             
  v_current_control_integral_q = 0.44919270277023315 (float)                                             
  Ibus = 0.1462455838918686 (float)                                                                      
  final_v_alpha = 0.8796867728233337 (float)                                                             
  final_v_beta = 0.2399977147579193 (float)                                                              
  Iq_setpoint = 10.0 (float)                                                                             
  Iq_measured = 10.19466781616211 (float)                                                                
  Id_measured = -0.15742921829223633 (float)                                                             
  I_measured_report_filter_k = 1.0 (float)                                                               
  max_allowed_current = 60.75 (float)                                                                    
  overcurrent_trip_level = 67.5 (float)                                                                  
gate_driver:                                                                                             
  drv_fault = 0 (int)                                                                                    
timing_log:                                                                                              
  TIMING_LOG_GENERAL = 0 (int)                                                                           
  TIMING_LOG_ADC_CB_I = 2770 (int)                                                                       
  TIMING_LOG_ADC_CB_DC = 12854 (int)                                                                     
  TIMING_LOG_MEAS_R = 7594 (int)                                                                         
  TIMING_LOG_MEAS_L = 7618 (int)
  TIMING_LOG_ENC_CALIB = 7914 (int)
  TIMING_LOG_IDX_SEARCH = 0 (int)
  TIMING_LOG_FOC_VOLTAGE = 7858 (int)
  TIMING_LOG_FOC_CURRENT = 8186 (int)
config:
  pre_calibrated = False (bool)
  pole_pairs = 8 (int)
  calibration_current = 10.0 (float)
  resistance_calib_max_voltage = 4.0 (float)
  phase_inductance = 2.5488227038295008e-05 (float)
  phase_resistance = 0.033564843237400055 (float)
  direction = -1 (int)
  motor_type = 0 (int)
  current_lim = 10.0 (float)
  inverter_temp_limit_lower = 100.0 (float)
  inverter_temp_limit_upper = 120.0 (float)
  requested_current_range = 25.0 (float)
  current_control_bandwidth = 1000.0 (float)

In [12]: odrv0.axis0.error = 0; odrv0.axis0.encoder.error = 0; odrv0.axis0.motor.error = 0      [30/1236]
                                                                                                         
In [13]: odrv0.axis0.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH                                   
                                                                                                         
In [14]: odrv0.axis0.encoder                                                                             
Out[14]:                                                                                                 
error = 0x0000 (int)                                                                                     
is_ready = False (bool)                                                                                  
index_found = True (bool)                                                                                
shadow_count = 314 (int)                                                                                 
count_in_cpr = 314 (int)                                                                                 
interpolation = 0.5 (float)                                                                              
phase = 1.5808494091033936 (float)                                                                       
pos_estimate = 314.9997253417969 (float)                                                                 
pos_cpr = 314.7500305175781 (float)                                                                      
hall_state = 5 (int)                                                                                     
vel_estimate = 0.0 (float)                                                                               
config:                                                                                                  
  mode = 0 (int)                                                                                         
  use_index = True (bool)                                                                                
  find_idx_on_lockin_only = True (bool)                                                                  
  pre_calibrated = False (bool)                                                                          
  zero_count_on_find_idx = True (bool)                                                                   
  cpr = 10000 (int)                                                                                      
  offset = 0 (int)                                                                                       
  offset_float = 0.0 (float)                                                                             
  enable_phase_interpolation = True (bool)                                                               
  bandwidth = 1000.0 (float)                                                                             
  calib_range = 0.019999999552965164 (float)                                                             
  idx_search_unidirectional = True (bool)                                                                
  ignore_illegal_hall_state = False (bool)                                                               

In [15]: odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION

In [16]: odrv0.axis0.encoder
Out[16]: 
error = 0x0002 (int)
is_ready = False (bool)
index_found = True (bool)
shadow_count = -98847 (int)
count_in_cpr = 1153 (int)
interpolation = 0.5 (float)
phase = -0.4850621223449707 (float)
pos_estimate = -98846.25 (float)
pos_cpr = 1153.75 (float)
hall_state = 4 (int)
vel_estimate = 0.0 (float)
config:
  mode = 0 (int)
  use_index = True (bool)
  find_idx_on_lockin_only = True (bool)
  pre_calibrated = False (bool)
  zero_count_on_find_idx = True (bool)
  cpr = 10000 (int)
  offset = 0 (int)
  offset_float = 0.0 (float)
  enable_phase_interpolation = True (bool)
  bandwidth = 1000.0 (float)
  calib_range = 0.019999999552965164 (float)
  idx_search_unidirectional = True (bool)
  ignore_illegal_hall_state = False (bool)
1 Like

Can you link to the motor datasheet? So you used a bench power supply over two wires with third disconnected and felt 8 detent positions per revolution? Do you think your counting could have been off by one? 7 pole pairs is a common value.

Sure, I uploaded the datasheet here: https://docdro.id/E2XRT7G - We have the KY130AS0430-15 motor.

So you used a bench power supply over two wires with third disconnected and felt 8 detent positions per revolution?

Yes I used a Lab power supply. I tried with 800mA, but at 2A it was really distinct. I did try to set 7 pole pairs (and 16), but it had the same result.

What I observed is that during the offset search the motor doesn’t rotate continuously, but jerks back a little in a cyclic way (maybe after each electrical revolution?). Would that pose a problem? Any way to fix that?

Yes that’s a problem. It likely means that phase B or phase C is disconnected. Check your phase wiring to the ODrive. If that doesn’t reveal anything, try disconnecting the motor and measuring the 3 phase pairs with a multimeter: they should have the same value.

The thing is that index search and sensorless operation work well without any jerking. The motor runs well with a Vesc controller, both via hall and ABI.

For comparison, here is the motor configuration XML that the Vesc uses. I don’t think that all of those fields are used, e.g. it says 14 motor poles. (But as I said earlier, I tried just setting 7 pole pairs and got the same error)

Vesc motor config
<?xml version="1.0" encoding="UTF-8"?>
<MCConfiguration>
    <sl_bemf_coupling_k>900</sl_bemf_coupling_k>
    <motor_type>2</motor_type>
    <foc_hall_table_7>255</foc_hall_table_7>
    <m_duty_ramp_step>0.02</m_duty_ramp_step>
    <s_pid_min_erpm>60</s_pid_min_erpm>
    <cc_startup_boost_duty>0.01</cc_startup_boost_duty>
    <l_max_erpm_fbrake>300</l_max_erpm_fbrake>
    <hall_table_6>2</hall_table_6>
    <l_temp_fet_end>100</l_temp_fet_end>
    <p_pid_ki>0</p_pid_ki>
    <foc_motor_flux_linkage>0.016087</foc_motor_flux_linkage>
    <foc_encoder_offset>149.2</foc_encoder_offset>
    <cc_ramp_step_max>0.04</cc_ramp_step_max>
    <motor_quality_description>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Some comments about the motor quality. Images can be added as well.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</motor_quality_description>
    <pwm_mode>1</pwm_mode>
    <l_watt_min>-15000</l_watt_min>
    <foc_encoder_inverted>0</foc_encoder_inverted>
    <sensor_mode>2</sensor_mode>
    <s_pid_kd_filter>0.2</s_pid_kd_filter>
    <l_max_erpm>100000</l_max_erpm>
    <foc_hall_table_6>165</foc_hall_table_6>
    <s_pid_allow_braking>1</s_pid_allow_braking>
    <foc_temp_comp_base_temp>25</foc_temp_comp_base_temp>
    <foc_hall_table_4>133</foc_hall_table_4>
    <foc_sample_high_current>0</foc_sample_high_current>
    <foc_current_ki>10.33</foc_current_ki>
    <l_min_erpm>-100000</l_min_erpm>
    <foc_motor_r>0.0103</foc_motor_r>
    <hall_table_0>6</hall_table_0>
    <foc_hall_table_0>255</foc_hall_table_0>
    <hall_table_1>5</hall_table_1>
    <motor_model>Not Specified</motor_model>
    <l_battery_cut_start>10</l_battery_cut_start>
    <foc_motor_l>5.82e-6</foc_motor_l>
    <p_pid_kd_filter>0.2</p_pid_kd_filter>
    <motor_quality_construction>0</motor_quality_construction>
    <l_in_current_max>8</l_in_current_max>
    <comm_mode>0</comm_mode>
    <m_drv8301_oc_mode>0</m_drv8301_oc_mode>
    <s_pid_kd>0.0001</s_pid_kd>
    <foc_encoder_ratio>8</foc_encoder_ratio>
    <l_current_min>-62.5</l_current_min>
    <cc_min_current>0</cc_min_current>
    <foc_sat_comp>0</foc_sat_comp>
    <motor_quality_bearings>0</motor_quality_bearings>
    <m_bldc_f_sw_max>40000</m_bldc_f_sw_max>
    <cc_gain>0.0046</cc_gain>
    <motor_loss_torque>0.03</motor_loss_torque>
    <foc_hall_table_5>100</foc_hall_table_5>
    <m_out_aux_mode>0</m_out_aux_mode>
    <hall_table_2>3</hall_table_2>
    <m_encoder_counts>10000</m_encoder_counts>
    <m_ntc_motor_beta>3380</m_ntc_motor_beta>
    <l_max_vin>57</l_max_vin>
    <motor_brand>Unnamed</motor_brand>
    <l_erpm_start>0.8</l_erpm_start>
    <foc_temp_comp>0</foc_temp_comp>
    <motor_sensor_type>0</motor_sensor_type>
    <motor_quality_magnets>0</motor_quality_magnets>
    <foc_sensor_mode>2</foc_sensor_mode>
    <sl_min_erpm_cycle_int_limit>1100</sl_min_erpm_cycle_int_limit>
    <l_min_duty>0.003</l_min_duty>
    <hall_sl_erpm>2000</hall_sl_erpm>
    <l_temp_accel_dec>0.15</l_temp_accel_dec>
    <p_pid_kd>0.0004</p_pid_kd>
    <m_invert_direction>0</m_invert_direction>
    <foc_hall_table_1>67</foc_hall_table_1>
    <hall_table_3>4</hall_table_3>
    <l_slow_abs_current>1</l_slow_abs_current>
    <foc_observer_gain>3.86e+6</foc_observer_gain>
    <foc_current_filter_const>0.1</foc_current_filter_const>
    <l_battery_cut_end>8</l_battery_cut_end>
    <foc_sample_v0_v7>0</foc_sample_v0_v7>
    <foc_hall_table_2>199</foc_hall_table_2>
    <l_current_max>62.5</l_current_max>
    <foc_f_sw>20000</foc_f_sw>
    <hall_table_5>6</hall_table_5>
    <m_fault_stop_time_ms>500</m_fault_stop_time_ms>
    <p_pid_kp>0.03</p_pid_kp>
    <foc_sl_d_current_duty>0</foc_sl_d_current_duty>
    <foc_sl_erpm>2500</foc_sl_erpm>
    <m_current_backoff_gain>0.5</m_current_backoff_gain>
    <sl_cycle_int_limit>192.99</sl_cycle_int_limit>
    <motor_weight>0</motor_weight>
    <sl_phase_advance_at_br>0.8</sl_phase_advance_at_br>
    <foc_observer_gain_slow>0.3</foc_observer_gain_slow>
    <m_sensor_port_mode>0</m_sensor_port_mode>
    <motor_description>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A motor description can be edited here.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</motor_description>
    <l_temp_motor_end>100</l_temp_motor_end>
    <m_bldc_f_sw_min>3000</m_bldc_f_sw_min>
    <foc_openloop_rpm>400</foc_openloop_rpm>
    <foc_dt_us>0.08</foc_dt_us>
    <foc_pll_kp>2000</foc_pll_kp>
    <foc_duty_dowmramp_kp>10</foc_duty_dowmramp_kp>
    <foc_sl_d_current_factor>0</foc_sl_d_current_factor>
    <s_pid_ki>0.004</s_pid_ki>
    <sl_cycle_int_rpm_br>80000</sl_cycle_int_rpm_br>
    <m_dc_f_sw>35000</m_dc_f_sw>
    <l_watt_max>15000</l_watt_max>
    <motor_poles>14</motor_poles>
    <foc_sl_openloop_time>0.1</foc_sl_openloop_time>
    <foc_hall_table_3>33</foc_hall_table_3>
    <p_pid_ang_div>1</p_pid_ang_div>
    <l_max_erpm_fbrake_cc>1500</l_max_erpm_fbrake_cc>
    <foc_sl_openloop_hyst>0.1</foc_sl_openloop_hyst>
    <l_min_vin>8</l_min_vin>
    <s_pid_kp>0.004</s_pid_kp>
    <l_in_current_min>0</l_in_current_min>
    <l_temp_fet_start>85</l_temp_fet_start>
    <foc_pll_ki>40000</foc_pll_ki>
    <sl_max_fullbreak_current_dir_change>10</sl_max_fullbreak_current_dir_change>
    <l_abs_current_max>130</l_abs_current_max>
    <l_temp_motor_start>85</l_temp_motor_start>
    <hall_table_7>6</hall_table_7>
    <l_max_duty>0.95</l_max_duty>
    <hall_table_4>1</hall_table_4>
    <foc_current_kp>0.0058</foc_current_kp>
    <sl_min_erpm>150</sl_min_erpm>
    <m_drv8301_oc_adj>16</m_drv8301_oc_adj>
    <foc_duty_dowmramp_ki>200</foc_duty_dowmramp_ki>
</MCConfiguration>

You can see the Vesc measured at least similar data:

<foc_motor_r>0.0103</foc_motor_r>
<foc_motor_l>5.82e-6</foc_motor_l>
<m_encoder_counts>10000</m_encoder_counts>

The VESC measured 10 mohm, and ODrive measured 34 mohm. Someone is wrong, or they are not measuring the same things.

Hm I don’t see why there should be jerking on encoder calibration but not index search. Can you upload a video showing index search, and showing the encoder offset calibration? Also let us know what firmware version you are using.

Because lalten was not here, we tried to continue investigate the issue. After looking into it, we got smoother offset calibration movement by simply increasing the allowed current.

But we now found out, that on the one hand, the index search fails for us (no error code, but every time different location). Furthermore, the offset calibration sill says an error (makes sense).

Strange is, that when we manually turn the motor eaxtly 1 round, we get a nice plot of exactly 10k steps. But when we do the plot while the ecoder offset or index search is done, we get way more steps. This is really weired.

In the linked gist you can see our script and the plots.

Important to know is, that the index search and offset search both took about 0.9 real turns (but not 5 like it seems on the plot).

We use the current branch of https://github.com/madcowswe/ODrive/tree/preroll, although we actually do not use the index signal of a hall sensor, but the index signal of the encoder built in. We also validated the index signal of the encoder with an oszi, which is normally low but high on a reproducible position.

We also investaged this line of code:

For our count_in_cpr:

using lib at /tmp/ODrive/tools/odrive/__init__.py
Finding an odrive...
Calibrating motor {}...
Checking motor {} direction...
checking for index
count in cpr 9978
shadow count -22
checking for encoder offset
Axis Error at : 0x100
OFFSET CALIB FAILED

After index search, shadow is e.g. -22, but count_in_cpr is 9989. This line mentioned above might lead to a wrong index offset cound, because the acutal encoder count is never reset, only after the first pulse, but then we add in our case 9989 to many pulses…

9978, which is shown in your paste, is the equivalent of -22 if you have a 10000 cpr encoder. In a single revolution, the motor goes from 0 to 9999. Then in the next count, it wraps back around to 0.

@Wetmelon. Thanks for your response. We already know that, thats also totally fine. The above was only to hilight a potential bug in the firmware. There, it makes a difference if you set the shadow_count to 9989 or almost zero on the next encoder update (becaue then the diff of current count - last shadow count is used to increment the count_in_cpr)

But our main question is:

  • When turning manually and plotting encoder ticks, we get for 360 degrees physically turning 10.000 ticks
  • Why does the index search after turning about 360degrees physically plot that it got about 50.000 ticks when it only should be 10.000?
  • Why does the index search successfully finish, but every time on a different position? (We validated that the Index signal is working correctly and only sending a signal at the excat same physical position the same time, therefore it seems the software/board does register somethign else)
  • The two above points would explain, why the encoder offset always fails, because we every time count a seemingly random number of encoder ticks (encoder is A/B signal and was verified to work when motor was turned by hand)

Andy ideas? Especially why during index search the encoder count is way off?

We gave up on integrated ABI encoders for now and switched to hall sensors (both are integrated into the motor). Initially this also didn’t work (ERROR_CPR_OUT_OF_RANGE), but increasing encoder.config.calib_range to 0.04 did the trick.

Now that both motors are spinning we have a new problem, but I will open a new thread for this.

Curious. I still don’t understand why you’re having all sorts of issues with the ABI encoders. You may just have a lot of electronic noise being picked up when the drives are active, especially if the encoders have very weak drive outputs. It looks like they actually use differential outputs for the encoder - you may want to try my PCBs here designed to convert from differential to single-ended output https://github.com/Wetmelon/Differential-Encoder-Adapter (admittedly untested)

Where are you located? If you’re near Stuttgart, Germany I may be able to drop by to help.

Thanks for your offer Wetmelon. We’re based in Munich, but right now we’re in Spain with our ODrive :slight_smile:

The signals are indeed differential, but for now I just connected all the inverted ones to GND.

Aha! So depending on how the differential output works, it may not be driving the ODrive inputs correctly and as soon as you turn on the motor drivers they start triggering. I strongly recommend using the differential adapters in this case.

Why would you connect the unused differential encoder OUTPUTS to ground?

Depending on the specs of your encoder, these are likely being driven high and low as the encoder moves. Any time these signals are high, this is shorting the output driver. Not at all surprised you have issues.
At the very least leave them floating, better yet is to use a differential adapter as Wetmelon suggested.

1 Like

I expected the output to be floating if I left the inverted ones unconnected. It did work with the inverted signals connected to ground, but it also works when they’re left floating. So I left them floating in the end :wink:

Hello
I can observe the same behaviour in my setup while offset search. The motor does not run smoothly and returns the error: cpr out of range. Index search worked so far. I use an incremental encoder with 8192 cpr and t-motor u13 85kv. I checked the wiring of my phases of my motor and my encoder. Have you solved the issue yet?

I could increase the calibration range of the encoder but I would need to increase it to 0.9. That seems to me pretty high. The offset is not failing anymore but still the motor does not run smoothly in the offset search.