ODrive Setup Driving Me Insane

Alright, so I have been working on this legitimately for 4 days. I have tried two different controllers (both same type and FW) and two different motors of the same type (ODrive Robotics D6374 - 150kv) both with two different 8192 CPR encoders. Same results regardless. Which leads me to believe that I am the problem. No shame here.

(What is happening:) Sometimes when I do a FULL_CALIBRATION_SEQUENCE the motor will start slow and speed up and then just stop. So not the typical calibration with the switching of directions. Once I do that, I can pull errors. Trying a few times, after clearing errors, I get through the calibration and then into CLOSED_LOOP and when I enter the command the motor will move a fraction of a revolution before stopping, also pulling the same errors.

I have followed the 0.5.5 documentation repeatedly with no success. Running a 56V Version 3.6 on 0.5.5 and the utility version is 0.6.3. Running off from a 4S Lipo and no brake resistor. I am under the impression I am not setting something properly at this point. Below I have precisely what I have entered into the command prompt (with notes added after for clarification). I know its long, but I figure this is the best way for someone to point out what I am doing wrong. Please advise. Save my sanity. Thank you in advance.

Please connect your ODrive.
You can also type help() or quit().

Connected to ODrive 2051398A4D4D as odrv0
In [1]: odrv0.vbus_voltage
Out[1]: 14.7718505859375

In [2]: dump_errors(odrv0) //checking to ensure no errors persist on startup. Good to go.
system: no error
axis0
axis: no error
motor: no error
DRV fault: none
sensorless_estimator: no error
encoder: no error
controller: no error
axis1
axis: no error
motor: no error
DRV fault: none
sensorless_estimator: no error
encoder: no error
controller: no error

In [3]: odrv0.axis0.motor.config.current_lim = 10

In [4]: odrv0.axis0.controller.config.vel_limit = 2

In [5]: odrv0.axis0.motor.config.calibration_current = 5 //I have also tried value of 10, no difference

In [6]: odrv0.axis0.motor.config.pole_pairs = 7

In [7]: odrv0.axis0.motor.config.torque_constant = 8.27/150

In [8]: odrv0.axis0.motor.config.motor_type = MOTOR_TYPE_HIGH_CURRENT

In [9]: odrv0.axis0.encoder.config.cpr = 8192

// Steps 10 and 11 are me requesting odrv0.axis0.motor and odrv0.axis0.encoder feedback, nothing really to report

