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.
 
 
 
 
 
 

144 Zeilen
5.3 KiB

  1. #!/usr/bin/env python3
  2. """Validate the production Modbus M -> PLSR WAIT bit-data path.
  3. X is intentionally read-only through function 0x02. A real X/EXT/hard-limit
  4. test is only possible after the board-specific GPIO-to-X mapping is supplied.
  5. """
  6. from __future__ import annotations
  7. import argparse
  8. import struct
  9. import time
  10. import serial
  11. from plsr_modbus_control_test import (
  12. CALL_COMMIT,
  13. CALL_START,
  14. CONTROL_BASE,
  15. CONTROL_WINDOW_WORDS,
  16. S0_BASE,
  17. S1_BASE,
  18. check_result,
  19. read_axis_status,
  20. send_call,
  21. )
  22. from plsr_modbus_frequency_test import RtuClient, choose_port, signed_dword_words
  23. RESULT_OK = 0
  24. STATE_WAIT = 5
  25. STATE_COMPLETED = 7
  26. M_TEST_POINT = 123
  27. def write_coil(client: RtuClient, address: int, value: bool) -> None:
  28. encoded = 0xFF00 if value else 0x0000
  29. pdu = bytes((0x05,)) + struct.pack(">HH", address, encoded)
  30. response = client.exchange(pdu, 8)
  31. expected = bytes((client.slave, 0x05)) + struct.pack(">HH", address, encoded)
  32. if response[:6] != expected:
  33. raise RuntimeError(f"FC05 回显错误: {response.hex(' ')}")
  34. def read_bits(client: RtuClient, function: int, address: int, count: int) -> list[int]:
  35. byte_count = (count + 7) // 8
  36. pdu = bytes((function,)) + struct.pack(">HH", address, count)
  37. response = client.exchange(pdu, 5 + byte_count)
  38. if response[1] != function or response[2] != byte_count:
  39. raise RuntimeError(f"FC{function:02X} 响应格式错误: {response.hex(' ')}")
  40. return [
  41. (response[3 + (index // 8)] >> (index % 8)) & 1
  42. for index in range(count)
  43. ]
  44. def wait_for_state(client: RtuClient, expected: int, timeout: float) -> dict[str, int]:
  45. deadline = time.monotonic() + timeout
  46. latest: dict[str, int] | None = None
  47. while time.monotonic() < deadline:
  48. latest = read_axis_status(client)
  49. if latest["state"] == expected:
  50. return latest
  51. raise RuntimeError(f"等待状态 {expected} 超时,最后状态: {latest}")
  52. def prepare_wait_job(client: RtuClient) -> None:
  53. words = [0] * 20
  54. words[0:2] = signed_dword_words(1) # one segment
  55. words[10:12] = signed_dword_words(1000) # 1000 Hz
  56. words[12:14] = signed_dword_words(500) # +500 pulses
  57. words[14] = (2 << 8) | 5 # WAIT_SIGNAL, source M
  58. words[15:17] = signed_dword_words(M_TEST_POINT)
  59. words[17] = 0 # constant fall-through jump
  60. words[18:20] = signed_dword_words(0)
  61. client.write_multiple(S0_BASE, words)
  62. client.write_multiple(S1_BASE, [0, 0, 0, 0])
  63. def main() -> int:
  64. parser = argparse.ArgumentParser(
  65. description="PLSR 真实 Modbus M 位源、WAIT 与 FC02 X 只读视图测试"
  66. )
  67. parser.add_argument("--port", default="COM5")
  68. parser.add_argument("--baud", type=int, default=9600)
  69. parser.add_argument("--slave", type=int, default=1)
  70. args = parser.parse_args()
  71. with serial.Serial(
  72. port=choose_port(args.port),
  73. baudrate=args.baud,
  74. bytesize=serial.EIGHTBITS,
  75. parity=serial.PARITY_EVEN,
  76. stopbits=serial.STOPBITS_ONE,
  77. timeout=1.0,
  78. write_timeout=1.0,
  79. ) as uart:
  80. client = RtuClient(uart, args.slave)
  81. header = client.read_holding(CONTROL_BASE, 8)
  82. if header[3] != CONTROL_WINDOW_WORDS:
  83. raise RuntimeError(
  84. f"控制窗口版本不匹配: firmware={header[3]}, script={CONTROL_WINDOW_WORDS}"
  85. )
  86. # FC02 must exist and must not alias writable M coils.
  87. x_before = read_bits(client, 0x02, M_TEST_POINT, 1)[0]
  88. write_coil(client, M_TEST_POINT, False)
  89. if read_bits(client, 0x01, M_TEST_POINT, 1) != [0]:
  90. raise RuntimeError("FC05 写 M=0 后 FC01 回读不一致")
  91. if read_bits(client, 0x02, M_TEST_POINT, 1)[0] != x_before:
  92. raise RuntimeError("X 与 M 发生别名:写 M 意外改变了 FC02 X")
  93. prepare_wait_job(client)
  94. sequence = int(time.time()) & 0x7FFFFFFF
  95. response = send_call(client, sequence, CALL_COMMIT)
  96. check_result(response, RESULT_OK, "COMMIT")
  97. response = send_call(client, sequence + 1, CALL_START)
  98. check_result(response, RESULT_OK, "START")
  99. waiting = wait_for_state(client, STATE_WAIT, 5.0)
  100. if waiting["task_pulses"] != 500:
  101. raise RuntimeError(f"进入 WAIT 时任务脉冲不是 500: {waiting}")
  102. print("M123=0:500 脉冲完成后稳定进入 WAIT")
  103. write_coil(client, M_TEST_POINT, True)
  104. if read_bits(client, 0x01, M_TEST_POINT, 1) != [1]:
  105. raise RuntimeError("FC05 写 M=1 后 FC01 回读不一致")
  106. completed = wait_for_state(client, STATE_COMPLETED, 3.0)
  107. if completed["task_pulses"] != 500:
  108. raise RuntimeError(f"WAIT 释放后任务计数异常: {completed}")
  109. print("M123 0->1:PLSR WAIT 已释放并正常 COMPLETED")
  110. print(f"FC02 X123 只读值={x_before};写 M 不会改变 X")
  111. print("PASS:Modbus FC05/FC01 -> M image -> PLSR readBit/WAIT 生产链通过")
  112. print("待硬件映射后再测:实际 X 输入、EXT 上升沿及正/负硬限位。")
  113. return 0
  114. if __name__ == "__main__":
  115. try:
  116. raise SystemExit(main())
  117. except (RuntimeError, serial.SerialException) as error:
  118. print(f"测试失败:{error}")
  119. raise SystemExit(1)