Hello, I’m trying to configure my odrive card v3.6 for the D5065 270kv motor from the odrive shop Dual Shaft Motor - D5065 270kv — ODrive Europe, with the AMT102-V encoder. I don’t yet fully understand how the whole system works and I’m trying to understand it. Unfortunately, I’m stuck on calibration. I manage to calibrate with AXIS_STATE_FULL_CALIBRATION_SEQUENCE and AXIS_STATE_CLOSED_LOOP_CONTROL does indeed force the motor, but self.odrv0.axis0.controller.input_vel = doesn’t make the motor turn and I’m getting errors.
do you have basic configuration for this motor ?
and what are the most important features ?
set_motor_specs() do i miss something ?
current code Work in progress :
import tkinter as tk
import odrive
from odrive.enums import *
import time
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.button_frame = tk.Frame(master)
self.button_frame.pack(side="left")
self.label_frame = tk.Frame(master)
self.label_frame.pack(side="right")
self.motor_types = {
0: "HIGH_CURRENT",
2: "GIMBAL",
}
self.odrv0 = None # Initialise le ODrive à None
self.velocity = 0 # Initialise la vitesse à 0
self.master = master
self.pack()
self.create_widgets()
def create_widgets(self):
self.connect_odrive_button = tk.Button(self.button_frame)
self.connect_odrive_button["text"] = "Connect ODrive"
self.connect_odrive_button["command"] = self.connect_odrive
self.connect_odrive_button.pack(side="top")
self.calibration_button = tk.Button(self.button_frame)
self.calibration_button["text"] = "Start Calibration"
self.calibration_button["command"] = self.start_calibration
self.calibration_button.pack(side="top")
self.closed_loop_button = tk.Button(self.button_frame)
self.closed_loop_button["text"] = "Start Closed Loop Control"
self.closed_loop_button["command"] = self.start_closed_loop
self.closed_loop_button.pack(side="top")
self.stop_motor_button = tk.Button(self.button_frame)
self.stop_motor_button["text"] = "Stop Motor"
self.stop_motor_button["command"] = self.stop_motor
self.stop_motor_button.pack(side="top")
self.reboot_odrive_button = tk.Button(self.button_frame)
self.reboot_odrive_button["text"] = "Reboot ODrive"
self.reboot_odrive_button["command"] = self.reboot_odrive
self.reboot_odrive_button.pack(side="top")
self.increase_velocity_button = tk.Button(self.button_frame)
self.increase_velocity_button["text"] = "Increase Velocity by 1"
self.increase_velocity_button["command"] = self.increase_velocity
self.increase_velocity_button.pack(side="top")
self.decrease_velocity_button = tk.Button(self.button_frame)
self.decrease_velocity_button["text"] = "Decrease Velocity by 1"
self.decrease_velocity_button["command"] = self.decrease_velocity
self.decrease_velocity_button.pack(side="top")
# Créer un bouton qui, lorsqu'on clique dessus, imprime les valeurs des labels dans la console
self.print_button = tk.Button(self.label_frame, text="Print labels", command=self.print_labels)
self.print_button.pack(side="top")
self.log = tk.Text(self, height=10, width=50)
self.log.pack(side="bottom")
self.set_specs_button = tk.Button(self.button_frame)
self.set_specs_button["text"] = "Set Motor Specs"
self.set_specs_button["command"] = self.set_motor_specs
self.set_specs_button.pack(side="top")
self.clear_errors_button = tk.Button(self.button_frame)
self.clear_errors_button["text"] = "Clear Errors"
self.clear_errors_button["command"] = self.clear_errors
self.clear_errors_button.pack(side="top")
self.erase_config_button = tk.Button(self.button_frame)
self.erase_config_button["text"] = "ERASE CONFIG"
self.erase_config_button["command"] = self.erase_config
self.erase_config_button.pack(side="top")
self.position_label = tk.Label(self.label_frame, text="")
self.position_label.pack(side="top")
self.velocity_label = tk.Label(self.label_frame, text="")
self.velocity_label.pack(side="top")
self.error_label = tk.Label(self.label_frame, text="")
self.error_label.pack(side="top")
# Ajouter ce label pour le mode
self.mode_label = tk.Label(self.label_frame, text="")
self.mode_label.pack(side="top")
self.current_label = tk.Label(self.label_frame, text="")
self.current_label.pack(side="top")
self.max_current_label = tk.Label(self.label_frame, text="")
self.max_current_label.pack(side="top")
self.torque_label = tk.Label(self.label_frame, text="")
self.torque_label.pack(side="top")
self.voltage_label = tk.Label(self.label_frame, text="")
self.voltage_label.pack(side="top")
self.overvoltage_label = tk.Label(self.label_frame, text="")
self.overvoltage_label.pack(side="top")
self.undervoltage_label = tk.Label(self.label_frame, text="")
self.undervoltage_label.pack(side="top")
self.pole_pairs_label = tk.Label(self.label_frame, text="")
self.pole_pairs_label.pack(side="top")
self.motor_type_label = tk.Label(self.label_frame, text="")
self.motor_type_label.pack(side="top")
self.pole_pairs_label = tk.Label(self.label_frame, text="")
self.pole_pairs_label.pack(side="top")
self.torque_constant_label = tk.Label(self.label_frame, text="")
self.torque_constant_label.pack(side="top")
self.current_lim_label = tk.Label(self.label_frame, text="")
self.current_lim_label.pack(side="top")
self.current_lim_margin_label = tk.Label(self.label_frame, text="")
self.current_lim_margin_label.pack(side="top")
self.calibration_current_label = tk.Label(self.label_frame, text="")
self.calibration_current_label.pack(side="top")
self.vel_limit_label = tk.Label(self.label_frame, text="")
self.vel_limit_label.pack(side="top")
self.vel_gain_label = tk.Label(self.label_frame, text="")
self.vel_gain_label.pack(side="top")
self.vel_integrator_gain_label = tk.Label(self.label_frame, text="")
self.vel_integrator_gain_label.pack(side="top")
self.pre_calibrated_label = tk.Label(self.label_frame, text="")
self.pre_calibrated_label.pack(side="top")
self.phase_resistance_label = tk.Label(self.label_frame, text="")
self.phase_resistance_label.pack(side="top")
self.phase_inductance_label = tk.Label(self.label_frame, text="")
self.phase_inductance_label.pack(side="top")
self.update_status_labels()
def update_status_labels(self):
if self.odrv0 is not None: # Vérifiez si self.odrv0 n'est pas None avant d'essayer d'y accéder
mode = AxisState(self.odrv0.axis0.current_state).name
position_in_turns = self.odrv0.axis0.encoder.pos_estimate / 8192.0
self.position_label["text"] = f"Position: {self.odrv0.axis0.encoder.pos_estimate} ({position_in_turns:.2f} tours)"
self.velocity_label["text"] = f"Velocity: {self.odrv0.axis0.encoder.vel_estimate}"
self.error_label["text"] = f"Error: {self.odrv0.axis0.error} | {AxisError(self.odrv0.axis0.error).name}"
self.mode_label["text"] = f"Mode: {self.odrv0.axis0.current_state} | {AxisState(self.odrv0.axis0.current_state).name}"
self.current_label["text"] = f"Current: {self.odrv0.axis0.motor.current_control.Iq_measured}"
self.max_current_label["text"] = f"Max Current: {self.odrv0.axis0.motor.config.current_lim}"
vbus_voltage = self.odrv0.vbus_voltage
overvoltage_trip_level = self.odrv0.config.dc_bus_overvoltage_trip_level # Get overvoltage trip level
undervoltage_trip_level = self.odrv0.config.dc_bus_undervoltage_trip_level # Get undervoltage trip level
self.voltage_label["text"] = f"Voltage: {vbus_voltage} V" # Display bus voltage
self.overvoltage_label["text"] = f"Overvoltage Trip Level: {overvoltage_trip_level} V"
self.undervoltage_label["text"] = f"Undervoltage Trip Level: {undervoltage_trip_level} V"
self.pole_pairs_label["text"] = f"Motor pole pairs: {self.odrv0.axis0.motor.config.pole_pairs}"
motor_type = self.odrv0.axis0.motor.config.motor_type
self.motor_type_label["text"] = f"Motor type: {self.odrv0.axis0.motor.config.motor_type} | {self.motor_types.get(motor_type, 'UNKNOWN')}"
self.pole_pairs_label["text"] = f"Pole pairs: {self.odrv0.axis0.motor.config.pole_pairs}"
self.torque_constant_label["text"] = f"Torque constant: {self.odrv0.axis0.motor.config.torque_constant}"
self.current_lim_label["text"] = f"Current limit: {self.odrv0.axis0.motor.config.current_lim}"
self.current_lim_margin_label["text"] = f"Current limit margin: {self.odrv0.axis0.motor.config.current_lim_margin}"
self.calibration_current_label["text"] = f"Calibration current: {self.odrv0.axis0.motor.config.calibration_current}"
self.vel_limit_label["text"] = f"Velocity limit: {self.odrv0.axis0.controller.config.vel_limit}"
self.vel_gain_label["text"] = f"Velocity gain: {self.odrv0.axis0.controller.config.vel_gain}"
self.vel_integrator_gain_label["text"] = f"Velocity integrator gain: {self.odrv0.axis0.controller.config.vel_integrator_gain}"
self.pre_calibrated_label["text"] = f"Pre-calibrated: {self.odrv0.axis0.motor.config.pre_calibrated}"
phase_resistance = self.odrv0.axis0.motor.config.phase_resistance
phase_inductance = self.odrv0.axis0.motor.config.phase_inductance
self.phase_resistance_label["text"] = f"Phase Resistance: {phase_resistance}"
self.phase_inductance_label["text"] = f"Phase Inductance: {phase_inductance}"
# Ajout d'une mise à jour périodique
# Le couple peut être calculé comme I * kT, où I est le courant et kT est la constante de couple du moteur
self.torque_label["text"] = f"Torque: {self.odrv0.axis0.motor.current_control.Iq_measured * self.odrv0.axis0.motor.config.torque_constant}"
self.after(1000, self.update_status_labels) # Mise à jour des informations chaque seconde
def connect_odrive(self):
self.odrv0 = odrive.find_any()
self.log.insert(tk.END, "ODrive connected.\n")
def start_calibration(self):
self.odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE
self.log.insert(tk.END, "Calibration started.\n")
def start_closed_loop(self):
self.odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
self.log.insert(tk.END, "Closed loop control started.\n")
def stop_motor(self):
self.odrv0.axis0.requested_state = AXIS_STATE_IDLE
self.log.insert(tk.END, "Motor stopped.\n")
def reboot_odrive(self):
self.odrv0.reboot()
time.sleep(2) # Attente de quelques secondes pour le redémarragethon
self.odrv0 = odrive.find_any()
self.log.insert(tk.END, "ODrive rebooted and reconnected.\n")
def increase_velocity(self):
if self.odrv0 is not None: # Assurez-vous que ODrive est connecté
self.velocity += 1
self.odrv0.axis0.controller.input_vel = self.velocity
self.log.insert(tk.END, f"Velocity increased to {self.velocity}.\n")
else:
self.log.insert(tk.END, "Please connect to ODrive first.\n")
def decrease_velocity(self):
if self.odrv0 is not None: # Assurez-vous que ODrive est connecté
self.velocity -= 1
self.odrv0.axis0.controller.input_vel = self.velocity
self.log.insert(tk.END, f"Velocity decreased to {self.velocity}.\n")
else:
self.log.insert(tk.END, "Please connect to ODrive first.\n")
def set_motor_specs(self):
if self.odrv0 is not None:
# Configurer les spécifications du moteur
self.odrv0.axis0.motor.config.motor_type = MOTOR_TYPE_HIGH_CURRENT
self.odrv0.axis0.motor.config.pole_pairs = 7 # Le moteur D5065 a 14 pôles, donc 7 paires de pôles
self.odrv0.axis0.motor.config.torque_constant = 8.27 / 270 # Le KV du moteur est de 270
self.odrv0.axis0.motor.config.current_lim = 60 # Courant nominal de 20A
self.odrv0.axis0.motor.config.current_lim_margin = 10.0 # Laisser une marge de 10A
self.odrv0.axis0.motor.config.calibration_current = 42.0 # Utiliser un courant de calibration de 10A
self.odrv0.axis0.controller.config.vel_limit = (6350 / 60) * (2 * 3.14159) # Limite de vitesse nominale en rad/s
self.odrv0.axis0.controller.config.vel_gain = 0.02 / ((270 * 8.27) / 60) # Gain de vitesse basé sur la vitesse nominale
self.odrv0.axis0.controller.config.vel_integrator_gain = 0.1 / ((270 * 8.27) / 60) # Gain de l'intégrateur de vitesse basé sur la vitesse nominale
self.odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION
self.odrv0.axis0.motor.config.pre_calibrated = True # Activer la pré-calibration
self.odrv0.save_configuration() # Enregistrer la configuration
def erase_config(self):
if self.odrv0 is not None:
self.odrv0.axis0.motor.config.pre_calibrated = False # Activer la pré-calibration
self.odrv0.erase_configuration()
time.sleep(2) # Wait for a couple of seconds for the erase to complete
self.log.insert(tk.END, "ODrive configuration reset. Please manually reboot the ODrive.\n")
def clear_errors(self):
if self.odrv0 is not None:
self.odrv0.axis0.error = 0
self.odrv0.axis0.motor.error = 0
self.odrv0.axis0.controller.error = 0
self.odrv0.axis0.encoder.error = 0
self.log.insert(tk.END, "Errors cleared.\n")
def print_labels(self):
print(f"Position: {self.position_label['text']}")
print(f"Velocity: {self.velocity_label['text']}")
print(f"Error: {self.error_label['text']}")
print(f"Mode: {self.mode_label['text']}")
print(f"Current: {self.current_label['text']}")
print(f"Max Current: {self.max_current_label['text']}")
print(f"Voltage: {self.voltage_label['text']}")
print(f"Overvoltage: {self.overvoltage_label['text']}")
print(f"Undervoltage: {self.undervoltage_label['text']}")
print(f"Pole Pairs: {self.pole_pairs_label['text']}")
print(f"Motor Type: {self.motor_type_label['text']}")
print(f"Torque Constant: {self.torque_constant_label['text']}")
print(f"Current Limit: {self.current_lim_label['text']}")
print(f"Current Limit Margin: {self.current_lim_margin_label['text']}")
print(f"Calibration Current: {self.calibration_current_label['text']}")
print(f"Velocity Limit: {self.vel_limit_label['text']}")
print(f"Velocity Gain: {self.vel_gain_label['text']}")
print(f"Velocity Integrator Gain: {self.vel_integrator_gain_label['text']}")
print(f"Pre-calibrated: {self.pre_calibrated_label['text']}")
print(f"Phase Resistance: {self.phase_resistance_label['text']}")
print(f"Phase Inductance: {self.phase_inductance_label['text']}")
root = tk.Tk()
app = Application(master=root)
app.mainloop()