ODrive S1 Position Control via CAN Bus

Hello, I’m using the ODrive S1 and M8325s Motor Kit, powered by a 24V 4.5A power supply. The system is configured via the GUI for position control (filtered). I’m controlling the motor using the CAN bus protocol with a Python script (code provided below). The issue I’m encountering is that the motor always starts with a very high velocity and then enters an error state, regardless of the velocity or torque limits I attempt to set. When the target position is close, the motor reaches it without error—but the set velocity is still ignored. I would appreciate guidance on how to properly set velocity or torque limits when using position control via CAN bus to avoid this overshoot and error. For your reference, I’ve included both the Python code and the ODrive configuration. Thank you!

Python Code:

import can
import struct
node_id = 0 # must match <odrv>.axis0.config.can.node_id. The default is 0.
bus = can.interface.Bus(“can0”, interface=“socketcan”)

while not (bus.recv(timeout=0) is None): pass

bus.send(can.Message(
arbitration_id=(node_id << 5 | 0x07), # 0x07: Set_Axis_State
data=struct.pack(‘<I’, 8), # 8: AxisState.CLOSED_LOOP_CONTROL
is_extended_id=False
))

for msg in bus:
if msg.arbitration_id == (node_id << 5 | 0x01): # 0x01: Heartbeat
error, state, result, traj_done = struct.unpack(‘<IBBB’, bytes(msg.data[:7]))
if state == 8: # 8: AxisState.CLOSED_LOOP_CONTROL
break

bus.send(can.Message(
arbitration_id=(node_id << 5 | 0x0B),
data=struct.pack(‘<II’, 3, 3),
is_extended_id=False
))
desPos = 0.0 #0 5
velo = 0 # 10 100 1000
torque = 0 # 10 100 1000
bus.send(can.Message(
arbitration_id=(node_id << 5 | 0x0C),
data=struct.pack(‘<fhh’, desPos, velo, torque),
is_extended_id=False
))

Config:
odrv = odrv0
odrv.config.dc_bus_overvoltage_trip_level = 30
odrv.config.dc_bus_undervoltage_trip_level = 10.5
odrv.config.dc_max_positive_current = math.inf
odrv.config.dc_max_negative_current = -math.inf
odrv.config.brake_resistor0.enable = False
odrv.axis0.config.motor.motor_type = MotorType.HIGH_CURRENT
odrv.axis0.config.motor.pole_pairs = 20
odrv.axis0.config.motor.torque_constant = 0.0827
odrv.axis0.config.motor.current_soft_max = 50
odrv.axis0.config.motor.current_hard_max = 70
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 = True
odrv.axis0.motor.motor_thermistor.config.r_ref = 10000
odrv.axis0.motor.motor_thermistor.config.beta = 3435
odrv.axis0.motor.motor_thermistor.config.temp_limit_lower = 110
odrv.axis0.motor.motor_thermistor.config.temp_limit_upper = 130
odrv.axis0.controller.config.control_mode = ControlMode.POSITION_CONTROL
odrv.axis0.controller.config.input_mode = InputMode.POS_FILTER
odrv.axis0.controller.config.vel_limit = 10
odrv.axis0.controller.config.vel_limit_tolerance = 1.2
odrv.axis0.config.torque_soft_min = -math.inf
odrv.axis0.config.torque_soft_max = math.inf
odrv.axis0.controller.config.input_filter_bandwidth = 20
odrv.can.config.protocol = Protocol.SIMPLE
odrv.can.config.baud_rate = 250000
odrv.axis0.config.can.node_id = 0
odrv.axis0.config.can.heartbeat_msg_rate_ms = 100
odrv.axis0.config.can.encoder_msg_rate_ms = 10
odrv.axis0.config.can.iq_msg_rate_ms = 10
odrv.axis0.config.can.torques_msg_rate_ms = 10
odrv.axis0.config.can.error_msg_rate_ms = 10
odrv.axis0.config.can.temperature_msg_rate_ms = 10
odrv.axis0.config.can.bus_voltage_msg_rate_ms = 10
odrv.axis0.config.enable_watchdog = False
odrv.axis0.config.load_encoder = EncoderId.ONBOARD_ENCODER0
odrv.axis0.config.commutation_encoder = EncoderId.ONBOARD_ENCODER0
odrv.config.enable_uart_a = False

Hi! When commanding large position step changes in PASSTHROUGH or POS_FILTER input mode, especially when your PID gains haven’t been tuned, a bit of velocity overshoot is normal – these input modes are generally designed for streaming in external trajectories, instead of point to point movement.

Your velocity hard max is quite close to your soft max (vel_limit_tolerance*vel_limit = 12 turns/s hard max, vel_limit = 10 turn/s soft max). I would recommend either increasing the vel_limit_tolerance (or hard velocity limit in the GUI configuration flow), or switching to trajectory control (TRAP_TRAJ input mode), which is more suited for large point to point movements, as it clamps acceleration/deceleration to set bounds.

1 Like

Thank you for your reply.
I changed the input mode to TRAP_TRAJ and also increased the hard velocity limit. Now the motor no longer enters an error state.

However, it still seems that I don’t have any control over the velocity or torque using the following CAN message:

arbitration_id=(node_id << 5 | 0x0C),
data=struct.pack(‘<fhh’, desPos, velo, torque),
is_extended_id=False
))

No matter what values I use for velo and torque, they appear to be ignored—the motor always moves at the velocity limit set in the configuration.

Why are these parameters included if they don’t have any effect?

The 0x0C Set_Input_Pos message velocity and torque fields are feedforwards, not limits. For TRAP_TRAJ, only the torque feedforward is used, as the ODrive will calculate the velocity setpoint. So you should be setting the velocity and torque fields to zero, as long as you don’t have a specific reason to use torque feedforwards.

If you want to change the TRAP_TRAJ accel/decel/vel limits in real time, you need to use the 0x11 Set_Traj_Vel_Limit and 0x12 Set_Traj_Accel_Limits commands.

1 Like

Thank you very much! I did the same, and it’s working perfectly.

Wonderful!

1 Like