|
- #!/usr/bin/env python3
- """PLSR P16 Modbus 性能统计自动测试(对应规格:PLSR_MODBUS_PERFORMANCE_TEST.md)。
-
- 本脚本在四轴 100kHz 输出与 Modbus 持续轮询的并发压力下,测量并判定
- 规格中定义的各项性能指标,运行完成后自动读取 P16 DWT 统计并做预算检查:
-
- - 版本号一致性(任务要求的“32/64 位版本号一致性检查”):
- D1205~D1206 的 32 位 CPU 计时频率非零、D1207 性能统计版本为 V7、
- 控制窗口协议版本 0x0100;P16 主统计块(32 位周期数与 16 位饱和字段)
- 连续两次读取必须完全一致。
- - 单条命令响应时间:0x03/0x10 完整往返耗时,判定预算 = 波特率折算的
- 线上传输时间(8E1,每字符 11 位)+ 10ms 处理余量。
- - 连续状态读取吞吐:四轴状态块(48 字×4)持续轮询速率,要求不低于
- 波特率理论上限的 50%。
- - 重复序号幂等:运行期重发完全相同序号的命令,固件必须只回放既有应答、
- 不得重复执行(沿用 P13 控制测试的 PAUSE 重复序号用例)。
- - 并发读写压力模拟:四轴 100kHz 期间持续读取轴状态,并穿插重写相同的
- S0/S1 数据(幂等写入)与读取 P16 统计块,验证并发下计数精确。
- - P16 自动预算:PlsrProcess 自身 < 1ms、TIM6 控制 ISR < 0.1ms、输出/计数
- ISR < 10us,四项均须实际执行(数值不得为 0);墙钟响应单独显示,
- 超过 1ms 只给抢占提示、不判失败。
-
- 与规格/现有脚本的差异说明:
- - 规格“测试方法”一节引用 P14 脚本;本脚本独立实现相同的四轴 100kHz +
- 持续轮询场景(规格要求),并额外测量主机侧性能指标,满足规格
- “自动预算”与“记录到测试报告”的要求。
- - 规格要求“无毛刺、无残余输出”,该两项只能由逻辑分析仪验收,本脚本
- 按规格保留为人工核对提示,与 plsr_modbus_counter_stress_test.py 口径一致。
- - P16 主统计块读取比 P14 脚本更严格:必须连续两次读取一致(规格要求
- 统计快照一致性),三次尝试仍不一致即判定失败。
- """
-
- from __future__ import annotations
-
- import argparse
- import time
-
- import serial
-
- from plsr_modbus_frequency_test import RtuClient, choose_port, signed_dword_words
-
-
- CONTROL_BASE = 1200
- CONTROL_WINDOW_WORDS = 338
- CALL_REQUEST = CONTROL_BASE + 8
- CALL_RESPONSE = CONTROL_BASE + 24
- COMMAND_REQUEST = CONTROL_BASE + 40
- COMMAND_RESPONSE = CONTROL_BASE + 48
- AXIS_STATUS_BASE = CONTROL_BASE + 64
- AXIS_STATUS_WORDS = 48
- PERFORMANCE_BASE = CONTROL_BASE + 56 # D1256:P16 主统计块
- STAGE_PERFORMANCE_BASE = CONTROL_BASE + 256 # D1456:P16 分阶段统计块
- AB_GATE_PERFORMANCE_BASE = CONTROL_BASE + 268 # D1468:AB末周期快速门控
- PERFORMANCE_VERSION = 7
- STAGE_NAMES = (
- "脉冲合并/保护",
- "关键事件",
- "命令队列",
- "普通事件/方向提交",
- "HAL/路径/Profile",
- "HSD检查点",
- )
- STAGE_PERFORMANCE_WORDS = len(STAGE_NAMES) * 2
-
- S0_BASES = (1600, 1800, 2000, 2200)
- S1_BASES = (1700, 1900, 2100, 2300)
- TEST_FREQUENCY_HZ = 100_000
- TEST_PULSES = 200_000
-
- RESULT_OK = 0
- RESULT_QUEUED = 1
- STATE_ACCEL = 2
- STATE_RUN = 3
- STATE_PAUSED = 6
- STATE_COMPLETED = 7
-
- CALL_COMMIT = 1
- CALL_START = 2
- CMD_PAUSE = 3
- CMD_RESUME = 4
- CMD_SET_POSITION = 5
-
- BITS_PER_CHARACTER = 11 # Modbus RTU 8E1:每字符 11 位
- PROCESSING_BUDGET_MS = 10.0 # 单条命令响应预算中的处理余量(主机+从站开销)
- THROUGHPUT_MIN_RATIO = 0.5 # 实测吞吐不低于波特率理论上限的比例
- LATENCY_SAMPLES = 20 # 每种命令的响应时间采样次数
- RUN_DEADLINE_SECONDS = 15.0 # 四轴运行完成的等待上限
-
- METRICS: list[tuple[str, bool, str]] = []
-
-
- def put_u32(words: list[int], offset: int, value: int) -> None:
- words[offset : offset + 2] = signed_dword_words(value)
-
-
- def put_u64(words: list[int], offset: int, value: int) -> None:
- raw = value & 0xFFFFFFFFFFFFFFFF
- words[offset : offset + 4] = [
- (raw >> shift) & 0xFFFF for shift in (0, 16, 32, 48)
- ]
-
-
- def get_u32(words: list[int], offset: int) -> int:
- return words[offset] | (words[offset + 1] << 16)
-
-
- def get_u64(words: list[int], offset: int, signed: bool = False) -> int:
- raw = sum(words[offset + index] << (16 * index) for index in range(4))
- if signed and raw & (1 << 63):
- return raw - (1 << 64)
- return raw
-
-
- def wait_response(
- client: RtuClient, address: int, words: int, sequence: int, timeout: float = 2.0
- ) -> list[int]:
- deadline = time.monotonic() + timeout
- while time.monotonic() < deadline:
- response = client.read_holding(address, words)
- if get_u32(response, 0) == sequence:
- return response
- raise RuntimeError(f"等待序号 {sequence} 的应答超时")
-
-
- def send_command(
- client: RtuClient, sequence: int, axis: int, opcode: int, argument: int = 0
- ) -> list[int]:
- request = [0] * 8
- put_u32(request, 0, sequence)
- request[2] = opcode
- request[3] = axis
- put_u64(request, 4, argument)
- client.write_multiple(COMMAND_REQUEST, request)
- return wait_response(client, COMMAND_RESPONSE, 8, sequence)
-
-
- def send_call(
- client: RtuClient,
- sequence: int,
- axis: int,
- operation: int,
- s2_set: int = 1,
- ) -> list[int]:
- request = [0] * 16
- put_u32(request, 0, sequence)
- request[2] = 0 # S0 device D
- put_u32(request, 3, S0_BASES[axis])
- request[5] = 0 # S1 device D
- put_u32(request, 6, S1_BASES[axis])
- request[8] = 0 # S2 constant
- put_u32(request, 10, s2_set)
- request[12] = axis
- request[13] = 0 # PULSE/DIR
- request[14] = operation
- client.write_multiple(CALL_REQUEST, request)
- return wait_response(client, CALL_RESPONSE, 12, sequence)
-
-
- def check_result(response: list[int], offset: int, expected: int, label: str) -> None:
- if response[offset] != expected:
- raise RuntimeError(
- f"{label} 返回 {response[offset]},期望 {expected};应答={response}"
- )
-
-
- def read_axis_status(client: RtuClient, axis: int) -> dict[str, int]:
- address = AXIS_STATUS_BASE + axis * AXIS_STATUS_WORDS
- words = client.read_holding(address, AXIS_STATUS_WORDS)
- generation_begin = get_u32(words, 0)
- generation_end = get_u32(words, 46)
- if generation_begin != generation_end or generation_begin & 1:
- raise RuntimeError(
- f"轴{axis}状态快照不一致:begin={generation_begin}, end={generation_end}"
- )
- return {
- "state": words[2],
- "flags": get_u32(words, 3),
- "error": words[6],
- "stop_reason": words[7],
- "last_result": words[8],
- "last_sequence": get_u32(words, 10),
- "logical_position": get_u64(words, 16, signed=True),
- "task_pulses": get_u64(words, 20, signed=True),
- "physical_pulses": get_u64(words, 28),
- "counter_mode": words[37],
- "current_frequency": get_u32(words, 38),
- "target_frequency": get_u32(words, 40),
- }
-
-
- def wait_status(
- client: RtuClient,
- axis: int,
- states: set[int],
- sequence: int | None = None,
- timeout: float = 5.0,
- ) -> dict[str, int]:
- deadline = time.monotonic() + timeout
- latest: dict[str, int] | None = None
- while time.monotonic() < deadline:
- latest = read_axis_status(client, axis)
- sequence_ok = sequence is None or latest["last_sequence"] == sequence
- if latest["state"] in states and sequence_ok:
- return latest
- raise RuntimeError(f"等待轴{axis}状态 {sorted(states)} 超时,最后状态:{latest}")
-
-
- def wait_running_output(
- client: RtuClient, axis: int, sequence: int, timeout: float = 5.0
- ) -> dict[str, int]:
- """等待真实脉冲恢复,不能只依据 ACCEL/RUN 状态标签。"""
- deadline = time.monotonic() + timeout
- latest: dict[str, int] | None = None
- while time.monotonic() < deadline:
- latest = read_axis_status(client, axis)
- pulse_active = (latest["flags"] & (1 << 1)) != 0
- if (
- latest["last_sequence"] == sequence
- and latest["state"] in {STATE_ACCEL, STATE_RUN}
- and pulse_active
- and latest["current_frequency"] > 0
- ):
- return latest
- raise RuntimeError(f"等待轴{axis}实际脉冲恢复超时,最后状态:{latest}")
-
-
- def print_process_checkpoint(client: RtuClient, label: str) -> None:
- words = client.read_holding(PERFORMANCE_BASE, 4)
- stage_words = client.read_holding(
- STAGE_PERFORMANCE_BASE, STAGE_PERFORMANCE_WORDS
- )
- stages = ", ".join(
- f"{name}={get_u32(stage_words, index * 2)}"
- for index, name in enumerate(STAGE_NAMES)
- )
- print(
- f"P16阶段[{label}]:自身最大={get_u32(words, 0)} cycles,"
- f"响应最大={get_u32(words, 2)} cycles"
- )
- print(f" 分段最大:{stages}")
-
-
- def read_performance_block(client: RtuClient) -> list[int] | None:
- """读取 P16 主统计块;连续两次读取一致才算有效(快照一致性)。"""
- for _ in range(3):
- first = client.read_holding(PERFORMANCE_BASE, 8)
- second = client.read_holding(PERFORMANCE_BASE, 8)
- if first == second:
- return first
- return None
-
-
- def wire_time_ms(baud: int, request_bytes: int, response_bytes: int) -> float:
- """RTU 8E1 线上传输时间:每字符 11 位。"""
- return (request_bytes + response_bytes) * BITS_PER_CHARACTER * 1000.0 / baud
-
-
- def throughput_theory(baud: int) -> float:
- """四轴状态轮询(4×48字,请求 8B + 响应 101B)的波特率理论速率(轮/s)。"""
- bytes_per_poll = 4 * (8 + 101)
- return baud / (bytes_per_poll * BITS_PER_CHARACTER)
-
-
- def sample_round_trips(
- client: RtuClient,
- baud: int,
- actions: list[tuple[str, int, int, object]],
- samples: int,
- ) -> dict[str, tuple[float, float, float, float]]:
- """对每种命令采样往返耗时,返回 {名称: (最小, 中位, 最大, 预算) ms}。"""
- results = {}
- for name, request_bytes, response_bytes, action in actions:
- collected = []
- for _ in range(samples):
- started = time.perf_counter()
- action()
- collected.append((time.perf_counter() - started) * 1000.0)
- ordered = sorted(collected)
- results[name] = (
- ordered[0],
- ordered[len(ordered) // 2],
- ordered[-1],
- wire_time_ms(baud, request_bytes, response_bytes) + PROCESSING_BUDGET_MS,
- )
- return results
-
-
- def build_s0_job() -> list[int]:
- s0 = [0] * 20
- put_u32(s0, 0, 1)
- put_u32(s0, 10, TEST_FREQUENCY_HZ)
- put_u32(s0, 12, TEST_PULSES)
- return s0
-
-
- def prepare_jobs(client: RtuClient) -> None:
- for axis in range(4):
- client.write_multiple(S0_BASES[axis], build_s0_job())
- client.write_multiple(S1_BASES[axis], [0] * 4)
-
-
- def record_metric(name: str, passed: bool, detail: str) -> None:
- """记录一项指标并实时打印 通过/失败 + 实测值。"""
- METRICS.append((name, passed, detail))
- print(f" [{'通过' if passed else '失败'}] {name}:{detail}")
-
-
- def main() -> int:
- parser = argparse.ArgumentParser(
- description="PLSR P16 Modbus 性能统计自动测试"
- "(PLSR_MODBUS_PERFORMANCE_TEST.md)"
- )
- parser.add_argument("--port", help="串口,例如 COM5;只有一个串口时可省略")
- parser.add_argument("--baud", type=int, default=9600)
- parser.add_argument("--slave", type=int, default=1)
- parser.add_argument(
- "--polls",
- type=int,
- default=10,
- help="静态吞吐测试的完整四轴轮询轮数(默认 10)",
- )
- 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"P16 控制窗口未就绪:{header};请烧录当前固件并复位"
- )
- print("P16 控制窗口就绪:D1200~D1537,协议 V1.0")
-
- # ---- 指标1:版本号一致性(32位CPU时钟 + 16位性能版本 + 统计块快照) ----
- core_clock_hz = get_u32(header, 5)
- performance_version = header[7]
- baseline_block = read_performance_block(client)
- version_ok = (
- core_clock_hz != 0
- and performance_version == PERFORMANCE_VERSION
- and baseline_block is not None
- )
- consistency_text = (
- "一致" if baseline_block is not None else "连续三次读取不一致"
- )
- record_metric(
- "版本号一致性",
- version_ok,
- f"协议版本={header[2]:#06x},32位CPU计时频率(D1205~D1206)="
- f"{core_clock_hz}Hz,性能统计版本(D1207)=V{performance_version},"
- f"P16统计块两次读取{consistency_text}",
- )
- if not version_ok:
- raise RuntimeError("版本号一致性检查失败,后续指标失去判定基准")
- print_process_checkpoint(client, "复位后基线")
- print("P16 已就绪:四轴 PULSE/DIR,100kHz,200000脉冲/轴")
-
- # ---- 指标2:单条命令响应时间 ----
- s0_job = build_s0_job()
- actions = [
- (
- "0x03读取控制头(8字)",
- 8,
- 21,
- lambda: client.read_holding(CONTROL_BASE, 8),
- ),
- (
- "0x10写入S0(20字)",
- 49,
- 8,
- lambda: client.write_multiple(S0_BASES[0], s0_job),
- ),
- (
- "0x10写入S1(4字)",
- 17,
- 8,
- lambda: client.write_multiple(S1_BASES[0], [0] * 4),
- ),
- ]
- latency = sample_round_trips(client, args.baud, actions, LATENCY_SAMPLES)
- latency_ok = True
- latency_parts = []
- for name, (minimum, median, maximum, budget) in latency.items():
- latency_ok = latency_ok and median <= budget
- latency_parts.append(
- f"{name} 中位{median:.1f}ms"
- f"(最小{minimum:.1f}/最大{maximum:.1f},预算{budget:.1f}ms)"
- )
- record_metric("单条命令响应时间", latency_ok, ";".join(latency_parts))
-
- # ---- 指标3:连续状态读取吞吐(静态轮询,无运行压力) ----
- static_started = time.monotonic()
- for _ in range(args.polls):
- for axis in range(4):
- read_axis_status(client, axis)
- static_elapsed = time.monotonic() - static_started
- static_polls = args.polls * 4
- static_rate = static_polls / static_elapsed
- theory = throughput_theory(args.baud)
- ratio = static_rate / theory
- record_metric(
- "连续状态读取吞吐",
- ratio >= THROUGHPUT_MIN_RATIO,
- f"静态四轴轮询 {static_polls} 次耗时 {static_elapsed:.3f}s,"
- f"速率 {static_rate:.2f} 轮/s,波特率理论上限 {theory:.2f} 轮/s"
- f"(占比 {ratio:.0%})",
- )
-
- prepare_jobs(client)
- sequence = 100
- for axis in range(4):
- response = send_command(
- client, sequence, axis, CMD_SET_POSITION, argument=0
- )
- check_result(response, 4, RESULT_QUEUED, f"轴{axis} SET_POSITION")
- sequence += 1
- print("四轴位置已清零,S0/S1 已用 0x10 原子写入")
- print_process_checkpoint(client, "位置清零")
-
- for axis in range(4):
- response = send_call(client, sequence, axis, CALL_COMMIT)
- check_result(response, 3, RESULT_OK, f"轴{axis} COMMIT")
- if response[11] != 1:
- raise RuntimeError(f"轴{axis} COMMIT 未建立有效快照")
- sequence += 1
- print("四轴 COMMIT 校验通过")
- print_process_checkpoint(client, "COMMIT")
-
- physical_baseline = [
- read_axis_status(client, axis)["physical_pulses"]
- for axis in range(4)
- ]
-
- started = time.monotonic()
- for axis in range(4):
- response = send_call(client, sequence, axis, CALL_START)
- check_result(response, 3, RESULT_QUEUED, f"轴{axis} START")
- sequence += 1
- print("四轴 START 已排队;持续读取状态以施加 Modbus/任务并发压力")
- print_process_checkpoint(client, "START")
-
- running_status = [read_axis_status(client, axis) for axis in range(4)]
- for axis, status in enumerate(running_status):
- if status["state"] not in {STATE_ACCEL, STATE_RUN}:
- raise RuntimeError(f"轴{axis} 未进入运行态:{status}")
- expected_mode = 1 if axis < 2 else 0
- if status["counter_mode"] != expected_mode:
- raise RuntimeError(
- f"轴{axis}计数模式={status['counter_mode']},期望={expected_mode}"
- )
- if status["current_frequency"] != TEST_FREQUENCY_HZ:
- raise RuntimeError(f"轴{axis}频率不正确:{status}")
- print("运行期计数租约正确:Q0/Q1=硬件,Q2/Q3=软件回退")
- print_process_checkpoint(client, "进入运行态")
-
- # ---- 指标4:重复序号幂等(轴0,运行期命令处理) ----
- pause_sequence = sequence
- response = send_command(client, pause_sequence, 0, CMD_PAUSE)
- check_result(response, 4, RESULT_QUEUED, f"轴0 PAUSE#{pause_sequence}")
- status = wait_status(client, 0, {STATE_PAUSED}, sequence=pause_sequence)
- # 重发完全相同的请求:必须只回放既有应答、不得重复执行
- replay = send_command(client, pause_sequence, 0, CMD_PAUSE)
- status_again = read_axis_status(client, 0)
- idem_ok = (
- replay == response
- and status_again["state"] == STATE_PAUSED
- and status_again["last_sequence"] == pause_sequence
- )
- record_metric(
- "重复序号幂等",
- idem_ok,
- f"重发 PAUSE#{pause_sequence}:应答与首次完全一致={replay == response},"
- f"状态保持暂停,last_sequence={status_again['last_sequence']}"
- f"(期望 {pause_sequence})",
- )
- sequence += 1
- response = send_command(client, sequence, 0, CMD_RESUME)
- check_result(response, 4, RESULT_QUEUED, f"轴0 RESUME#{sequence}")
- status = wait_running_output(client, 0, sequence=sequence)
- resumed_pulses = status["task_pulses"]
- time.sleep(0.5)
- status = read_axis_status(client, 0)
- if status["task_pulses"] <= resumed_pulses:
- raise RuntimeError(f"RESUME#{sequence} 后轴0脉冲计数未增长:{status}")
- print(
- f"轴0 PAUSE/RESUME 完成:恢复输出,当前 {status['current_frequency']}Hz"
- )
- print_process_checkpoint(client, "暂停/恢复")
-
- # ---- 指标5:并发读写压力模拟(运行期持续轮询 + 幂等写入) ----
- loop_started = time.monotonic()
- deadline = loop_started + RUN_DEADLINE_SECONDS
- polls = 0
- writes = 0
- final_status: list[dict[str, int]] = running_status
- while time.monotonic() < deadline:
- final_status = [read_axis_status(client, axis) for axis in range(4)]
- polls += 4
- # 写压力:重写与 prepare_jobs 完全相同的 S0/S1(幂等,
- # 不影响 COMMIT 后已建立的运行快照)
- client.write_multiple(S0_BASES[0], s0_job)
- client.write_multiple(S1_BASES[0], [0] * 4)
- # 并发下验证 P16 统计块可正常读取
- client.read_holding(PERFORMANCE_BASE, 8)
- writes += 2
- if all(item["state"] == STATE_COMPLETED for item in final_status):
- break
- else:
- raise RuntimeError(f"等待四轴完成超时,最后状态:{final_status}")
- run_elapsed = time.monotonic() - loop_started
-
- # 并发压力下四轴计数精确性核对
- count_errors: list[str] = []
- for axis, status in enumerate(final_status):
- expected = {
- "logical_position": TEST_PULSES,
- "task_pulses": TEST_PULSES,
- "error": 0,
- "last_result": RESULT_OK,
- }
- bad = {
- key: (status[key], value)
- for key, value in expected.items()
- if status[key] != value
- }
- physical_delta = status["physical_pulses"] - physical_baseline[axis]
- if physical_delta != TEST_PULSES:
- bad["physical_pulses_delta"] = (physical_delta, TEST_PULSES)
- if bad:
- count_errors.append(f"轴{axis}: {bad}")
- record_metric(
- "并发读写压力",
- not count_errors,
- f"四轴100kHz运行期间读取状态 {polls} 次、穿插幂等写入 {writes} 次,"
- f"轮询吞吐 {polls / run_elapsed:.2f} 次/s;四轴计数均精确为 "
- f"{TEST_PULSES}、error=0"
- + (f";异常:{';'.join(count_errors)}" if count_errors else ""),
- )
-
- elapsed = time.monotonic() - started
- print_process_checkpoint(client, "运行完成")
-
- # ---- 指标6:P16 统计块快照一致性(运行后,含基线单调性) ----
- block = read_performance_block(client)
- if block is None:
- record_metric(
- "P16统计块快照一致性", False, "三次读取未得到连续一致结果"
- )
- else:
- regressions = [
- f"D{PERFORMANCE_BASE + offset}: {baseline_block[offset]}→{block[offset]}"
- for offset in range(len(block))
- if block[offset] < baseline_block[offset]
- ]
- detail = "连续两次读取完全一致(32位周期数与16位饱和字段)"
- if regressions:
- detail += ";但相对复位后基线发生回退:" + "、".join(regressions)
- record_metric("P16统计块快照一致性", not regressions, detail)
-
- # ---- 指标7~10:P16 自动预算 ----
- stage_performance = client.read_holding(
- STAGE_PERFORMANCE_BASE, STAGE_PERFORMANCE_WORDS
- )
- ab_gate_cycles = get_u32(
- client.read_holding(AB_GATE_PERFORMANCE_BASE, 2), 0
- )
- cycle_values: dict[str, int] = {}
- if block is not None:
- cycle_values = {
- "PlsrProcess自身": get_u32(block, 0),
- "PlsrProcess响应": get_u32(block, 2),
- "TIM6控制ISR": get_u32(block, 4),
- "输出定时器ISR": block[6],
- "TIM9/12计数ISR": block[7],
- }
- print("P16 DWT最坏执行时间:")
- for name, cycles in cycle_values.items():
- microseconds = cycles * 1_000_000.0 / core_clock_hz
- print(f" {name:<16} {cycles:8d} cycles {microseconds:8.3f}us")
- print("P16 PlsrProcess分段最大执行时间(各段独立峰值,来自不同轮次不能相加):")
- for index, name in enumerate(STAGE_NAMES):
- cycles = get_u32(stage_performance, index * 2)
- microseconds = cycles * 1_000_000.0 / core_clock_hz
- print(f" {name:<18} {cycles:8d} cycles {microseconds:8.3f}us")
- print(
- " AB末周期快速门控 "
- f"{ab_gate_cycles:8d} cycles "
- f"{ab_gate_cycles * 1_000_000.0 / core_clock_hz:8.3f}us "
- "(本PULSE/DIR用例未执行时允许为0)"
- )
-
- budgets = {
- "PlsrProcess自身": (core_clock_hz // 1_000, "1ms"),
- "TIM6控制ISR": (core_clock_hz // 10_000, "0.1ms"),
- "输出定时器ISR": (core_clock_hz // TEST_FREQUENCY_HZ, "10us"),
- "TIM9/12计数ISR": (core_clock_hz // TEST_FREQUENCY_HZ, "10us"),
- }
- for name, (budget, budget_text) in budgets.items():
- if block is None:
- record_metric(f"P16 {name}预算", False, "统计块不可用,无法判定")
- continue
- cycles = cycle_values[name]
- microseconds = cycles * 1_000_000.0 / core_clock_hz
- passed = cycles != 0 and cycles < budget
- detail = (
- f"实测 {cycles} cycles = {microseconds:.3f}us,"
- f"预算 < {budget} cycles({budget_text})"
- )
- if cycles == 0:
- detail += ";数值为 0,路径未被实际执行"
- record_metric(f"P16 {name}预算", passed, detail)
-
- # 墙钟响应与阶段峰值超过 1ms 时仅给提示(规格:不与自身 CPU 混算)
- if block is not None:
- response_cycles = cycle_values["PlsrProcess响应"]
- if response_cycles >= core_clock_hz // 1_000:
- print(
- "提示:PlsrProcess墙钟响应超过1ms,但自身CPU执行时间达标;"
- "差值来自高优先级PLSR定时器中断抢占。"
- )
- for index, name in enumerate(STAGE_NAMES):
- cycles = get_u32(stage_performance, index * 2)
- if cycles >= core_clock_hz // 1_000:
- print(
- f"提示:阶段“{name}”峰值超过1ms,需定位峰值路径;"
- "各段最大值来自不同轮次,不能直接相加。"
- )
-
- # ---- 汇总 ----
- print("\n性能测试指标汇总:")
- passed_count = sum(1 for _, passed, _ in METRICS if passed)
- for name, passed, detail in METRICS:
- print(f" [{'通过' if passed else '失败'}] {name}:{detail}")
- print(f"最终判定:{passed_count}/{len(METRICS)} 项通过")
- failed = [name for name, passed, _ in METRICS if not passed]
- if failed:
- raise RuntimeError("未通过指标:" + "、".join(failed))
- print(
- f"全部 PASS:四轴均为 {TEST_PULSES} 脉冲,耗时 {elapsed:.3f}s,"
- f"运行期状态读取 {polls} 次"
- )
- print("请再核对逻辑分析仪:Q0~Q3 各200000个上升沿、100kHz、无窄脉冲。")
- return 0
-
-
- if __name__ == "__main__":
- try:
- raise SystemExit(main())
- except (RuntimeError, serial.SerialException) as error:
- print(f"测试失败:{error}")
- raise SystemExit(1)
|