Changing States

Hello Fellow ODrivers,

I am running into an issue where I cannot set my ODrive S1 to to Position Control mode from the Dashboard or in scripts. It keeps faulting to NOT_CALIBRATED even though I ran through all of the steps of the Apply & Calibrate page successfully.

I can run in Velocity and Torque Control modes but not in Position Control mode.

When I try to run from a python script I get the following errors when I dump_errors():

Exception: Axis failed to enter <AxisState.CLOSED_LOOP_CONTROL: 8>: axis0
  active_errors: no error
  disarm_reason: no error
  procedure_result: ProcedureResult.NOT_CALIBRATED
  last_drv_fault: none
internal issues: none

The axis I am trying to drive has a load encoder (absolute multi-turn RS485) and a commutator encoder (incremental - no index).

I had seen some other posts about similar problems but not exactly what I am seeing. If you have experienced a similar issue or can point me to a thread that resolves this issue I’d appreciate that.

Best,
Emi

Hi! My guess is that either you have your position reference frame set up weird, or you’re just not calibrating the encoder before entering closed loop. Would you be open to posting your python code, as well as your ODrive configuration (odrivetool backup-config config.json)?

Hey Solomon,

I used the GUI to apply my configs and calibrate (steps 1-6 of the Apply & Calibrate page in the GUI).

What is weird is that I ran through the same process with another motor and I was able to run that in closed loop mode.

Here is my (very rough please don’t judge :slight_smile: ) python code:

import math
import time

import odrive
from odrive.enums import AxisState, ControlMode, InputMode
from odrive.utils import dump_errors, request_state

# Find a connected ODrive (this will block until you connect one)
print("waiting for ODrive...")
odrv1 = odrive.find_sync(serial_number="XXXXXXXXXXXXXX")
print(f"found ODrive {odrv1._dev.serial_number}")


# clear errors on the odrive(s)
odrv1.clear_errors()

# Quick Calibration if needed
request_state(odrv1.axis0, AxisState.FULL_CALIBRATION_SEQUENCE)
request_state(odrv1.axis0, AxisState.ENCODER_OFFSET_CALIBRATION)



# Configure Current Limits
odrv1.axis0.config.I_bus_hard_min = -5
odrv1.axis0.config.I_bus_soft_min = -5
odrv1.axis0.config.I_bus_hard_max = 5
odrv1.axis0.config.I_bus_soft_max = 5
odrv1.axis0.controller.config.vel_limit = 100



# Enter closed loop control
odrv1.axis0.controller.config.input_mode = InputMode.PASSTHROUGH
odrv1.axis0.controller.config.control_mode = ControlMode.POSITION_CONTROL
request_state(odrv1.axis0, AxisState.CLOSED_LOOP_CONTROL)



# Step the Drives
step_delta = 1.0
timer_counter = 0
timer_delta = 5
max_loop_time = 20
try:
    p1 = odrv1.axis0.controller.input_pos
    t0 = time.monotonic()
    setpoint1 = p1

    while odrv1.axis0.current_state == AxisState.CLOSED_LOOP_CONTROL and timer_counter < max_loop_time:
        setpoint1 = setpoint1 + step_delta

        print(f"Set Point 1: {setpoint1}, Position 1: {odrv1.axis0.pos_estimate}\n\n")


        # # Position
        odrv1.axis0.controller.input_pos = setpoint1

        time.sleep(timer_delta)
        timer_counter = timer_counter + timer_delta

finally:
    request_state(odrv1.axis0, AxisState.IDLE)

# Show errors
dump_errors(odrv1)

I X’d out the serial number for this post.

Here is the config uploaded from the GUI:

