Implement TempCo characterisation test

This commit is contained in:
2025-09-15 18:32:58 +00:00
parent 8516101514
commit 01c2d89c1b

View File

@@ -0,0 +1,243 @@
"""Temperature Coefficient (TempCo) characterisation test.
This test characterises the output voltage temperature coefficient by
sweeping the chamber temperature and measuring output voltage at each point.
The TempCo is calculated from the linear regression slope and expressed
in parts per million per degree Celsius (ppm/°C).
"""
from py_dvt_ate.data.models import TestStatus
from py_dvt_ate.framework.context import TestContext
from py_dvt_ate.tests.base import BaseDVTTest
class TempCoTest(BaseDVTTest):
"""Temperature coefficient characterisation test.
Measures how output voltage varies with temperature. This is a critical
parameter for voltage regulators, as it indicates stability across
the operating temperature range.
Test Procedure:
1. Configure DUT supply voltage and load current
2. Sweep chamber temperature from min to max
3. At each temperature point:
- Wait for thermal stability
- Measure output voltage (averaged)
- Log measurement with conditions
4. Calculate TempCo from linear regression
5. Evaluate against specification limits
Configuration:
temperatures: List of temperature points (°C). Default: [-40, -20, 0, 25, 50, 85]
input_voltage: DUT input voltage (V). Default: 5.0
load_current: DUT load current (A). Default: 0.1
settle_time: Additional settling time at each temp (s). Default: 5.0
num_samples: Number of measurements to average per point. Default: 5
tempco_limit: Maximum allowed TempCo magnitude (ppm/°C). Default: ±50.0
"""
@property
def name(self) -> str:
"""Return test identifier."""
return "tempco"
@property
def description(self) -> str:
"""Return test description."""
return "Output voltage temperature coefficient"
def execute(self, context: TestContext) -> TestStatus:
"""Execute TempCo characterisation test.
Args:
context: Test context with instruments, logger, and configuration.
Returns:
PASSED if TempCo is within limits, FAILED otherwise.
ERROR if a critical failure occurs.
"""
try:
# Get configuration
config = context.config
temperatures = config.get("temperatures", [-40.0, -20.0, 0.0, 25.0, 50.0, 85.0])
input_voltage = config.get("input_voltage", 5.0)
load_current = config.get("load_current", 0.1)
settle_time = config.get("settle_time", 5.0)
num_samples = config.get("num_samples", 5)
tempco_limit = config.get("tempco_limit", 50.0)
context.logger.log_event(
f"Starting TempCo test: {len(temperatures)} temperature points, "
f"Vin={input_voltage}V, Iload={load_current}A",
level="INFO",
)
# Configure DUT power
context.logger.log_event(
f"Configuring PSU: Vin={input_voltage}V, Ilimit={load_current + 0.5}A",
level="INFO",
)
psu = context.instruments.psu
psu.set_voltage(1, input_voltage)
psu.set_current_limit(1, load_current + 0.5) # Add headroom
psu.enable_output(1, True)
# Storage for measurements
temp_points: list[float] = []
vout_points: list[float] = []
# Temperature sweep
for temp_setpoint in temperatures:
context.logger.log_event(
f"Temperature point: {temp_setpoint}°C",
level="INFO",
)
# Wait for thermal stability
stable = self.wait_for_temperature(
context,
temp_setpoint,
timeout=300.0,
)
if not stable:
context.logger.log_event(
f"Warning: Temperature did not stabilise at {temp_setpoint}°C",
level="WARNING",
)
# Additional settling for DUT junction temperature
self.thermal_settle(context, settle_time)
# Measure output voltage (averaged)
actual_temp = context.instruments.chamber.get_temperature()
def measure_vout() -> float:
return context.instruments.dmm.measure_dc_voltage()
vout_mean, vout_std = self.measure_averaged(
measure_vout,
num_samples=num_samples,
)
# Log individual measurement
context.logger.log_measurement(
parameter="v_out",
value=vout_mean,
unit="V",
conditions={
"temperature": actual_temp,
"input_voltage": input_voltage,
"load_current": load_current,
},
)
context.logger.log_event(
f"Measured Vout = {vout_mean:.6f}V ± {vout_std * 1e6:.1f}μV "
f"at T={actual_temp:.2f}°C",
level="INFO",
)
# Store for TempCo calculation
temp_points.append(actual_temp)
vout_points.append(vout_mean)
# Calculate TempCo from linear regression
tempco_ppm = self._calculate_tempco(temp_points, vout_points)
context.logger.log_event(
f"Calculated TempCo = {tempco_ppm:.2f} ppm/°C",
level="INFO",
)
# Log result with limits
context.logger.log_result(
parameter="temp_co",
value=tempco_ppm,
unit="ppm/°C",
lower_limit=-abs(tempco_limit),
upper_limit=abs(tempco_limit),
)
# Evaluate pass/fail
passed = abs(tempco_ppm) <= tempco_limit
if passed:
context.logger.log_event(
f"TempCo test PASSED: {tempco_ppm:.2f} ppm/°C within ±{tempco_limit} ppm/°C",
level="INFO",
)
return TestStatus.PASSED
else:
context.logger.log_event(
f"TempCo test FAILED: {tempco_ppm:.2f} ppm/°C exceeds ±{tempco_limit} ppm/°C",
level="ERROR",
)
return TestStatus.FAILED
except Exception as e:
context.logger.log_event(
f"TempCo test ERROR: {e!s}",
level="ERROR",
)
return TestStatus.ERROR
finally:
# Cleanup: disable PSU output
try:
context.instruments.psu.enable_output(1, False)
context.logger.log_event("PSU output disabled", level="INFO")
except Exception:
pass # Best effort cleanup
def _calculate_tempco(
self,
temperatures: list[float],
voltages: list[float],
) -> float:
"""Calculate temperature coefficient from measurements.
Uses linear regression to find the slope (dV/dT), then converts
to ppm/°C relative to the nominal voltage (voltage at median temperature).
Args:
temperatures: Temperature measurements in °C.
voltages: Output voltage measurements in V.
Returns:
Temperature coefficient in ppm/°C.
Raises:
ValueError: If insufficient data points.
"""
if len(temperatures) < 2 or len(temperatures) != len(voltages):
raise ValueError("Need at least 2 matching temperature-voltage pairs")
n = len(temperatures)
# Linear regression: V = a + b*T
# We want slope b = dV/dT
mean_t = sum(temperatures) / n
mean_v = sum(voltages) / n
# Covariance and variance
cov = sum(
(t - mean_t) * (v - mean_v)
for t, v in zip(temperatures, voltages, strict=True)
)
var_t = sum((t - mean_t) ** 2 for t in temperatures)
if var_t == 0:
raise ValueError("Temperature variance is zero (all temps identical)")
slope = cov / var_t # dV/dT in V/°C
# Find nominal voltage (voltage at median temperature)
sorted_pairs = sorted(zip(temperatures, voltages, strict=True))
mid_idx = len(sorted_pairs) // 2
v_nominal = sorted_pairs[mid_idx][1]
# Convert to ppm/°C: (dV/dT) / V_nom * 10^6
tempco_ppm = (slope / v_nominal) * 1e6
return tempco_ppm