I am new to odrive and can bus.
I have two motors and drivers on the can bus:
id 0: odrive S1
id 1: odrive micro
The motors are identical. Custom built motors with US digital 4096 incremental encoders (I have used these for years with traditional bldc controllers). I can walk through the odrive gui setup, save the configurations, do index search, etc., and run them via the gui with no issues. The work great.
My understanding is that the configuration saved in the gui is saved on the drivers, and I can go directly to contolling them over can. The only reason to send the configuration via can would be if I had changes, and did not want to use the gui.
With the code below, based on the simple example, id1 will spin, as if it is doing an index search, proceed to the velocity setting, but not spin. Power increases for 5 seconds, but no movement.
id0 will not do the index search, at all. If I remove the index search, it will set the velocity, draw high current, but not spin.
As noted, these both run GREAT via the gui. What am I doing wrong?
"""
Minimal example for controlling an ODrive via the CANSimple protocol.
Puts the ODrive into closed loop control mode, sends a velocity setpoint of 1.0
and then prints the encoder feedback.
Assumes that the ODrive is already configured for velocity control.
See https://docs.odriverobotics.com/v/latest/manual/can-protocol.html for protocol
documentation.
"""
import can
import struct
import time
node_id = 1 # must match `<odrv>.axis0.config.can.node_id`. The default is 0.
bus = can.interface.Bus("can0", bustype="socketcan")
print('#### print status currently')
cmd_id = 0x01 # heartbeat command ID
message_id = (node_id << 5 | cmd_id)
for msg in bus:
if msg.arbitration_id == message_id:
error, state, result, traj_done = struct.unpack('<IBBB', bytes(msg.data[:7]))
break
print(error, state, result, traj_done)
print('#### flushing buffer')
# Flush CAN RX buffer so there are no more old pending messages
while not (bus.recv(timeout=0) is None): pass
# ----- ADD: run encoder index search -----
print('#### index search')
bus.send(can.Message(
arbitration_id=(node_id << 5 | 0x07), # 0x07: Set_Axis_State
data=struct.pack('<I', 6), # 6 = ENCODER_INDEX_SEARCHL
is_extended_id=False
))
# wait until state != 6 (watch heartbeat)
while True:
msg = bus.recv(timeout=0.5)
print(msg)
if msg and msg.arbitration_id == ((0x01 << 5) | node_id): # Heartbeat
error, state, result, trajDone = struct.unpack('<IBBB', bytes(msg.data[:7]))
if error != 0:
raise RuntimeError(f'Axis error 0x{error:08X}')
if state != 6:
break
print('#### index search done')
time.sleep(1)
# Put axis into closed loop control state
print('#### put in closed loop')
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
))
# Wait for axis to enter closed loop control by scanning heartbeat messages
print('#### wait for close loop to take effect')
for msg in bus:
print(msg)
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
print('worked, set to 8, close loop')
break
# Set velocity to 1.0 turns/s
vSet=30.0
print('#### set velocity')
bus.send(can.Message(
arbitration_id=(node_id << 5 | 0x0d), # 0x0d: Set_Input_Vel
data=struct.pack('<ff', vSet, 0.0), # 1.0: velocity, 0.0: torque feedforward
is_extended_id=False
))
'''
# Print encoder feedback
print('#### Encoder feedback')
for msg in bus:
if msg.arbitration_id == (node_id << 5 | 0x09): # 0x09: Get_Encoder_Estimates
pos, vel = struct.unpack('<ff', bytes(msg.data))
print(f"pos: {pos:.3f} [turns], vel: {vel:.3f} [turns/s]")
'''
time.sleep(5)
vSet=0.0
print('#### turning off')
bus.send(can.Message(
arbitration_id=(node_id << 5 | 0x0d), # 0x0d: Set_Input_Vel
data=struct.pack('<ff', vSet, 0.0), # 1.0: velocity, 0.0: torque feedforward
is_extended_id=False
))