HardwareX/ODrive Calibration/ODrive_calibration.py
2023-02-15 10:45:13 +01:00

273 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
These configurations is based on Austin Owens implementation: https://github.com/AustinOwens/robodog/blob/main/odrive/configs/odrive_hoverboard_config.py
It was based on the hoverboard tutorial found on the Odrive Robotics website at: https://docs.odriverobotics.com/v/0.5.4/hoverboard.html
"""
import sys
import time
import math
import odrive
from odrive.enums import *
import fibre.libfibre
from fibre import Logger, Event
from odrive.utils import OperationAbortedException
class MotorConfig:
"""
Class for configuring an Odrive axis for a Hoverboard motor.
Only works with one Odrive at a time.
"""
# Hoverboard Kv
HOVERBOARD_KV = 16.0
# Min/Max phase inductance of motor
MIN_PHASE_INDUCTANCE = 0
MAX_PHASE_INDUCTANCE = 0.001
# Min/Max phase resistance of motor
MIN_PHASE_RESISTANCE = 0
MAX_PHASE_RESISTANCE = 0.5
# Tolerance for encoder offset float
ENCODER_OFFSET_FLOAT_TOLERANCE = 0.05
def __init__(self, axis_num):
"""
Initalizes MotorConfig class by finding odrive, erase its
configuration, and grabbing specified axis object.
:param axis_num: Which channel/motor on the odrive your referring to.
:type axis_num: int (0 or 1)
"""
self.axis_num = axis_num
# Connect to Odrive
print("Looking for ODrive...")
self._find_odrive()
print("Found ODrive.")
def _find_odrive(self):
# connect to Odrive
self.odrv = odrive.find_any()
self.odrv_axis = getattr(self.odrv, "axis{}".format(self.axis_num))
def configure(self):
"""
Configures the odrive device for a hoverboard motor.
"""
# Erase pre-exsisting configuration
print("Erasing pre-exsisting configuration...")
try:
self.odrv.erase_configuration()
except ChannelBrokenException:
pass
self._find_odrive()
# Standard 6.5 inch hoverboard hub motors have 30 permanent magnet
# poles, and thus 15 pole pairs
self.odrv_axis.motor.config.pole_pairs = 15
# Hoverboard hub motors are quite high resistance compared to the hobby
# aircraft motors, so we want to use a bit higher voltage for the motor
# calibration, and set up the current sense gain to be more sensitive.
# The motors are also fairly high inductance, so we need to reduce the
# bandwidth of the current controller from the default to keep it
# stable.
self.odrv_axis.motor.config.resistance_calib_max_voltage = 4
self.odrv_axis.motor.config.requested_current_range = 25
self.odrv_axis.motor.config.current_control_bandwidth = 100
# Estimated KV but should be measured using the "drill test", which can
# be found here: # https://discourse.odriverobotics.com/t/project-hoverarm/441
self.odrv_axis.motor.config.torque_constant = 8.27 / self.HOVERBOARD_KV
# Hoverboard motors contain hall effect sensors instead of incremental
# encorders
self.odrv_axis.encoder.config.mode = ENCODER_MODE_HALL
# The hall feedback has 6 states for every pole pair in the motor. Since
# we have 15 pole pairs, we set the cpr to 15*6 = 90.
self.odrv_axis.encoder.config.cpr = 90
# Since hall sensors are low resolution feedback, we also bump up the
#offset calibration displacement to get better calibration accuracy.
self.odrv_axis.encoder.config.calib_scan_distance = 150
# Since the hall feedback only has 90 counts per revolution, we want to
# reduce the velocity tracking bandwidth to get smoother velocity
# estimates. We can also set these fairly modest gains that will be a
# bit sloppy but shouldnt shake your rig apart if its built poorly.
# Make sure to tune the gains up when you have everything else working
# to a stiffness that is applicable to your application.
self.odrv_axis.encoder.config.bandwidth = 100
self.odrv_axis.controller.config.pos_gain = 1
self.odrv_axis.controller.config.vel_gain = 0.02 * self.odrv_axis.motor.config.torque_constant * self.odrv_axis.encoder.config.cpr
self.odrv_axis.controller.config.vel_integrator_gain = 0.1 * self.odrv_axis.motor.config.torque_constant * self.odrv_axis.encoder.config.cpr
self.odrv_axis.controller.config.vel_limit = 10
# Set in position control mode so we can control the position of the wheel
self.odrv_axis.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL
# In the next step we are going to start powering the motor and so we
# want to make sure that some of the above settings that require a
# reboot are applied first.
print("Saving manual configuration and rebooting...")
self.odrv.save_configuration()
print("Manual configuration saved.")
try:
self.odrv.reboot()
except ChannelBrokenException:
pass
self._find_odrive()
input("Make sure the motor is free to move, then press enter...")
print("Calibrating Odrive for hoverboard motor (you should hear a " "beep)...")
self.odrv_axis.requested_state = AXIS_STATE_MOTOR_CALIBRATION
# Wait for calibration to take place
time.sleep(10)
if self.odrv_axis.motor.error != 0:
print("Error: Odrive reported an error of {} while in the state "
"AXIS_STATE_MOTOR_CALIBRATION. Printing out Odrive motor data for "
"debug:\n{}".format(self.odrv_axis.motor.error,
self.odrv_axis.motor))
sys.exit(1)
if self.odrv_axis.motor.config.phase_inductance <= self.MIN_PHASE_INDUCTANCE or \
self.odrv_axis.motor.config.phase_inductance >= self.MAX_PHASE_INDUCTANCE:
print("Error: After odrive motor calibration, the phase inductance "
"is at {}, which is outside of the expected range. Either widen the "
"boundaries of MIN_PHASE_INDUCTANCE and MAX_PHASE_INDUCTANCE (which "
"is between {} and {} respectively) or debug/fix your setup. Printing "
"out Odrive motor data for debug:\n{}".format(self.odrv_axis.motor.config.phase_inductance,
self.MIN_PHASE_INDUCTANCE,
self.MAX_PHASE_INDUCTANCE,
self.odrv_axis.motor))
sys.exit(1)
if self.odrv_axis.motor.config.phase_resistance <= self.MIN_PHASE_RESISTANCE or \
self.odrv_axis.motor.config.phase_resistance >= self.MAX_PHASE_RESISTANCE:
print("Error: After odrive motor calibration, the phase resistance "
"is at {}, which is outside of the expected range. Either raise the "
"MAX_PHASE_RESISTANCE (which is between {} and {} respectively) or "
"debug/fix your setup. Printing out Odrive motor data for "
"debug:\n{}".format(self.odrv_axis.motor.config.phase_resistance,
self.MIN_PHASE_RESISTANCE,
self.MAX_PHASE_RESISTANCE,
self.odrv_axis.motor))
sys.exit(1)
# If all looks good, then lets tell ODrive that saving this calibration
# to persistent memory is OK
self.odrv_axis.motor.config.pre_calibrated = True
# Check the alignment between the motor and the hall sensor. Because of
# this step you are allowed to plug the motor phases in random order and
# also the hall signals can be random. Just dont change it after
# calibration.
print("Calibrating Odrive for encoder...")
self.odrv_axis.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
# Wait for calibration to take place
time.sleep(30)
if self.odrv_axis.encoder.error != 0:
print("Error: Odrive reported an error of {} while in the state "
"AXIS_STATE_ENCODER_OFFSET_CALIBRATION. Printing out Odrive encoder "
"data for debug:\n{}".format(self.odrv_axis.encoder.error,
self.odrv_axis.encoder))
sys.exit(1)
# If offset_float isn't 0.5 within some tolerance, or its not 1.5 within
# some tolerance, raise an error
if not ((self.odrv_axis.encoder.config.offset_float > 0.5 - self.ENCODER_OFFSET_FLOAT_TOLERANCE and \
self.odrv_axis.encoder.config.offset_float < 0.5 + self.ENCODER_OFFSET_FLOAT_TOLERANCE) or \
(self.odrv_axis.encoder.config.offset_float > 1.5 - self.ENCODER_OFFSET_FLOAT_TOLERANCE and \
self.odrv_axis.encoder.config.offset_float < 1.5 + self.ENCODER_OFFSET_FLOAT_TOLERANCE)):
print("Error: After odrive encoder calibration, the 'offset_float' "
"is at {}, which is outside of the expected range. 'offset_float' "
"should be close to 0.5 or 1.5 with a tolerance of {}. Either "
"increase the tolerance or debug/fix your setup. Printing out "
"Odrive encoder data for debug:\n{}".format(self.odrv_axis.encoder.config.offset_float,
self.ENCODER_OFFSET_FLOAT_TOLERANCE,
self.odrv_axis.encoder))
sys.exit(1)
# If all looks good, then lets tell ODrive that saving this calibration
# to persistent memory is OK
self.odrv_axis.encoder.config.pre_calibrated = True
print("Saving calibration configuration and rebooting...")
self.odrv.save_configuration()
print("Calibration configuration saved.")
try:
self.odrv.reboot()
except ChannelBrokenException:
pass
self._find_odrive()
print("Odrive configuration finished.")
def mode_idle(self):
"""
Puts the motor in idle (i.e. can move freely).
"""
self.odrv_axis.requested_state = AXIS_STATE_IDLE
def mode_close_loop_control(self):
"""
Puts the motor in closed loop control.
"""
self.odrv_axis.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
def move_input_pos(self, angle):
"""
Puts the motor at a certain angle.
:param angle: Angle you want the motor to move.
:type angle: int or float
"""
self.odrv_axis.controller.input_pos = angle/360.0
if __name__ == "__main__":
hb_motor_config = MotorConfig(axis_num = 0)
hb_motor_config.configure()
print("CONDUCTING MOTOR TEST")
print("Placing motor in close loop control. If you move motor, motor will "
"resist you.")
hb_motor_config.mode_close_loop_control()
# Go from 0 to 360 degrees in increments of 30 degrees
for angle in range(0, 390, 30):
print("Setting motor to {} degrees.".format(angle))
hb_motor_config.move_input_pos(angle)
time.sleep(5)
print("Placing motor in idle. If you move motor, motor will "
"move freely")
hb_motor_config.mode_idle()