feat: add ALIGNMENT group tests (frequency calibration + position sweep)

This commit is contained in:
2026-08-07 14:19:43 +08:00
parent 1f11c0b832
commit 9d7c350c21
+241
View File
@@ -0,0 +1,241 @@
import csv
import time
from dataclasses import dataclass
from pathlib import Path
import pytest
from common.config_loader import load_jig_registers
from drivers.jig import Jig, Position
from drivers.rpu import Rpu
from drivers.tpu import Tpu
RESULTS_DIR = Path(__file__).resolve().parent.parent / "results"
DEFAULT_POSITION = Position(0, 0, 0)
CLOSE_BORDER_POSITION = Position(0, 0, 15)
FAR_BORDER_POSITION = Position(30, 10, 60)
SWEEP_POSITIONS = [
Position(0, 0, 5), Position(0, 0, 10), Position(0, 0, 15), Position(0, 0, 20),
Position(30, 10, 15), Position(60, 10, 15), Position(80, 10, 15), Position(90, 10, 15),
Position(20, 10, 50), Position(30, 10, 50), Position(0, 0, 55), Position(10, 10, 55),
Position(30, 10, 55), Position(30, 10, 60), Position(30, 10, 65), Position(0, 0, 80),
]
DEFAULT_MOVE_WAIT_S = 5
POSITION_MOVE_WAIT_S = 5
START_CHARGING_WAIT_S = 1
RAMP_UP_WAIT_S = 8
RAMP_DOWN_WAIT_S = 3
FREQ_CAL_TIMEOUT_S = 30
FREQ_CAL_POLL_INTERVAL_S = 1
TARGET_CURRENT_A = 30.0
ALIGNMENT_RAMP_MA_PER_SEC = 20000
# "BLE Conn mode" (TPU: Debug dataset addr 5, RPU: sec_flash addr 142) selects
# which frequency-calibration handshake the firmware expects
# (global_variables.h: typedef enum _BLE_MODE { BLE_DEFAULT, BLE_RECONN, ... }).
BLE_MODE_DEFAULT = 0
BLE_MODE_RECONN = 1
# TPU firmware resets PASSWORD_LO/HI back to 0 as soon as it recognizes the
# combined password (main.c: check_debug_test_mode() does
# `*main_app.pops->pass_code = 0` right after matching) - verifying by
# read-back would race against that reset, so these writes use verify=False.
TPU_DEBUG_PASSWORD_LO = 0xBEEF
TPU_DEBUG_PASSWORD_HI = 0xFEED
RPU_DEBUG_PASSWORD_LO = 0xDAAD
RPU_DEBUG_PASSWORD_HI = 0xCBAD
RPU_NORMAL_PASSWORD_LO = 0xFEED
RPU_NORMAL_PASSWORD_HI = 0xCEE5
@dataclass
class AlignmentContext:
jig: Jig
tpu: Tpu
rpu: Rpu
@pytest.fixture(scope="module")
def alignment(jig_port, tpu_port, rpu_port):
jig_registers = load_jig_registers()
jig = Jig(jig_port, jig_registers)
tpu = Tpu(tpu_port, extra_datasets=["frequency_calibration"])
rpu = Rpu(rpu_port)
ctx = AlignmentContext(jig=jig, tpu=tpu, rpu=rpu)
rpu.set_charging(False)
rpu.wait_until_config_writable()
# Alignment sweep needs Iout to ramp up fast enough to settle within the
# per-position wait window - bump both ramp rates for the duration of
# this file, then restore the original values on teardown.
default_ramp_control = rpu.read("Ramp_control_mA_per_sec")
default_ramp_slow = rpu.read("Ramp_slow_mA_per_sec")
rpu.write("Ramp_control_mA_per_sec", ALIGNMENT_RAMP_MA_PER_SEC)
rpu.write("Ramp_slow_mA_per_sec", ALIGNMENT_RAMP_MA_PER_SEC)
rpu.rewrite_config()
try:
yield ctx
finally:
rpu.set_charging(False)
jig.move_to_position(DEFAULT_POSITION, wait_time=DEFAULT_MOVE_WAIT_S)
rpu.set_charging(False)
rpu.wait_until_config_writable()
rpu.write("Ramp_control_mA_per_sec", default_ramp_control)
rpu.write("Ramp_slow_mA_per_sec", default_ramp_slow)
rpu.rewrite_config()
jig.close()
tpu.close()
rpu.close()
def _wait_for_freq_cal_done(tpu, position_name, timeout_s=FREQ_CAL_TIMEOUT_S):
deadline = time.time() + timeout_s
while time.time() < deadline:
if tpu.read("FREQ_CAL_DONE"):
error_code = tpu.read("FREQ_CAL_ERROR_CODE")
assert error_code == 0, f"[{position_name}] frequency calibration failed, error code {error_code}"
return
time.sleep(FREQ_CAL_POLL_INTERVAL_S)
pytest.fail(f"[{position_name}] frequency calibration timed out after {timeout_s}s")
def _run_freq_cal_step(tpu, cmd, position_name, ble_mode):
if ble_mode == BLE_MODE_DEFAULT:
# Method 1 (BLE mode 0): TPU and RPU acknowledge each other directly
# over the wireless link - no host-driven ACK handshake needed.
tpu.write("FREQ_CAL_CMD", cmd)
else:
# Method 2 (BLE mode 1): host must drive the FREQ_CAL_CMD_ACK
# handshake itself (self-clearing coil, confirmed in
# freq_calibration.c: "*freq_cal.cal_modbus_ack = false").
tpu.write("FREQ_CAL_CMD_ACK", True, verify=False)
time.sleep(0.1)
tpu.write("FREQ_CAL_CMD", cmd)
time.sleep(0.1)
tpu.write("FREQ_CAL_CMD_ACK", True, verify=False)
time.sleep(0.1)
_wait_for_freq_cal_done(tpu, position_name)
def test_frequency_calibration(alignment):
jig = alignment.jig
tpu = alignment.tpu
rpu = alignment.rpu
tpu_ble_mode = tpu.read("BLE Conn mode")
rpu_ble_mode = rpu.read("BLE Conn mode")
if tpu_ble_mode != rpu_ble_mode:
pytest.fail(
f"TPU/RPU BLE Conn mode mismatch (TPU={tpu_ble_mode}, RPU={rpu_ble_mode}) - "
f"both must be the same mode (0=BLE_DEFAULT or 1=BLE_RECONN) to calibrate"
)
ble_mode = tpu_ble_mode
rpu.set_charging(False)
jig.move_to_position(DEFAULT_POSITION, wait_time=DEFAULT_MOVE_WAIT_S)
if ble_mode == BLE_MODE_RECONN:
# Enter RPU debug / force-frequency-calibration mode (Method 2 only).
rpu.write("PASSWORD_LO", RPU_DEBUG_PASSWORD_LO)
rpu.write("PASSWORD_HI", RPU_DEBUG_PASSWORD_HI)
time.sleep(0.1)
rpu.write("FORCE_FREQ_CAL", True)
time.sleep(0.1)
try:
jig.move_to_position(CLOSE_BORDER_POSITION, wait_time=POSITION_MOVE_WAIT_S)
_run_freq_cal_step(tpu, 1, "close-border", ble_mode)
jig.move_to_position(FAR_BORDER_POSITION, wait_time=POSITION_MOVE_WAIT_S)
_run_freq_cal_step(tpu, 2, "far-border", ble_mode)
finally:
if ble_mode == BLE_MODE_RECONN:
rpu.write("FORCE_FREQ_CAL", False)
time.sleep(0.1)
rpu.write("PASSWORD_LO", RPU_NORMAL_PASSWORD_LO)
rpu.write("PASSWORD_HI", RPU_NORMAL_PASSWORD_HI)
time.sleep(0.1)
freq_low = tpu.read("FREQ_CAL_RESULT_LOW")
freq_high = tpu.read("FREQ_CAL_RESULT_HIGH")
# Commit the calibrated frequencies: TPU needs its own debug-mode
# password first (combined 0xFEEDBEEF -> LO=0xBEEF, HI=0xFEED).
tpu.write("PASSWORD_LO", TPU_DEBUG_PASSWORD_LO, verify=False)
tpu.write("PASSWORD_HI", TPU_DEBUG_PASSWORD_HI, verify=False)
time.sleep(0.1)
tpu.write("Coupling_freq_1", freq_low)
tpu.write("Coupling_freq_2", freq_high)
time.sleep(0.1)
tpu.rewrite_config()
jig.move_to_position(DEFAULT_POSITION, wait_time=DEFAULT_MOVE_WAIT_S)
print(f"Frequency calibration done: f1 (close-border) = {freq_low:.2f} kHz, "
f"f2 (far-border) = {freq_high:.2f} kHz")
def test_alignment_sweep(alignment):
jig = alignment.jig
tpu = alignment.tpu
rpu = alignment.rpu
RESULTS_DIR.mkdir(exist_ok=True)
csv_path = RESULTS_DIR / f"alignment_sweep_{time.strftime('%Y%m%d_%H%M%S')}.csv"
rpu.set_charging(False)
jig.move_to_position(DEFAULT_POSITION, wait_time=DEFAULT_MOVE_WAIT_S)
try:
with open(csv_path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow([
"position_index", "x", "y", "z", "iref",
"pfc_vout", "pfc_il_peak", "inv_iout_peak",
"coupling_phase_1", "coupling_phase_2", "rpu_pi_i_out",
"coil_vac_out", "coil_vac_out_est",
"tpu_uart_lost_rate", "tpu_ble_rssi", "tpu_shadow_error_code",
"rpu_output_current", "rpu_dc_batt_voltage",
"rpu_ble_uart_lost_rate", "rpu_ble_rssi", "rpu_shadow_error_code",
])
for index, pos in enumerate(SWEEP_POSITIONS, start=1):
jig.move_to_position(pos, wait_time=POSITION_MOVE_WAIT_S)
rpu.set_charging(True)
time.sleep(START_CHARGING_WAIT_S)
rpu.set_runtime_current(TARGET_CURRENT_A)
time.sleep(RAMP_UP_WAIT_S)
actual_pos = jig.get_current_position()
writer.writerow([
index, actual_pos.x, actual_pos.y, actual_pos.z, TARGET_CURRENT_A,
tpu.read("PFC_Vout"), tpu.read("PFC_iL_peak"), tpu.read("Inv_Iout_peak"),
tpu.read("coupling phase 1") / 10.0, tpu.read("coupling phase 2") / 10.0,
tpu.read("RPU_PI_I_OUT") / 100.0,
tpu.read("Coil_VAC_out"), tpu.read("Coil_VAC_out_est"),
tpu.read("UART_Lost_rate"), tpu.read("BLE_RSSI"), hex(tpu.read_shadow_error_code()),
rpu.read("Output_current_Q128") / rpu.Q128_DIVISOR,
rpu.read("DC_Batt_voltage_Q128") / rpu.Q128_DIVISOR,
rpu.read("BLE_UART_Lost_rate"), rpu.read("BLE_RSSI"), hex(rpu.read_shadow_error_code()),
])
f.flush()
rpu.set_charging(False)
time.sleep(RAMP_DOWN_WAIT_S)
finally:
rpu.set_charging(False)
jig.move_to_position(DEFAULT_POSITION, wait_time=DEFAULT_MOVE_WAIT_S)
print(f"Saved to {csv_path}")