169 lines
5.1 KiB
Python
169 lines
5.1 KiB
Python
|
|
import time
|
|||
|
|
import struct
|
|||
|
|
import can
|
|||
|
|
|
|||
|
|
|
|||
|
|
def can_id_single_motor(motor_id: int) -> int:
|
|||
|
|
"""
|
|||
|
|
单电机命令:标识符 = 0x140 + ID(1~32)
|
|||
|
|
"""
|
|||
|
|
assert 1 <= motor_id <= 32
|
|||
|
|
return 0x140 + motor_id
|
|||
|
|
|
|||
|
|
|
|||
|
|
def send_run(bus: can.Bus, motor_id: int):
|
|||
|
|
"""
|
|||
|
|
电机运行命令:DATA[0]=0x88,其余 0
|
|||
|
|
"""
|
|||
|
|
arb_id = can_id_single_motor(motor_id)
|
|||
|
|
data = bytes([0x88, 0, 0, 0, 0, 0, 0, 0])
|
|||
|
|
msg = can.Message(arbitration_id=arb_id, data=data, is_extended_id=False)
|
|||
|
|
bus.send(msg)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def send_stop(bus: can.Bus, motor_id: int):
|
|||
|
|
"""
|
|||
|
|
电机停止命令:DATA[0]=0x81,其余 0
|
|||
|
|
"""
|
|||
|
|
arb_id = can_id_single_motor(motor_id)
|
|||
|
|
data = bytes([0x81, 0, 0, 0, 0, 0, 0, 0])
|
|||
|
|
msg = can.Message(arbitration_id=arb_id, data=data, is_extended_id=False)
|
|||
|
|
bus.send(msg)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def send_speed_cmd_a2(bus: can.Bus, motor_id: int, speed_dps: float):
|
|||
|
|
"""
|
|||
|
|
速度闭环控制命令1(0xA2):
|
|||
|
|
DATA[0]=0xA2
|
|||
|
|
DATA[4..7]=speedControl(int32, little-endian), 0.01 dps/LSB
|
|||
|
|
|
|||
|
|
speed_dps: 目标角速度 (deg/s)
|
|||
|
|
"""
|
|||
|
|
speed_control = int(round(speed_dps / 0.01)) # 0.01 dps/LSB
|
|||
|
|
arb_id = can_id_single_motor(motor_id)
|
|||
|
|
|
|||
|
|
data = bytearray(8)
|
|||
|
|
data[0] = 0xA2
|
|||
|
|
data[1] = 0x00
|
|||
|
|
data[2] = 0x00
|
|||
|
|
data[3] = 0x00
|
|||
|
|
data[4:8] = struct.pack("<i", speed_control) # int32 little-endian
|
|||
|
|
|
|||
|
|
msg = can.Message(arbitration_id=arb_id, data=data, is_extended_id=False)
|
|||
|
|
bus.send(msg)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def send_speed_cmd_ad(bus: can.Bus, motor_id: int, speed_dps: float, iq_limit: int):
|
|||
|
|
"""
|
|||
|
|
速度闭环控制命令2(0xAD,带力矩/转矩电流限制):
|
|||
|
|
DATA[0]=0xAD
|
|||
|
|
DATA[2..3]=iqControl(int16, little-endian), 范围 -2048~2048
|
|||
|
|
DATA[4..7]=speedControl(int32, little-endian), 0.01 dps/LSB
|
|||
|
|
"""
|
|||
|
|
iq_limit = int(iq_limit)
|
|||
|
|
if not (-2048 <= iq_limit <= 2048):
|
|||
|
|
raise ValueError("iq_limit must be in [-2048, 2048]")
|
|||
|
|
|
|||
|
|
speed_control = int(round(speed_dps / 0.01))
|
|||
|
|
arb_id = can_id_single_motor(motor_id)
|
|||
|
|
|
|||
|
|
data = bytearray(8)
|
|||
|
|
data[0] = 0xAD
|
|||
|
|
data[1] = 0x00
|
|||
|
|
data[2:4] = struct.pack("<h", iq_limit) # int16 little-endian
|
|||
|
|
data[4:8] = struct.pack("<i", speed_control) # int32 little-endian
|
|||
|
|
|
|||
|
|
msg = can.Message(arbitration_id=arb_id, data=data, is_extended_id=False)
|
|||
|
|
bus.send(msg)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def read_status2(bus: can.Bus, motor_id: int, timeout=0.2):
|
|||
|
|
"""
|
|||
|
|
读取电机状态2命令(0x9C),回复里包含:
|
|||
|
|
temperature(int8), iq/power(int16), speed(int16, 1 dps/LSB), encoder(uint16)
|
|||
|
|
注意:这里的 speed 是“反馈转速”,分辨率 1 dps/LSB(协议里写 int16,1dps/LSB)
|
|||
|
|
"""
|
|||
|
|
arb_id = can_id_single_motor(motor_id)
|
|||
|
|
|
|||
|
|
# 发请求
|
|||
|
|
req = can.Message(arbitration_id=arb_id, data=bytes([0x9C, 0, 0, 0, 0, 0, 0, 0]), is_extended_id=False)
|
|||
|
|
bus.send(req)
|
|||
|
|
|
|||
|
|
# 收回复(同 arb_id)
|
|||
|
|
t_end = time.time() + timeout
|
|||
|
|
while time.time() < t_end:
|
|||
|
|
msg = bus.recv(timeout=timeout)
|
|||
|
|
if msg is None:
|
|||
|
|
break
|
|||
|
|
if msg.arbitration_id != arb_id or len(msg.data) != 8:
|
|||
|
|
continue
|
|||
|
|
if msg.data[0] != 0x9C:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
temperature = struct.unpack("<b", msg.data[1:2])[0]
|
|||
|
|
iq_or_power = struct.unpack("<h", msg.data[2:4])[0]
|
|||
|
|
speed_fb_dps = struct.unpack("<h", msg.data[4:6])[0] # 1 dps/LSB
|
|||
|
|
encoder = struct.unpack("<H", msg.data[6:8])[0]
|
|||
|
|
return {
|
|||
|
|
"temperature_C": temperature,
|
|||
|
|
"iq_or_power_raw": iq_or_power,
|
|||
|
|
"speed_fb_dps": speed_fb_dps,
|
|||
|
|
"encoder": encoder,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def rpm_to_dps(rpm: float) -> float:
|
|||
|
|
# 1 rev = 360 deg; rpm -> deg/s
|
|||
|
|
return rpm * 360.0 / 60.0
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
# 1) 配置你的 CAN 接口(按你的系统改 channel / bustype)
|
|||
|
|
# 常见:socketcan: channel="can0"
|
|||
|
|
# bus = can.Bus(interface="socketcan", channel="can0", bitrate=1000000)
|
|||
|
|
bus = can.Bus(interface="canalystii", channel=0, bitrate=1000000)
|
|||
|
|
|
|||
|
|
motor_id = 2
|
|||
|
|
# 2) 使能运行
|
|||
|
|
send_run(bus, motor_id)
|
|||
|
|
time.sleep(0.05)
|
|||
|
|
|
|||
|
|
# 3) 恒速:例如 30 rpm
|
|||
|
|
target_rpm = 100.0
|
|||
|
|
target_dps = rpm_to_dps(target_rpm)
|
|||
|
|
|
|||
|
|
# 推荐:循环周期性刷新速度命令(例如 50~200Hz),更稳
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
last_print = 0.0
|
|||
|
|
while True:
|
|||
|
|
# 方案A:纯速度闭环(0xA2)
|
|||
|
|
send_speed_cmd_a2(bus, 1, target_dps)
|
|||
|
|
send_speed_cmd_a2(bus, 2, target_dps)
|
|||
|
|
send_speed_cmd_a2(bus, 3, target_dps)
|
|||
|
|
# send_speed_cmd_a2(bus, 4, target_dps)
|
|||
|
|
|
|||
|
|
# 方案B:带转矩电流限制(0xAD),例如限制 iq=300
|
|||
|
|
# send_speed_cmd_ad(bus, motor_id, target_dps, iq_limit=300)
|
|||
|
|
|
|||
|
|
time.sleep(0.01) # 100 Hz
|
|||
|
|
|
|||
|
|
if time.time() - last_print > 0.1:
|
|||
|
|
st1 = read_status2(bus, 1)
|
|||
|
|
st2 = read_status2(bus, 2)
|
|||
|
|
st3 = read_status2(bus, 3)
|
|||
|
|
# st4 = read_status2(bus, 4)
|
|||
|
|
print("status:", [st1, st2, st3])
|
|||
|
|
last_print = time.time()
|
|||
|
|
|
|||
|
|
except KeyboardInterrupt:
|
|||
|
|
send_stop(bus, 1)
|
|||
|
|
send_stop(bus, 2)
|
|||
|
|
send_stop(bus, 3)
|
|||
|
|
# send_stop(bus, 4)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|