First Odrive d5065 270kv configuration

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()