ERROR_CURRENT_UNSTABLE in closed loop position control

Hi guys ,
I started playing around with my ODrive (v3.6 - 56v, Firmware v0.4.12) recently, and I am getting this ERROR_CURRENT_UNSTABLE when i try to move the motor to a pos_setpoint in AXIS_STATE_CLOSED_LOOP_CONTROL. This happens for smaller positions changes as well(eg-400cpr). When a pos_setpoint command is giving motor starts to rotate at a very high speed and stops. and the error dump give the above error. My setup is as follows - 8308 Motor, AS5047D encoder(ABI interface) and a 6S lipo battery.
My motor,encoder and controller settings are attached below along with the error dump.
I have tried increasing motor current_lim, which caused ERROR_CURRENT_SENSE_SATURATION at some point. And there seem to be no issue with the motor inductance as well because i used to play with this motor few months back without any problems.

Can anyone help me with this problem?

I guess the motor draws a huge amount of current due to high acceleration. Is there a way to limit the acceleration in order to limit the current?

Thanks in advance!!!

Error Dump -
In [14]: dump_errors(odrv0,True)
axis0
axis: Error(s):
ERROR_MOTOR_FAILED
motor: Error(s):
ERROR_CURRENT_UNSTABLE
encoder: no error
controller: no error
axis1
axis: no error
motor: no error
encoder: no error
controller: no error

Axis Config -
In [18]: odrv0.axis0.config
Out[18]:
startup_motor_calibration = False (bool)
startup_encoder_index_search = True (bool)
startup_encoder_offset_calibration = False (bool)
startup_closed_loop_control = False (bool)
startup_sensorless_control = False (bool)
enable_step_dir = False (bool)
counts_per_step = 2.0 (float)
watchdog_timeout = 0.0 (float)
step_gpio_pin = 1 (int)
dir_gpio_pin = 2 (int)
calibration_lockin:
current = 10.0 (float)
ramp_time = 0.4000000059604645 (float)
ramp_distance = 3.1415927410125732 (float)
accel = 20.0 (float)
vel = 40.0 (float)
sensorless_ramp:
current = 10.0 (float)
ramp_time = 0.4000000059604645 (float)
ramp_distance = 3.1415927410125732 (float)
accel = 200.0 (float)
vel = 400.0 (float)
finish_distance = 100.0 (float)
finish_on_vel = True (bool)
finish_on_distance = False (bool)
finish_on_enc_idx = False (bool)
general_lockin:
current = 10.0 (float)
ramp_time = 0.4000000059604645 (float)
ramp_distance = 3.1415927410125732 (float)
accel = 20.0 (float)
vel = 40.0 (float)
finish_distance = 100.0 (float)
finish_on_vel = False (bool)
finish_on_distance = False (bool)
finish_on_enc_idx = False (bool)