odrv = odrv0
odrv.config.dc_bus_overvoltage_trip_level = 28.5
odrv.config.dc_bus_undervoltage_trip_level = 18
odrv.config.dc_max_positive_current = 5
odrv.config.dc_max_negative_current = -5
odrv.config.brake_resistor0.enable = False
odrv.axis0.config.motor.motor_type = MotorType.PMSM_CURRENT_CONTROL
odrv.axis0.config.motor.pole_pairs = 7
odrv.axis0.config.motor.torque_constant = 0.030629629629629628
odrv.axis0.config.motor.current_soft_max = 65
odrv.axis0.config.motor.current_hard_max = 85
odrv.axis0.config.motor.calibration_current = 10
odrv.axis0.config.motor.resistance_calib_max_voltage = 2
odrv.axis0.config.calibration_lockin.current = 10
odrv.axis0.motor.motor_thermistor.config.enabled = False
odrv.axis0.controller.config.control_mode = ControlMode.VELOCITY_CONTROL
odrv.axis0.controller.config.input_mode = InputMode.VEL_RAMP
odrv.axis0.controller.config.vel_limit = 2
odrv.axis0.controller.config.vel_limit_tolerance = 5
odrv.axis0.config.torque_soft_min = -math.inf
odrv.axis0.config.torque_soft_max = math.inf
odrv.axis0.trap_traj.config.accel_limit = 2
odrv.axis0.controller.config.vel_ramp_rate = 2
odrv.can.config.protocol = Protocol.NONE
odrv.axis0.config.enable_watchdog = False
odrv.axis0.config.load_encoder = EncoderId.RS485_ENCODER0
odrv.rs485_encoder_group0.config.mode = Rs485EncoderMode.AMT21_POLLING
odrv.inc_encoder0.config.enabled = True
odrv.axis0.config.commutation_encoder = EncoderId.INC_ENCODER0
odrv.inc_encoder0.config.cpr = 8192
odrv.axis0.commutation_mapper.config.use_index_gpio = False
odrv.config.enable_uart_a = False

Let me know if this is not the correct config you were asking for.

Best,
Emi

Thanks for all the info! In the future, no need to blank out the serial number – I get the paranoia, but the only thing that lets me do is know the production batch the ODrive came from :slight_smile: (and nobody else will know anything from it).

Some notes:

  1. Not related, but I notice you’re using Rs485EncoderMode.AMT21_POLLING for the load encoder. This implies a stock AMT21, which has a pretty significant firmware bug that really hurts performance at higher speeds (above a few hundred RPM). I’d recommend switching to the AMT21-B-V-OD from our shop, which has custom firmware that gives it much higher performance.
  2. Seems like you’re using the D5065 270KV from our shop, but your calibration_lockin.current is really low – you should bump this to around the motor’s max continuous current (about 40-45A) for more accurate calibration
  3. FULL_CALIBRATION_SEQUENCE does both MOTOR_CALIBRATION and ENCODER_OFFSET_CALIBRATION, so you don’t need to call both FULL_CALIBRATION_SEQUENCE and then ENCODER_OFFSET_CALIBRATION. In fact, you only need to call ENCODER_OFFSET_CALIBRATION, since the motor calibration (resistance+inductance) gets saved to the ODrive.
  4. This is the issue you’re having – request_state only switches the ODrive to that state, it doesn’t actually wait for the state to be done. As such, you’re requesting encoder offset calibration, but then immediately requesting closed loop control, so the encoder offset cal isn’t finished – hence the NOT_CALIBRATED error. My recommendation would be to use run_state() instead – this will set the new state and then wait for it to be complete.

So instead, you should have:

run_state(odrv1.axis0, AxisState.ENCODER_OFFSET_CALIBRATION)

Hey Solomon,

  1. We needed to use a larger diameter encoder so that is why we ended up with the AMT24. We may have been able to get away with using an adapter though. This is good to know for the next one. Luckily we are never running the absolute encoders over 50-100 RPM

  2. I used the default value given for the motor and I thought that would be all good. I will set this higher - probably try 20A and move up if that doesn’t work since I don’t want to fry the ODrive if it is too high. What is interesting is that I have calibrated another D5065 the exact same way I did for this one and it all worked exactly as I had intended. Weird that this one is acting up but this is still worth a shot.

  3. Excellent. I was doing the full calibration each time just to make sure I wasn’t missing anything. I was primarily using the GUI for this step.

  4. Ah that is good to know for my scripts. What is odd is that this happens even when I am using the GUI for calibration.

I will try changing the lock-in spin current for calibration and update my script and see where that gets me.

As always, thank you for the help!

When I ran the script with the suggested changes I got these errors after ENCODER_OFFSET_CALIBRATION was complete:

Just to reiterate, I am able to go into velocity and torque control modes, just not position control.

Likely something related to the position reference frames then. Could you share your config? odrivetool backup-config config.json, then you can paste the config.json here.

Also

I used the default value given for the motor and I thought that would be all good. I will set this higher - probably try 20A and move up if that doesn’t work since I don’t want to fry the ODrive if it is too high. What is interesting is that I have calibrated another D5065 the exact same way I did for this one and it all worked exactly as I had intended. Weird that this one is acting up but this is still worth a shot.

Could also be just due to different load on the motor. No worries on frying the ODrive – it’ll self-limit current if it gets too hot! Even without a heatspreader plate or any sort of thermal management (just bare PCB), 20-30A is just fine.