Merge branch 'dev/alignment-test'
This commit is contained in:
@@ -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}")
|
||||||
Reference in New Issue
Block a user