Control charts show whether your test process is stable or drifting out of control. They're the foundation of Statistical Process Control (SPC) and let you catch problems before they cause failures.
What Control Charts Show
A control chart plots individual measurements (or subgroup statistics) over time against three lines: a center line (process mean), an Upper Control Limit (UCL), and a Lower Control Limit (LCL). These limits are calculated from your actual data, typically at 3-sigma from the mean.
Points inside the control limits mean your process is behaving normally. Points outside, or patterns like seven consecutive points on one side of the center line, signal something has changed.
Control Limits vs. Spec Limits
This distinction matters. Spec limits (USL/LSL) define what's acceptable for the product. Control limits (UCL/LCL) describe what your process actually does.
A process can be in control but out of spec (consistently producing bad parts). It can also be in spec but out of control (passing today, but unpredictably). Control charts catch the second case, which spec limits alone miss.
Writing Tests That Feed Control Charts
Good control chart data starts with well-structured measurements. Each measurement needs a name, a unit, and consistent conditions across runs.
import openhtf as htf
from openhtf.util import units
from tofupilot.openhtf import TofuPilot
@htf.measures(
htf.Measurement("supply_voltage")
.with_units(units.VOLT)
.in_range(minimum=4.75, maximum=5.25),
htf.Measurement("supply_current")
.with_units(units.AMPERE)
.in_range(minimum=0.090, maximum=0.110),
htf.Measurement("output_frequency")
.with_units(units.HERTZ)
.in_range(minimum=999.5, maximum=1000.5),
)
def power_supply_check(test):
test.measurements.supply_voltage = 5.02
test.measurements.supply_current = 0.0987
test.measurements.output_frequency = 1000.1
@htf.measures(
htf.Measurement("signal_amplitude")
.in_range(minimum=-1.0, maximum=1.0),
htf.Measurement("signal_noise_floor")
.in_range(maximum=-60.0),
)
def signal_integrity_check(test):
test.measurements.signal_amplitude = 0.3
test.measurements.signal_noise_floor = -72.5
def main():
test = htf.Test(power_supply_check, signal_integrity_check)
with TofuPilot(test):
test.execute(test_start=lambda: "PCB-001")
if __name__ == "__main__":
main()Each run uploads measurements to TofuPilot. Over time, these build the dataset that control charts need.
Choosing the Right Chart Type
For individual measurements (one value per run), use an I-MR chart (Individual and Moving Range). This is the most common case in electronics testing where each DUT produces one reading per measurement.
For subgrouped data (multiple samples per batch), X-bar and R charts track the subgroup mean and range. This applies when you test multiple units from the same production lot and want to monitor lot-to-lot variation.
Reading Control Charts in TofuPilot
TofuPilot's measurement analytics automatically generates control charts from your test data. Open any measurement's detail view to see the time-series plot with calculated control limits.
Watch for these signals:
- A single point beyond UCL or LCL
- Seven or more consecutive points above or below the center line
- Six or more consecutive points trending in one direction
- Alternating patterns (up-down-up-down) suggesting measurement system issues
When you spot any of these, investigate the root cause before the process produces out-of-spec parts.
Keeping Charts Useful
Recalculate control limits periodically, especially after process changes. If you've fixed a root cause and the process has genuinely improved, update the baseline. Stale limits hide real shifts and generate false alarms.
Group your charts by test station. Mixing data from different stations, fixtures, or environments inflates variation and makes the charts less sensitive to real changes on any single station.