返回教程正文

配套源码

zdt_x57s_protocol.py

can-gateway/zdt_x57s_protocol.py
zdt-x57s-can-test:封装单电机 Brick 并组合验证四台电机can-gateway/zdt_x57s_protocol.py
Python160 行
  1. """
  2. @description : 封装ZDT X57S第二代闭环电机FW_Emm固定CAN协议
  3. @param : 无
  4. @return : 无
  5. """
  6. CHECKSUM = 0x6B
  7. ACK_SUCCESS = 0x02
  8. ACK_REJECTED = 0xE2
  9. class ZdtProtocolError(RuntimeError):
  10. """
  11. @description : 表示ZDT应答格式错误或电机拒绝命令
  12. @param message : 错误说明
  13. @return : ZdtProtocolError实例
  14. """
  15. def __init__(self, message):
  16. """
  17. @description : 初始化ZDT协议异常
  18. @param message : 错误说明
  19. @return : 无
  20. """
  21. super().__init__(message)
  22. def validate_motor_id(motor_id, allow_broadcast=False):
  23. """
  24. @description : 检查ZDT电机地址是否合法
  25. @param motor_id : 电机地址
  26. @param allow_broadcast: true允许广播地址0; false仅允许1至255
  27. @return : 合法时返回规范化整数地址
  28. """
  29. normalized_id = int(motor_id)
  30. minimum_id = 0 if allow_broadcast else 1
  31. if normalized_id < minimum_id or normalized_id > 255:
  32. raise ValueError("motor_id must be in the valid address range")
  33. return normalized_id
  34. def arbitration_id(motor_id, packet_index=0):
  35. """
  36. @description : 生成ZDT CAN扩展帧标识符Addr左移8位或分包序号
  37. @param motor_id : 电机地址,范围0至255
  38. @param packet_index : 分包序号,范围0至255
  39. @return : 29位CAN扩展帧标识符
  40. """
  41. normalized_id = validate_motor_id(motor_id, allow_broadcast=True)
  42. normalized_packet = int(packet_index)
  43. if normalized_packet < 0 or normalized_packet > 255:
  44. raise ValueError("packet_index must be in range 0-255")
  45. return (normalized_id << 8) | normalized_packet
  46. def build_speed_query():
  47. """
  48. @description : 构造读取电机实时转速命令
  49. @param : 无
  50. @return : 数据35 6B
  51. """
  52. return bytes((0x35, CHECKSUM))
  53. def build_enable_command(enabled, synchronized=False):
  54. """
  55. @description : 构造电机使能或失能命令
  56. @param enabled : true使能; false失能
  57. @param synchronized : true等待同步启动; false立即执行
  58. @return : F3功能码命令数据
  59. """
  60. return bytes(
  61. (
  62. 0xF3,
  63. 0xAB,
  64. 0x01 if enabled else 0x00,
  65. 0x01 if synchronized else 0x00,
  66. CHECKSUM,
  67. )
  68. )
  69. def build_speed_command(rpm, acceleration_level, synchronized=False):
  70. """
  71. @description : 构造FW_Emm速度模式F6命令
  72. @param rpm : 带符号目标转速,单位整数RPM,范围负3000至3000
  73. @param acceleration_level: 加减速档位,范围0至255,0表示直接启动
  74. @param synchronized : true等待同步启动; false立即执行
  75. @return : 7字节F6命令数据
  76. """
  77. normalized_rpm = int(rpm)
  78. normalized_acceleration = int(acceleration_level)
  79. if normalized_rpm < -3000 or normalized_rpm > 3000:
  80. raise ValueError("rpm must be in range -3000 to 3000")
  81. if normalized_acceleration < 0 or normalized_acceleration > 255:
  82. raise ValueError("acceleration_level must be in range 0-255")
  83. speed = abs(normalized_rpm)
  84. return bytes(
  85. (
  86. 0xF6,
  87. 0x01 if normalized_rpm < 0 else 0x00,
  88. (speed >> 8) & 0xFF,
  89. speed & 0xFF,
  90. normalized_acceleration,
  91. 0x01 if synchronized else 0x00,
  92. CHECKSUM,
  93. )
  94. )
  95. def build_stop_command(synchronized=False):
  96. """
  97. @description : 构造立即停止命令
  98. @param synchronized : true等待同步启动; false立即执行
  99. @return : FE功能码命令数据
  100. """
  101. return bytes(
  102. (0xFE, 0x98, 0x01 if synchronized else 0x00, CHECKSUM)
  103. )
  104. def parse_ack(data, expected_function):
  105. """
  106. @description : 校验ZDT控制命令确认应答
  107. @param data : 电机返回的数据字节
  108. @param expected_function: 期望的功能码
  109. @return : 校验成功返回True
  110. """
  111. payload = bytes(data)
  112. if len(payload) < 3:
  113. raise ZdtProtocolError("acknowledgement is too short")
  114. if payload[-1] != CHECKSUM:
  115. raise ZdtProtocolError("acknowledgement checksum is invalid")
  116. if payload[0] != expected_function:
  117. raise ZdtProtocolError("unexpected acknowledgement function code")
  118. if payload[1] == ACK_REJECTED:
  119. raise ZdtProtocolError("motor rejected the command")
  120. if payload[1] != ACK_SUCCESS:
  121. raise ZdtProtocolError("unexpected acknowledgement status")
  122. return True
  123. def parse_speed_reply(data):
  124. """
  125. @description : 解析FW_Emm实时转速应答
  126. @param data : 期望格式35 方向 转速高字节 转速低字节 6B
  127. @return : 带符号实时转速,单位整数RPM
  128. """
  129. payload = bytes(data)
  130. if len(payload) != 5:
  131. raise ZdtProtocolError("speed reply length must be 5 bytes")
  132. if payload[0] != 0x35 or payload[-1] != CHECKSUM:
  133. raise ZdtProtocolError("speed reply function or checksum is invalid")
  134. if payload[1] not in (0x00, 0x01):
  135. raise ZdtProtocolError("speed reply direction is invalid")
  136. magnitude = (payload[2] << 8) | payload[3]
  137. return -magnitude if payload[1] == 0x01 else magnitude