PCBA & Functional Testing

Screen Solder Joints Without Destroying Them

Learn how to screen solder joints on every unit with a 4-wire Kelvin voltage drop at moderate current, compared with a golden-unit baseline.

JJulien Buteau
intermediate10 min readOctober 27, 2026

A power solder joint is a resistance of a fraction of a milliohm, and a defective one is a few milliohms. Learn how to measure that difference on every unit with a 4-wire voltage drop at moderate current, compare it with a golden-unit baseline, and record each joint as a measurement with limits in a TofuPilot procedure.

Why Measure the Voltage Drop

A solder joint carrying current I with resistance R drops a voltage V = I × R. The method is to force a known current through the joint and measure that voltage directly across it. With example values for a heavy wire soldered to an ESC pad:

JointResistance (example)Drop at 4 AHeat in joint at 4 A
Good0.5 mΩ2 mV8 mW
Marginal1.5 mΩ6 mV24 mW
Cracked or cold5 mΩ20 mV80 mW

The drop is computed from V = I × R and the heat from P = I² × R. Two properties make this current level suitable for production:

  • The signal is large. 2 mV against 20 mV is a factor of ten, far above the resolution of a precise differential voltmeter.
  • The test does not change the joint. A few tens of milliwatts produce no measurable heating, so the measurement is non-destructive and repeatable.

An INA228 power monitor, used as a differential voltmeter on its shunt input, has a 20-bit ADC with a 312.5 nV LSB on the ±163.84 mV range. A 2 mV drop is 6,400 LSB. A bench 6.5-digit DMM with offset compensation is an alternative for low volume.

Why Not High-Current Burn-In

Driving the full flight current, or more, through the joints to make weak ones fail is sometimes proposed as a screen. It is not industrial practice:

ProblemExplanation
DestructiveAt 100 A, a good 0.5 mΩ joint dissipates 5 W and a cracked 5 mΩ joint 50 W. Every unit takes a thermal stress cycle that consumes part of its life.
UnsafeA 100 A source, hot joints and possible arcing on an open bench
Blind to cracks in compressionA crack held closed by the fixture conducts and passes
No dataThe result is "survived" or "burnt", with no value to trend or compare

The non-destructive measurement detects the defect from its resistance, without stressing the joint. Mechanical and thermal stress belong in a controlled environmental stress screening (ESS) step, followed by a repeat of the same measurement.

Prerequisites

  • The TofuPilot CLI installed on the test station
  • A Kelvin fixture: a constant-current source (a few amps), a relay or MOSFET multiplexer to select the joint, and a differential voltmeter such as an INA228, controlled by a microcontroller over a serial port
  • One golden unit, a unit whose joints have been verified by cross-section or by long-term field data, and a set of known-good production units
  • Each joint's lead reachable by the fixture: test before the motors and the battery connector are fitted, or through mating connectors

Step 1: Build the Kelvin Fixture

Kelvin probing uses two separate pairs of contacts. One pair carries the current (force), the other measures the voltage (sense). The sense pair carries almost no current, so the contact resistance of the sense pins does not appear in the reading.

For a wire soldered to a pad:

ContactPosition
Force +On the free end of the wire, through a clamp or the mating connector
Sense +On the wire, as close as possible to where it enters the fillet
Sense -On the pad copper, just past the fillet
Force -On the pad copper or trace, beyond the sense contact

The current then flows only through wire, joint and pad copper, never through the ESC power stage. The reading covers exactly what lies between the two sense contacts, so their position must be identical on every unit: spring-loaded pogo pins in a machined or 3D-printed nest give that repeatability. Check the rated current of the force pins; several pins in parallel or a clamp are used when one pin is not enough.

Step 2: Write the Fixture Plug

The fixture microcontroller in this example accepts four ASCII commands over serial: SEL <channel>, CUR <amps>, I? (measured current in amps) and V? <samples> (averaged sense voltage in microvolts). The protocol is your firmware's; adapt the plug to it.

plugs/kelvin.py
34 lines
import serialclass KelvinFixture:    def __init__(self, port, baudrate=115200):        self._ser = serial.Serial(port, baudrate, timeout=2)    def _query(self, command):        self._ser.write((command + "").encode())        reply = self._ser.readline().decode().strip()        if not reply or reply.startswith("ERR"):            raise RuntimeError(f"Fixture command {command!r} failed: {reply!r}")        return reply    def select(self, channel):        self._query(f"SEL {channel}")    def set_current(self, amps):        self._query(f"CUR {amps:.3f}")    def read_current(self):        return float(self._query("I?"))    def read_sense_uv(self, samples):        return float(self._query(f"V? {samples}"))    def __del__(self):        # Never leave the current source on between runs.        try:            self._query("CUR 0")        except Exception:            pass        self._ser.close()

Add the serial dependency to the procedure:

requirements.txt
pyserial>=3.5

Step 3: Write the Measurement Phase

One function measures any joint. The channel comes from the phase with: block, so the YAML declares one phase per joint and reuses the same code.

phases/joints.py
26 lines
import timeSETTLE_S = 0.05def measure_joint(kelvin, measurements, log, channel, current_a=4.0, samples=64):    kelvin.select(channel)    # Reading at zero current: thermal EMF at the contacts and amplifier    # offset are in the microvolt range, the same order as the signal.    kelvin.set_current(0.0)    time.sleep(SETTLE_S)    offset_uv = kelvin.read_sense_uv(samples)    kelvin.set_current(current_a)    try:        time.sleep(SETTLE_S)        loaded_uv = kelvin.read_sense_uv(samples)        actual_a = kelvin.read_current()    finally:        kelvin.set_current(0.0)    drop_mv = (loaded_uv - offset_uv) / 1000.0    measurements.drop_mv = drop_mv    measurements.resistance_mohm = drop_mv / actual_a  # mV / A = mΩ    log.info(f"Channel {channel}: {drop_mv:.3f} mV at {actual_a:.3f} A")