In [12]: odrv0.save_configuration()
←[93;1m20:32:20.691452300 [LEGACY_OBJ] protocol failed with 3 - propagating error to application←[0m
Oh no odrv0 disappeared

// This LEGACY_OBJ failing has me wondering if the config is ever truly saved. I do recall seeing a previous post about this and I believe it was indicated that it was fine and nothing to worry about. Am I wrong?

Reconnected to ODrive 2051398A4D4D as odrv0
In [13]: odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE

//Faulty calibration noted, so I pulled errors

In [14]: dump_errors(odrv0)
system: Error(s):
ODriveError.MISSING_ESTIMATE //Cannot find this error in the documentation
axis0
axis: Error(s):
AxisError.MOTOR_FAILED // Triple checked wiring. This is what prompted me to change motor/encoder. No change
motor: Error(s):
MotorError.SYSTEM_LEVEL
DRV fault: none
sensorless_estimator: no error
encoder: no error
controller: no error
axis1
axis: Error(s):
AxisError.MOTOR_FAILED
motor: Error(s):
MotorError.SYSTEM_LEVEL
DRV fault: none
sensorless_estimator: no error
encoder: no error
controller: no error

In [15]: odrv0.clear_errors()

Oh no odrv0 disappeared
Reconnected to ODrive 2051398A4D4D as odrv0
In [16]: odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE

//Calibration sequence looked good, performed as normal

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

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

//This is where it starts to move and then suddenly stops just a fraction of a rotation into the turn. Steps 19-21 are me attempting to send two more position commands without success and then setting AXIS_STATE_IDLE (not sure if its needed but I do it anyway). Pull errors, only to get same result as when a bad calibration sequence is noted.

In [22]: dump_errors(odrv0)
system: Error(s):
ODriveError.MISSING_ESTIMATE
axis0
axis: Error(s):
AxisError.MOTOR_FAILED
motor: Error(s):
MotorError.SYSTEM_LEVEL
DRV fault: none
sensorless_estimator: no error
encoder: no error
controller: no error
axis1
axis: Error(s):
AxisError.MOTOR_FAILED
motor: Error(s):
MotorError.SYSTEM_LEVEL
DRV fault: none
sensorless_estimator: no error
encoder: no error
controller: no error

The legacy fuckery isn’t an issue, I get it all the time and it’s not the cause of any problems

The only time I get system level fail is if something is if I do the anticog calibration then set it to position control and it yeets itself into place

Is the motor connected to anything? Works best if it’s just a mounted motor with nothing connected to the shaft except the encoder

Also make sure the encoder isn’t moving relative to the motor

My best advice is run odrv0.erase_configuration() to make sure there’s nothing else in the drive messing with it

I have erased the config multiple times to ensure that I am starting fresh and not skipping anything or any previously set values are a non-issue.

Also, the motor is not connected to anything other than it’s case (and encoder of course). Lastly, the encoder is not shifting during rotation either.

That’s a toughie then

Are you setting both axes to idle before saving the configuration?

I only have one motor hooked up. Should I still be setting both to idle regardless?

Prior to saving yes, otherwise it won’t save properly

Set your parameters > set both to idle > save > calibrate axis > set motor.pre_calibrated and encoder.pre_calibrated to true > set both to idle > save > whatever else you wanna do

1 Like

If you @ me tomorrow around noon I can send you my full auto calibration script, it’s built to run in Python but it has the correct parameters in order so if you follow that you should be in good standing

I’m running a 190kv 6354 with z index so make sure you adjust things accordingly

1 Like

I will give this a shot. I honestly didn’t think that I’d have to set it to idle if I never set it to closed loop control. Particularly on an axis that I’m not dealing with.

That would be amazing to take a look at that script! I’ll definitely hit you up for it!

I got stuck for a while getting it to save correctly

But basically the procedure is to set both pre_calibrated and any startups to false plus set all the other parameters you want, save, run detection, check for errors, set pre_calibrated and startups to true, then save again

@ZachTetra Hey, just wanted to follow up with you for the script you spoke of.

It’s not perfect but try to take a look through this and copy or modify as needed, if a command is ‘missing’ its because I am using the default still, in another thread I posted all the parameters with default values

‘’‘’‘’

VSTA001.py written by Zachary The’ on October 6th, 2022 for HORC Lab VST 2.0
Contact at +1 (302) 753-3570 or thezach@udel.edu

For odrive info visit Getting Started — ODrive Documentation 0.5.5 documentation
Using ODrive v3.6-56V

To install/update odrivetool run … in command window
pip install --upgrade odrive
Running ODrive control utility v0.6.3

To install missing modules run … or … in command window
conda install -c conda-forge
pip install

Ro resolve USB issues visit https://zadig.akeo.ie/
Run application, Options > List All Devices, ODrive 3.6 Native Interface (Interface 2), libusb-win32 (v.1.2.6.0), Install Driver

To update odrive firmware run … in command window, use Zadig utility to set STM32 BOOTLOADER to libusb-win32
odrivetool dfu
Running v0.5.5.0

odrv0 → ODrive 316D335C3030 (patient left)
odrv1 → ODrive 3359365B3539 (patient right)

axis0 → VSM
axis1 → Belt

‘’’

import os
import sys
import time
from cmath import pi
from logging import exception

import odrive

def save_odrive(odrv, odrv_ID):

print("Saving ODrive")

odrv.axis0.requested_state = 1
odrv.axis1.requested_state = 1
try:
    odrv.save_configuration()
    countdown(5)
except Exception:
    countdown(5)
print()

odrv = odrive.find_any(serial_number=odrv_ID)
odrv.axis0.requested_state = 1
odrv.axis1.requested_state = 1

return odrv

def countdown(t):

for i in range(t):

    # write = sys.stdout.write
    # write('\b \b')
    if (t - i) > 1:
        print(f"Please wait {t - i} seconds  \t", end="\r")
    else:
        print("Please wait 1 second  \t", end="\r")
    time.sleep(1)
print("                          ", end="\r")

def autoanticog(odrv, odrv_ID):

odrv.axis0.requested_state = 8
odrv.axis0.controller.config.anticogging.pre_calibrated = False  # {False}
odrv.axis0.controller.config.control_mode = 2  # velocity control {3}
odrv.axis0.controller.config.input_mode = 1  # passthrough {1}
odrv.axis0.controller.config.pos_gain = 10*20  # {20}
odrv.axis0.controller.config.vel_integrator_gain = 10*0.3333  # {0.3333}

odrv.axis0.controller.start_anticogging_calibration()

t = time.time()
i = odrv.axis0.encoder.pos_estimate_counts
while odrv.axis0.controller.config.anticogging.calib_anticogging:
    dt = time.time() - t
    rem = 1 - abs(odrv.axis0.encoder.pos_estimate_counts - i) / 8192
    est = dt / rem
    print(f"Expected run time of {est:.1f} seconds (run time of {dt:.1f} seconds, {100 * rem:.1f}% complete", end="\r")
print("                                           ", end="\r")

odrv.axis0.controller.config.anticogging.pre_calibrated = True  # {False}
odrv.axis1.controller.config.control_mode = 3  # position control {3}
odrv.axis0.controller.config.input_mode = 5  # trapazoidal {1}
odrv.axis0.controller.config.pos_gain = 20  # {20}
odrv.axis0.controller.config.vel_integrator_gain = 0.3333  # {0.3333}

# Save ODrive
odrv = save_odrive(odrv, odrv_ID)

return odrv

def autocal_VSM(odrv, odrv_ID):

# Continue/cancel autodetection
str_in = input(
    "Remove platform and center VSM prior to calibration\nPress ENTER to continue or X to cancel\n")

if str_in in ["x", "X"]:
    print()
    return

# look into anticogging
# look into autotuning

# Update parameters
odrv.axis0.controller.config.input_mode = 5  # trapazoidal {1}
odrv.axis0.controller.config.pos_gain = 20  # {20}
odrv.axis0.controller.config.vel_gain = 0.1667  # {0.1667}
odrv.axis0.controller.config.vel_integrator_gain = 0.3333  # {0.3333}
odrv.axis0.controller.config.vel_limit = 114  # {2}
odrv.axis0.encoder.config.pre_calibrated = False  # {False}
odrv.axis0.encoder.config.use_index = True  # {False}
odrv.axis0.motor.config.calibration_current = 20  # {10}
odrv.axis0.motor.config.current_lim = 65  # {10}
odrv.axis0.motor.config.current_lim_margin = 52  # {8}
odrv.axis0.motor.config.pre_calibrated = False  # {False}
odrv.axis0.motor.config.requested_current_range = 130  # {60}
odrv.axis0.motor.config.torque_constant = 8.27/190  # {0.04}
odrv.axis0.trap_traj.config.accel_limit = 114  # {0.5}
odrv.axis0.trap_traj.config.decel_limit = 114  # {0.5}
odrv.axis0.trap_traj.config.vel_limit = 114  # {2}

# Save ODrive
odrv = save_odrive(odrv, odrv_ID)

# Calibrate VSM motor
print("Calibrating VSM")
odrv.axis0.requested_state = 3  # full calibration sequence
countdown(20)
print()

# Error exit case
if odrv.axis0.error:
    print("ERROR")
    sys.exit()

# Update additional parameters
odrv.axis0.config.startup_closed_loop_control = True  # {False}
odrv.axis0.config.startup_encoder_index_search = True  # {False}
odrv.axis0.encoder.config.pre_calibrated = True  # {False}
odrv.axis0.motor.config.pre_calibrated = True  # {False}

# Save ODrive
odrv = save_odrive(odrv, odrv_ID)

str_in = input("Press ENTER to skip or Y to start VSM anticogging\n")
if str_in in ["y", "Y"]:
    print()
    odrv = autoanticog(odrv, odrv_ID)
    return

return odrv

'''
# odrv0.axis0 data
# odrv0.axis0.controller.electrical_power
# odrv0.axis0.controller.error
# odrv0.axis0.controler.mechanical_power
# odrv0.axis0.current_state
# odrv0.axis0.encoder.count_in_cpr # raw counts bound
# odrv0.axis0.encoder.error
# odrv0.axis0.encoder.index_found
# odrv0.axis0.encoder.is_ready
# odrv0.axis0.encoder.pos_circular # circular turns
# odrv0.axis0.encoder.pos_cpr_counts # circular counts
# odrv0.axis0.encoder.pos_estimate # linear turns
# odrv0.axis0.encoder.pos_estimate_counts # linear counts
# odrv0.axis0.encoder.shadow_count # raw counts
# odrv0.axis0.encoder.vel_estimate # turns/sec
# odrv0.axis0.encoder.vel_estimate_counts # counts/sec
# odrv0.axis0.error
# odrv0.axis0.is_homed
# odrv0.axis0.motor.I_bus
# odrv0.axis0.motor.error
# odrv0.axis0.motor.fet_thermistor.temperature

# odrv0.axis0 fix
# odrv0.axis0.encoder.config.pre_calibrated = False

# odrv0.axis0 input
# odrv0.axis0.controller.input_pos
# odrv0.axis0.controller.start_anticogging_calibration
# odrv0.axis0.requested_state
'''

def autocal_belt(odrv, odrv_ID):

# Continue/cancel autodetection
str_in = input(
    "Remove roller gear prior to calibration\nPress ENTER to continue or X to cancel\n")

if str_in in ["x", "X"]:
    print()
    return

# Update parameters
odrv.axis1.controller.config.control_mode = 2  # velocity control {3}
odrv.axis1.controller.config.input_mode = 2  # velocity ramp {3}
odrv.axis1.controller.config.vel_gain = 0.1667  # {0.1667}
odrv.axis1.controller.config.vel_integrator_gain = 0.3333  # {0.3333}
odrv.axis1.controller.config.vel_limit = 114  # {2}
odrv.axis1.controller.config.vel_ramp_rate = 5  # {1}
odrv.axis1.encoder.config.pre_calibrated = False  # {False}
odrv.axis1.encoder.config.use_index = True  # {False}
odrv.axis1.motor.config.calibration_current = 20  # {10}
odrv.axis1.motor.config.current_lim = 100  # {10}
odrv.axis1.motor.config.current_lim_margin = 80  # {8}
odrv.axis1.motor.config.pre_calibrated = False  # {False}
odrv.axis1.motor.config.requested_current_range = 200  # {60}
odrv.axis1.motor.config.torque_constant = 8.27/190  # {0.04}

# Save ODrive
odrv = save_odrive(odrv, odrv_ID)

# Calibrate velt motor
print("Calibrating Belt motor")
odrv.axis1.requested_state = 3  # full calibration sequence
countdown(20)
print()

# Error exit case
if odrv.axis0.error:
    print("ERROR")
    sys.exit()

# Update additional parameters
odrv.axis1.config.startup_closed_loop_control = True  # {False}
odrv.axis1.config.startup_encoder_index_search = True  # {False}
odrv.axis1.encoder.config.pre_calibrated = True  # {False}
odrv.axis1.motor.config.pre_calibrated = True  # {False}

# Save ODrive
odrv = save_odrive(odrv, odrv_ID)

return odrv

def autocal(odrv, odrv_ID):

# Find ODrive
odrv.axis0.requested_state = 1
odrv.axis1.requested_state = 1

# Print Info
print("Running Variable Stiffness Treadmill Autocalibrater v001")
print(f"ODrive {odrv_ID} with hardware v{odrv.hw_version_major}.{odrv.hw_version_minor}-{odrv.hw_version_variant}V running firmware v{odrv.fw_version_major}.{odrv.fw_version_minor}.{odrv.fw_version_revision}.{odrv.fw_version_unreleased}")
print(f"Pulling {odrv.ibus}A at {odrv.vbus_voltage}V\n")

# Clear/erase ODrive
str_in = input("Press ENTER to clear errors or E to erase configuration\n")

if str_in in ["e", "E"]:
    print()
    print("Erasing ODrive")
    try:
        odrv.erase_configuration()
        countdown(5)
    except Exception:
        countdown(5)
    odrv = odrive.find_any(serial_number=odrv_ID)
    print()
else:
    odrv.clear_errors()

# Update parameters
odrv.config.dc_max_positive_current = 20  # {inf}
odrv.config.enable_brake_resistor = True  # {False}

# Save ODrive
odrv = save_odrive(odrv, odrv_ID)

# Autocalibrate VSM
str_in = input(
    "Press ENTER to skip or Y to start VSM motor autocalibration\n")

if str_in in ["y", "Y"]:
    print()
    odrv = autocal_VSM(odrv, odrv_ID)

# Autocalibrate belt
str_in = input(
    "Press ENTER to skip or Y to start belt motor autocalibration\n")

if str_in in ["y", "Y"]:
    print()
    odrv = autocal_belt(odrv, odrv_ID)

odrv.axis0.requested_state = 8
odrv.axis1.requested_state = 8

'''
# odrv0 data
# odrv0.error
# odrv0.ibus
# odrv0.system_stats.uptime
# odrv0.vbus_voltage
'''

def main():

# Clear terminal
os.system('cls')

# ODrive ID numbers
odrv0_ID = '316D335C3030'
odrv1_ID = '3359365B3539'

# Find ODrives
odrv0 = odrive.find_any(serial_number=odrv0_ID)
# odrv1 = odrive.find_any(serial_number=odrv1_ID)

# Autocalibration sequence
odrv0 = autocal(odrv0, odrv0_ID)
# odrv1 = autocal(odrv1, odrv1_ID)

main()
‘’’

1 Like

Thank you so much!

Sorry I didn’t follow this thread, the issue is MISSING_ESTIMATE is incorrectly parsed. It’s actually DC_BUS_OVER_REGEN or something to that effect. We just pushed an update to odrivetool to correct the error parsing. (pip install --upgrade odrive)

Set dc_max_negstivr_current more negative, and make sure brake resistor is enabled.

1 Like

Hey @ZachTetra I simply wanted to know if I want to run this code (pretty new to all that stuff) do I simply have to create a new pyhton script and copy your code. Then I simply run that ? Do I have to create my python file in a certain folder or do I have to indicate in what folder I want my code to execute etc…

Thank you.

That is dependent on how you run python on your machine, I use VSCode so I have everything in a script running out of my Google Drive in a folder for this project (I have other programs and notes in that folder)

Alternatively, you can just copy the relevant lines into the odrivetool terminal one line at a time (you can paste in blocks of code but sometimes it cries because of how it is tabbed over)

This was the fix!

After taking a long break from this project, not only did I take the bits from @ZachTetra, including the setting of each axis to idle before saving but then also addressing the dc_max_negative_current to a more negative number, I was able to get everything working as intended, for now. lol.

I really appreciate yall helping me out on this!

1 Like

Glad I could help!