#!/usr/bin/env python3 # -*- coding: utf-8 -*- import argparse import math import struct import can CMD_READ_MULTITURN_ANGLE = 0x92 def arb_id_single(motor_id: int) -> int: # 单电机命令/回包 arbitration id return 0x140 + int(motor_id) def build_read_multiturn_angle(motor_id: int) -> can.Message: """ 读取多圈角度命令: data[0]=0x92,其余填0 """ data = [CMD_READ_MULTITURN_ANGLE, 0, 0, 0, 0, 0, 0, 0] return can.Message(arbitration_id=arb_id_single(motor_id), is_extended_id=False, data=data) def sign_extend(value: int, bits: int) -> int: """ 对 value 做符号扩展,使其成为 Python int(有符号) """ sign_bit = 1 << (bits - 1) mask = (1 << bits) - 1 value &= mask return (value ^ sign_bit) - sign_bit def parse_multiturn_angle_reply_int56(msg: can.Message) -> int: """ 兼容你贴的文档:DATA[1..7] 是角度的低->高字节(共7字节) => 解析为有符号 int56 单位:0.01 deg / LSB """ d = bytes(msg.data) if len(d) != 8 or d[0] != CMD_READ_MULTITURN_ANGLE: raise ValueError("not 0x92 reply") raw_u56 = int.from_bytes(d[1:8], byteorder="little", signed=False) # 7 bytes raw_s56 = sign_extend(raw_u56, 56) return raw_s56 def parse_multiturn_angle_reply_int64(msg: can.Message) -> int: """ 如果你确认实际回的是 8 字节 motorAngle(int64): 常见做法是 DATA[0..7] 都是 motorAngle 的 8 字节(但会与“命令字节=0x92”冲突) 或者命令字节在别处。 这个函数留作你确认后切换用。 """ d = bytes(msg.data) # 这里假设 d[0] 仍是 0x92,则 motorAngle 应该在 d[1:9] 不存在 # 所以只有当你的实际报文不是“d[0]=0x92”时才用它 raise NotImplementedError("Need exact 8-byte layout confirmation.") def main(): ap = argparse.ArgumentParser() ap.add_argument("--interface", default="canalystii", help="python-can interface: canalystii / socketcan / ...") ap.add_argument("--channel", default="0", help="channel: canalystii 0/1, socketcan can0/can1 ...") ap.add_argument("--bitrate", type=int, default=1000000) ap.add_argument("--motor-id", type=int, default=2) ap.add_argument("--timeout", type=float, default=0.2, help="seconds") ap.add_argument("--repeat", type=int, default=3, help="query times") args = ap.parse_args() motor_id = args.motor_id rx_arb = arb_id_single(motor_id) bus = can.Bus(interface=args.interface, channel=args.channel, bitrate=args.bitrate) # 只收这个电机的回包(降低噪声) try: bus.set_filters([{"can_id": rx_arb, "can_mask": 0x7FF, "extended": False}]) except Exception: pass try: for i in range(args.repeat): # 发送 0x92 bus.send(build_read_multiturn_angle(motor_id)) # 等回包 msg = bus.recv(timeout=args.timeout) if msg is None: print(f"[M{motor_id}] timeout: no reply") continue if msg.arbitration_id != rx_arb or len(msg.data) != 8: print(f"[M{motor_id}] unexpected frame: arb=0x{msg.arbitration_id:X} data={list(msg.data)}") continue # 解析(按你贴的文档:int56 in DATA[1..7]) motor_angle_raw = parse_multiturn_angle_reply_int56(msg) # 单位换算:0.01°/LSB angle_deg = motor_angle_raw * 0.01 angle_rad = angle_deg * math.pi / 180.0 print( f"[M{motor_id}] motorAngle_raw={motor_angle_raw} " f"angle_deg={angle_deg:+.4f}° angle_rad={angle_rad:+.6f} rad" ) finally: bus.shutdown() if __name__ == "__main__": main()