Implement TempCo characterisation test
This commit is contained in:
243
src/py_dvt_ate/tests/thermal/tempco.py
Normal file
243
src/py_dvt_ate/tests/thermal/tempco.py
Normal 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
|
||||
Reference in New Issue
Block a user