Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

208 Zeilen
7.0 KiB

  1. #!/usr/bin/env python3
  2. """PLSR P15 four-axis positive/negative soft-limit precision test."""
  3. from __future__ import annotations
  4. import argparse
  5. import time
  6. import serial
  7. from plsr_modbus_counter_stress_test import (
  8. CALL_COMMIT,
  9. CALL_START,
  10. CMD_SET_POSITION,
  11. CONTROL_BASE,
  12. CONTROL_WINDOW_WORDS,
  13. RESULT_OK,
  14. RESULT_QUEUED,
  15. RtuClient,
  16. S0_BASES,
  17. S1_BASES,
  18. check_result,
  19. put_u32,
  20. read_axis_status,
  21. send_call,
  22. send_command,
  23. )
  24. from plsr_modbus_frequency_test import choose_port
  25. STATE_ACCEL = 2
  26. STATE_RUN = 3
  27. STATE_STOPPED = 8
  28. STATE_IDLE = 1
  29. CMD_RESET_ERROR = 10
  30. ERROR_LIMIT_POSITIVE = 6
  31. ERROR_LIMIT_NEGATIVE = 7
  32. STOP_LIMIT_POSITIVE = 5
  33. STOP_LIMIT_NEGATIVE = 6
  34. SOFT_LIMIT = 1_000_000
  35. CASES = (
  36. # axis, start position, frequency, requested pulses, expected output pulses
  37. (0, 999_800, 500, 10_000, 200),
  38. (1, 999_000, 2_000, 10_000, 1_000),
  39. (2, -999_800, 500, -10_000, 200),
  40. (3, -999_000, 2_000, -10_000, 1_000),
  41. )
  42. def wait_command_applied(
  43. client: RtuClient,
  44. axis: int,
  45. sequence: int,
  46. expected_result: int = RESULT_OK,
  47. timeout: float = 3.0,
  48. ) -> dict[str, int]:
  49. deadline = time.monotonic() + timeout
  50. latest: dict[str, int] | None = None
  51. while time.monotonic() < deadline:
  52. latest = read_axis_status(client, axis)
  53. if latest["last_sequence"] == sequence:
  54. if latest["last_result"] != expected_result:
  55. raise RuntimeError(
  56. f"轴{axis}命令#{sequence}执行结果={latest['last_result']},"
  57. f"期望={expected_result};状态={latest}"
  58. )
  59. return latest
  60. raise RuntimeError(f"等待轴{axis}命令#{sequence}执行超时,最后状态={latest}")
  61. def wait_state(
  62. client: RtuClient, axis: int, expected: set[int], timeout: float = 6.0
  63. ) -> dict[str, int]:
  64. deadline = time.monotonic() + timeout
  65. latest: dict[str, int] | None = None
  66. while time.monotonic() < deadline:
  67. latest = read_axis_status(client, axis)
  68. if latest["state"] in expected:
  69. return latest
  70. raise RuntimeError(
  71. f"等待轴{axis}状态{sorted(expected)}超时,最后状态={latest}"
  72. )
  73. def write_job(
  74. client: RtuClient, axis: int, frequency_hz: int, signed_pulses: int
  75. ) -> None:
  76. s0 = [0] * 20
  77. put_u32(s0, 0, 1)
  78. put_u32(s0, 10, frequency_hz)
  79. put_u32(s0, 12, signed_pulses)
  80. client.write_multiple(S0_BASES[axis], s0)
  81. client.write_multiple(S1_BASES[axis], [0] * 4)
  82. def main() -> int:
  83. parser = argparse.ArgumentParser(
  84. description="PLSR P15 四轴正负软限位边界精度测试"
  85. )
  86. parser.add_argument("--port", help="串口,例如 COM5;只有一个串口时可省略")
  87. parser.add_argument("--baud", type=int, default=9600)
  88. parser.add_argument("--slave", type=int, default=1)
  89. args = parser.parse_args()
  90. with serial.Serial(
  91. port=choose_port(args.port),
  92. baudrate=args.baud,
  93. bytesize=serial.EIGHTBITS,
  94. parity=serial.PARITY_EVEN,
  95. stopbits=serial.STOPBITS_ONE,
  96. timeout=1.0,
  97. write_timeout=1.0,
  98. ) as uart:
  99. client = RtuClient(uart, args.slave)
  100. header = client.read_holding(CONTROL_BASE, 8)
  101. if header[:5] != [
  102. 0x504C,
  103. 0x5352,
  104. 0x0100,
  105. CONTROL_WINDOW_WORDS,
  106. 0x0007,
  107. ]:
  108. raise RuntimeError(
  109. f"P15控制窗口未就绪:{header};请烧录当前固件并复位"
  110. )
  111. print("P15 已就绪:软限位±1000000,K2保护矩阵")
  112. sequence = 500
  113. for axis, start_position, frequency, requested, expected_pulses in CASES:
  114. positive = requested > 0
  115. label = "正限位" if positive else "负限位"
  116. expected_position = SOFT_LIMIT if positive else -SOFT_LIMIT
  117. expected_error = (
  118. ERROR_LIMIT_POSITIVE if positive else ERROR_LIMIT_NEGATIVE
  119. )
  120. expected_reason = (
  121. STOP_LIMIT_POSITIVE if positive else STOP_LIMIT_NEGATIVE
  122. )
  123. response = send_command(
  124. client, sequence, axis, CMD_SET_POSITION, start_position
  125. )
  126. check_result(response, 4, RESULT_QUEUED, f"轴{axis} SET_POSITION")
  127. before = wait_command_applied(client, axis, sequence)
  128. sequence += 1
  129. write_job(client, axis, frequency, requested)
  130. response = send_call(
  131. client, sequence, axis, CALL_COMMIT, s2_set=2
  132. )
  133. check_result(response, 3, RESULT_OK, f"轴{axis} COMMIT")
  134. sequence += 1
  135. start_sequence = sequence
  136. response = send_call(
  137. client, start_sequence, axis, CALL_START, s2_set=2
  138. )
  139. check_result(response, 3, RESULT_QUEUED, f"轴{axis} START")
  140. sequence += 1
  141. running = wait_state(client, axis, {STATE_ACCEL, STATE_RUN})
  142. if running["counter_mode"] != 1:
  143. raise RuntimeError(f"轴{axis}未取得硬件计数器:{running}")
  144. stopped = wait_state(client, axis, {STATE_STOPPED})
  145. physical_delta = stopped["physical_pulses"] - before["physical_pulses"]
  146. expected_task = expected_pulses if positive else -expected_pulses
  147. checks = {
  148. "逻辑位置": (stopped["logical_position"], expected_position),
  149. "任务脉冲": (stopped["task_pulses"], expected_task),
  150. "物理脉冲增量": (physical_delta, expected_pulses),
  151. }
  152. bad = {
  153. name: values
  154. for name, values in checks.items()
  155. if abs(values[0] - values[1]) > 1
  156. }
  157. if bad:
  158. raise RuntimeError(f"轴{axis}{label}边界超差:{bad};状态={stopped}")
  159. if (
  160. stopped["error"] != expected_error
  161. or stopped["stop_reason"] != expected_reason
  162. ):
  163. raise RuntimeError(f"轴{axis}{label}错误语义不正确:{stopped}")
  164. print(
  165. f"轴{axis} {label} {frequency:5d}Hz:"
  166. f"输出={physical_delta},位置={stopped['logical_position']},PASS"
  167. )
  168. response = send_command(client, sequence, axis, CMD_RESET_ERROR)
  169. check_result(response, 4, RESULT_QUEUED, f"轴{axis} RESET_ERROR")
  170. reset = wait_command_applied(client, axis, sequence)
  171. sequence += 1
  172. if reset["state"] != STATE_IDLE or reset["error"] != 0:
  173. raise RuntimeError(f"轴{axis}错误复位不完整:{reset}")
  174. print("全部 PASS:四轴正/负软限位在±1脉冲窗口内停止,错误码及复位正确。")
  175. print("请核对波形:Q0/Q2约200个上升沿,Q1/Q3约1000个上升沿。")
  176. return 0
  177. if __name__ == "__main__":
  178. try:
  179. raise SystemExit(main())
  180. except (RuntimeError, serial.SerialException) as error:
  181. print(f"测试失败:{error}")
  182. raise SystemExit(1)