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:
| Joint | Resistance (example) | Drop at 4 A | Heat in joint at 4 A |
|---|---|---|---|
| Good | 0.5 mΩ | 2 mV | 8 mW |
| Marginal | 1.5 mΩ | 6 mV | 24 mW |
| Cracked or cold | 5 mΩ | 20 mV | 80 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:
| Problem | Explanation |
|---|---|
| Destructive | At 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. |
| Unsafe | A 100 A source, hot joints and possible arcing on an open bench |
| Blind to cracks in compression | A crack held closed by the fixture conducts and passes |
| No data | The 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:
| Contact | Position |
|---|---|
| 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.py34 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:
pyserial>=3.5Step 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.py26 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.yaml84 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: 1runs the phases one after another. Without it, phases withoutdepends_onstart 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: stationkeeps 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:
- 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.
- 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:
| View | Use |
|---|---|
| Failure Pareto per measurement | Shows which joint fails most |
| Control chart per joint | Shows drift before units cross the limit |
| Cpk per joint | Shows whether the limit leaves enough margin to the process |
| Yield per station and per operator | Separates 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.