Subtracting the zero-current reading removes offsets that do not depend on current. Dividing by the measured current, not the set point, removes the current source tolerance from the resistance.

Step 4: Declare the Procedure

procedure.yaml
84 lines
name: ESC Power Joint Screeningversion: 1.0.0execution:  workers: 1plugs:  - name: Kelvin Fixture    key: kelvin    python: plugs.kelvin:KelvinFixture    scope: station    with:      port: "COM4"setup:  - name: Fixture Reference Check    key: fixture_check    python: phases.joints:measure_joint    with:      channel: 15    measurements:      - name: Reference Shunt Resistance        key: resistance_mohm        unit: mΩ        validators:          - operator: ">="            expected_value: 0.98          - operator: "<="            expected_value: 1.02main:  - name: Battery Positive Joint    key: joint_bat_pos    python: phases.joints:measure_joint    with:      channel: 0    measurements:      - name: Voltage Drop        key: drop_mv        unit: mV        validators:          - operator: ">="            expected_value: 0.5          - operator: "<="            expected_value: 3.0      - name: Joint Resistance        key: resistance_mohm        unit: mΩ  - name: Battery Negative Joint    key: joint_bat_neg    python: phases.joints:measure_joint    with:      channel: 1    measurements:      - name: Voltage Drop        key: drop_mv        unit: mV        validators:          - operator: ">="            expected_value: 0.5          - operator: "<="            expected_value: 3.0      - name: Joint Resistance        key: resistance_mohm        unit: mΩ  - name: Motor 1 Phase A Joint    key: joint_m1_a    python: phases.joints:measure_joint    with:      channel: 2    measurements:      - name: Voltage Drop        key: drop_mv        unit: mV        validators:          - operator: ">="            expected_value: 0.5          - operator: "<="            expected_value: 4.0      - name: Joint Resistance        key: resistance_mohm        unit: mΩ

A 4-in-1 ESC has two battery joints and twelve motor phase joints. Add one phase per joint with its own channel and limits. Key points of this file:

  • execution: workers: 1 runs the phases one after another. Without it, phases without depends_on start in parallel and would fight over the multiplexer.
  • The setup phase measures a precision 1.000 mΩ shunt mounted on the fixture, on channel 15. Worn pogo pins or a loose sense wire make this check fail before any unit is judged.
  • scope: station keeps the serial port open between units.
  • Limits are per joint. Battery and phase pads have different copper geometry, so their baselines differ. The values shown are examples for a 4 A test current.
  • A lower limit catches a reading near zero, the signature of sense pins touching each other or not touching the part.

Step 5: Set Limits from a Golden-Unit Baseline

Limits come from data, in two passes:

  1. Fixture repeatability. Measure the golden unit 30 times, removing and reseating it each time. The spread of each joint's reading is the measurement noise of the fixture. A gauge R&R study with several operators formalizes this step.
  2. Production baseline. Measure every joint on a set of known-good units, 30 or more. For each joint, compute the mean and standard deviation of the voltage drop.

A common starting point is an upper limit at the baseline mean plus a fixed number of standard deviations, or a fixed ratio to the golden unit (for example 1.5 times its value). Both are example rules: the factor depends on how far the defect population sits from the good one on your product. Once production runs, refine the limits with the method in How to Set Measurement Limits Using Production Data.

Re-measure the golden unit at the start of each shift. A drift in its readings is a fixture problem, not a soldering problem.

Step 6: Review Results in TofuPilot

Run the procedure with tofupilot run during development, then deploy it to the station. Every run records each joint's voltage drop and resistance against the unit serial number. In the dashboard:

ViewUse
Failure Pareto per measurementShows which joint fails most
Control chart per jointShows drift before units cross the limit
Cpk per jointShows whether the limit leaves enough margin to the process
Yield per station and per operatorSeparates fixture issues from soldering issues

The reasons these joints fail in the field, and the process changes that prevent them, are covered in Why ESC Solder Joints Fail in Flight. A static measurement does not find every crack: add the flex measurement described in Catch Intermittent Contacts in Production.

Common Pitfalls

Two-wire measurement. Pogo pin contact resistance is typically tens of milliohms, larger than the joint. Without separate sense contacts, the reading tracks pin wear.

Ignoring the zero-current offset. Thermal EMF at dissimilar-metal contacts adds microvolts that do not depend on current. Measure at zero current and subtract.

Dividing by the set-point current. A source specified at 1% shifts every resistance by 1%. Measure the actual current.

Phases running in parallel on one fixture. Without workers: 1 or chained depends_on, two phases can select different channels at the same time.

One limit for every joint. Battery and phase pads have different baselines. A shared limit is too loose on one and too tight on the other.

Sense contacts in a different place on each unit. The reading includes the copper between the sense contacts. Fix their position with a rigid nest.

Raising the current to get more signal. Above a few amps the force pins heat and wear, and the joint starts to heat. Average more samples instead.

Key Points

  • A 4-wire voltage drop at a few amps resolves a 0.5 mΩ joint from a 5 mΩ one without stressing it.
  • Force and sense contacts must be separate, with sense contacts as close to the fillet as possible and in a fixed position.
  • Subtract the zero-current reading and divide by the measured current.
  • Check the fixture against a reference shunt in a setup phase before measuring units.
  • Set per-joint limits from fixture repeatability and a known-good baseline, then refine them with production data.
  • High-current burn-in is destructive, unsafe and produces no data. It is not a screen.

More Guides

Put this guide into practice