#!/usr/bin/env python3 """PLSR P15 four-axis positive/negative soft-limit precision test.""" from __future__ import annotations import argparse import time import serial from plsr_modbus_counter_stress_test import ( CALL_COMMIT, CALL_START, CMD_SET_POSITION, CONTROL_BASE, CONTROL_WINDOW_WORDS, RESULT_OK, RESULT_QUEUED, RtuClient, S0_BASES, S1_BASES, check_result, put_u32, read_axis_status, send_call, send_command, ) from plsr_modbus_frequency_test import choose_port STATE_ACCEL = 2 STATE_RUN = 3 STATE_STOPPED = 8 STATE_IDLE = 1 CMD_RESET_ERROR = 10 ERROR_LIMIT_POSITIVE = 6 ERROR_LIMIT_NEGATIVE = 7 STOP_LIMIT_POSITIVE = 5 STOP_LIMIT_NEGATIVE = 6 SOFT_LIMIT = 1_000_000 CASES = ( # axis, start position, frequency, requested pulses, expected output pulses (0, 999_800, 500, 10_000, 200), (1, 999_000, 2_000, 10_000, 1_000), (2, -999_800, 500, -10_000, 200), (3, -999_000, 2_000, -10_000, 1_000), ) def wait_command_applied( client: RtuClient, axis: int, sequence: int, expected_result: int = RESULT_OK, timeout: float = 3.0, ) -> dict[str, int]: deadline = time.monotonic() + timeout latest: dict[str, int] | None = None while time.monotonic() < deadline: latest = read_axis_status(client, axis) if latest["last_sequence"] == sequence: if latest["last_result"] != expected_result: raise RuntimeError( f"轴{axis}命令#{sequence}执行结果={latest['last_result']}," f"期望={expected_result};状态={latest}" ) return latest raise RuntimeError(f"等待轴{axis}命令#{sequence}执行超时,最后状态={latest}") def wait_state( client: RtuClient, axis: int, expected: set[int], timeout: float = 6.0 ) -> dict[str, int]: deadline = time.monotonic() + timeout latest: dict[str, int] | None = None while time.monotonic() < deadline: latest = read_axis_status(client, axis) if latest["state"] in expected: return latest raise RuntimeError( f"等待轴{axis}状态{sorted(expected)}超时,最后状态={latest}" ) def write_job( client: RtuClient, axis: int, frequency_hz: int, signed_pulses: int ) -> None: s0 = [0] * 20 put_u32(s0, 0, 1) put_u32(s0, 10, frequency_hz) put_u32(s0, 12, signed_pulses) client.write_multiple(S0_BASES[axis], s0) client.write_multiple(S1_BASES[axis], [0] * 4) def main() -> int: parser = argparse.ArgumentParser( description="PLSR P15 四轴正负软限位边界精度测试" ) parser.add_argument("--port", help="串口,例如 COM5;只有一个串口时可省略") parser.add_argument("--baud", type=int, default=9600) parser.add_argument("--slave", type=int, default=1) args = parser.parse_args() with serial.Serial( port=choose_port(args.port), baudrate=args.baud, bytesize=serial.EIGHTBITS, parity=serial.PARITY_EVEN, stopbits=serial.STOPBITS_ONE, timeout=1.0, write_timeout=1.0, ) as uart: client = RtuClient(uart, args.slave) header = client.read_holding(CONTROL_BASE, 8) if header[:5] != [ 0x504C, 0x5352, 0x0100, CONTROL_WINDOW_WORDS, 0x0007, ]: raise RuntimeError( f"P15控制窗口未就绪:{header};请烧录当前固件并复位" ) print("P15 已就绪:软限位±1000000,K2保护矩阵") sequence = 500 for axis, start_position, frequency, requested, expected_pulses in CASES: positive = requested > 0 label = "正限位" if positive else "负限位" expected_position = SOFT_LIMIT if positive else -SOFT_LIMIT expected_error = ( ERROR_LIMIT_POSITIVE if positive else ERROR_LIMIT_NEGATIVE ) expected_reason = ( STOP_LIMIT_POSITIVE if positive else STOP_LIMIT_NEGATIVE ) response = send_command( client, sequence, axis, CMD_SET_POSITION, start_position ) check_result(response, 4, RESULT_QUEUED, f"轴{axis} SET_POSITION") before = wait_command_applied(client, axis, sequence) sequence += 1 write_job(client, axis, frequency, requested) response = send_call( client, sequence, axis, CALL_COMMIT, s2_set=2 ) check_result(response, 3, RESULT_OK, f"轴{axis} COMMIT") sequence += 1 start_sequence = sequence response = send_call( client, start_sequence, axis, CALL_START, s2_set=2 ) check_result(response, 3, RESULT_QUEUED, f"轴{axis} START") sequence += 1 running = wait_state(client, axis, {STATE_ACCEL, STATE_RUN}) if running["counter_mode"] != 1: raise RuntimeError(f"轴{axis}未取得硬件计数器:{running}") stopped = wait_state(client, axis, {STATE_STOPPED}) physical_delta = stopped["physical_pulses"] - before["physical_pulses"] expected_task = expected_pulses if positive else -expected_pulses checks = { "逻辑位置": (stopped["logical_position"], expected_position), "任务脉冲": (stopped["task_pulses"], expected_task), "物理脉冲增量": (physical_delta, expected_pulses), } bad = { name: values for name, values in checks.items() if abs(values[0] - values[1]) > 1 } if bad: raise RuntimeError(f"轴{axis}{label}边界超差:{bad};状态={stopped}") if ( stopped["error"] != expected_error or stopped["stop_reason"] != expected_reason ): raise RuntimeError(f"轴{axis}{label}错误语义不正确:{stopped}") print( f"轴{axis} {label} {frequency:5d}Hz:" f"输出={physical_delta},位置={stopped['logical_position']},PASS" ) response = send_command(client, sequence, axis, CMD_RESET_ERROR) check_result(response, 4, RESULT_QUEUED, f"轴{axis} RESET_ERROR") reset = wait_command_applied(client, axis, sequence) sequence += 1 if reset["state"] != STATE_IDLE or reset["error"] != 0: raise RuntimeError(f"轴{axis}错误复位不完整:{reset}") print("全部 PASS:四轴正/负软限位在±1脉冲窗口内停止,错误码及复位正确。") print("请核对波形:Q0/Q2约200个上升沿,Q1/Q3约1000个上升沿。") return 0 if __name__ == "__main__": try: raise SystemExit(main()) except (RuntimeError, serial.SerialException) as error: print(f"测试失败:{error}") raise SystemExit(1)