Controller Settings -
In [15]: odrv0.axis0.controller
Out[15]:
error = 0x0000 (int)
pos_setpoint = 0.0 (float)
vel_setpoint = 0.0 (float)
vel_integrator_current = 0.0 (float)
current_setpoint = 0.0 (float)
vel_ramp_target = 0.0 (float)
vel_ramp_enable = False (bool)
config:
control_mode = 3 (int)
pos_gain = 30.0 (float)
vel_gain = 0.001792160328477621 (float)
vel_integrator_gain = 0.008960802108049393 (float)
vel_limit = 200000.0 (float)
vel_limit_tolerance = 1.2000000476837158 (float)
vel_ramp_rate = 5000.0 (float)
setpoints_in_cpr = True (bool)
set_pos_setpoint(pos_setpoint: float, vel_feed_forward: float
set_vel_setpoint(vel_setpoint: float, current_feed_forward: f
set_current_setpoint(current_setpoint: float)
move_to_pos(pos_setpoint: float)
move_incremental(displacement: float, from_goal_point: bool)
start_anticogging_calibration()

Encoder Settings -
In [16]: odrv0.axis0.encoder
Out[16]:
error = 0x0000 (int)
is_ready = True (bool)
index_found = True (bool)
shadow_count = 42799 (int)
count_in_cpr = 1839 (int)
interpolation = 0.5 (float)
phase = 0.038392066955566406 (float)
pos_estimate = 42799.75 (float)
pos_cpr = 1839.7542724609375 (float)
hall_state = 2 (int)
vel_estimate = 0.0 (float)
calib_scan_response = 801.0 (float)
config:
mode = 0 (int)
use_index = True (bool)
find_idx_on_lockin_only = False (bool)
pre_calibrated = False (bool)
zero_count_on_find_idx = True (bool)
cpr = 2048 (int)
offset = -404 (int)
offset_float = -0.14370310306549072 (float)
enable_phase_interpolation = True (bool)
bandwidth = 1000.0 (float)
calib_range = 0.05000000074505806 (float)
calib_scan_distance = 50.26548385620117 (float)
calib_scan_omega = 12.566370964050293 (float)
idx_search_unidirectional = False (bool)
ignore_illegal_hall_state = False (bool)
set_linear_count(count: int)

Motor Settings -
In [17]: odrv0.axis0.motor
Out[17]:
error = 0x0000 (int)
armed_state = 0 (int)
is_calibrated = True (bool)
current_meas_phB = 0.11798018217086792 (float)
current_meas_phC = -0.06924906373023987 (float)
DC_calib_phB = -0.8404545187950134 (float)
DC_calib_phC = -0.131206214427948 (float)
phase_current_rev_gain = 0.02500000037252903 (float)
thermal_current_lim = 168.91783142089844 (float)
get_inverter_temp()
current_control:
p_gain = 0.025867974385619164 (float)
i_gain = 55.49924087524414 (float)
v_current_control_integral_d = 0.0 (float)
v_current_control_integral_q = 0.0 (float)
Ibus = 26.162839889526367 (float)
final_v_alpha = -2.0920491218566895 (float)
final_v_beta = 8.765569686889648 (float)
Iq_setpoint = 40.0 (float)
Iq_measured = 50.51939392089844 (float)
Id_measured = -0.7509180307388306 (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 = 2482 (int)
TIMING_LOG_ADC_CB_DC = 13130 (int)
TIMING_LOG_MEAS_R = 6670 (int)
TIMING_LOG_MEAS_L = 6678 (int)
TIMING_LOG_ENC_CALIB = 6982 (int)
TIMING_LOG_IDX_SEARCH = 0 (int)
TIMING_LOG_FOC_VOLTAGE = 6930 (int)
TIMING_LOG_FOC_CURRENT = 7670 (int)
config:
pre_calibrated = True (bool)
pole_pairs = 21 (int)
calibration_current = 15.0 (float)
resistance_calib_max_voltage = 2.0 (float)
phase_inductance = 2.586797381809447e-05 (float)
phase_resistance = 0.05549923703074455 (float)
direction = -1 (int)
motor_type = 0 (int)
current_lim = 40.0 (float)
current_lim_tolerance = 1.25 (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)

Id (Blue) and Iq (Orange) plot during the process

Yes, simply reduce motor.config.current_lim - this will limit the acceleration by limiting the current (and therefore the torque).

I’m surprised you’re getting ERROR_CURRENT_SENSE_SATURATION with a 40A limit in a 60A range though! That suggests a marginally unstable current controller. Have you tried adjusting current_control_bandwidth? Try setting that to 500 and see if it helps

Thanks for the reply. I ll try your suggestions this weekend. Btw I got ERROR_CURRENT_SENSE_SATURATION when i increased the current limit to 75A in 90A range.

Yes, that’s hardly surprising. It’s a bad idea in general to use any amplifier / measurement device too close to its saturation limit. :slight_smile:
There is often some overshoot in a current controller, especially when dealing with high-inductance, low-resistance motors, and fast setpoint changes.

I tried your suggestions. But it made no difference, current unstable error does not go away. I removed both ERROR_CURRENT_UNSTABLE and ERROR_CURRENT_SENSE_SATURATION errors from the firmware and flashed the ODrive. Now getting violent vibrations when i try to move to a position. Trying to tune the controller now. Could you please clarify what sort of controller this is? It seems to be a cascaded feed forward controller.
Isnt it better to implement a cascaded PID controller instead?. I am not sure. i am new to control theory.

Please note that in order to change the current range, you must save config and then reboot. The range is applied only at start-up.

It’s a cascaded PI controller with feedforward, afaik.
Having a D term in a current controller would make no sense, because you’d just be amplifying the noise.

1 Like

yeah i did that when i wanted to change parameters

So, to clarify: You are still getting ERROR_CURRENT_SENSE_SATURATION even with a 40A limit in a 90A range?

Well, no surprise there, your current controller is unstable. If i had to guess, it’s an encoder problem. Does it work with setpoints_in_cpr = False?

i didnt check 40A limit in 90A range. i only tested 40A in 60A range. i ll check this again.

I get violent vibrations in both cases.
Btw my encoders might be the problem. I had to change encoder calib_range to 0.05 to avoid encoder ERROR_CPR_OUT_OF_RANGE error.

By default the 5047D in ABI mode uses 4000 for cpr (not 4096), so maybe?

ABI interface of 5047D has a max resolution of 2048ppr according to the datasheet. But your point showed me an error made by me. Even though it has a max res of 2048ppr , default value is 2000. I changed that and checked. Then i reverted back to original calib_range and i didnt get the error. But still get violent vibrations even when i try to enter closed loop control. It cant even handle the initial pos_setpoint. I ll try tuning more

Does the motor behave as expected in torque mode?
Hold the rotor by hand (if safe to do) and slowly increase the torque demand until you can feel it applying a constant torque. Then release it slightly and let it spin a few revs and make sure it applies the same constant torque throughout.

When I get a situation like this, I go back to very basics. Set ALL gains to zero, calibrate motor & verify the current controller works as intended. Move on to velocity and increase gain until it just starts to vibrate if I tweak it at zero setpoint, then cut it by a factor of 2 (rule of thumb). Then start on position - similar deal, except when I first get high frequency vibrations, I halve the velocity gain and then try to keep the product of pos_gain * vel_gain the same thereafter.
Last thing I set is the velocity integrator, if needed at all.

2 Likes

Good, this is quite important - it means that your expected encoder count and actual count are within 2% of each other during calibration, so I have a lot more confidence that it’s working correctly. I agree with @towen, it’s probably a tuning issue at this point

1 Like

I ll try this out. Thanks a lot !