Motor and Actuator Testing with TofuPilot
Every motor that leaves your line needs to spin at the right speed, draw the right current, and not vibrate itself apart. Manual spot checks don't scale. You need automated tests that capture torque curves, speed profiles, and vibration signatures for every unit.
TofuPilot and OpenHTF let you build those tests in Python and track every result across production.
What Motor Tests Cover
Production motor testing typically includes:
| Test Type | What It Measures | Why It Matters |
|---|---|---|
| No-load speed | RPM at rated voltage, no torque | Winding and magnet integrity |
| Stall torque | Maximum torque at zero RPM | Mechanical strength |
| Current draw | Amps at no-load, rated load, stall | Efficiency, winding shorts |
| Back-EMF | Generated voltage when spun externally | Magnet and winding quality |
| Vibration | Acceleration spectrum at operating speed | Bearing health, balance |
| Insulation resistance | Megohm reading winding-to-case | Safety, dielectric integrity |
Prerequisites
- Python 3.8+ with
openhtfandtofupilotinstalled - A dynamometer or torque sensor with serial/GPIB/USB interface
- A programmable power supply for motor drive
- Optional: accelerometer or vibration sensor
Step 1: Set Up the No-Load Test
The no-load test is your first gate. A motor that can't reach rated speed with no load has a fundamental problem.
import openhtf as htf
from openhtf.util import units
import time
@htf.measures(
htf.Measurement("no_load_speed_rpm")
.with_units(units.HERTZ) # RPM stored as value
.in_range(2850, 3150)
.doc("No-load speed at rated voltage"),
htf.Measurement("no_load_current_a")
.with_units(units.AMPERE)
.at_most(0.5)
.doc("No-load current draw"),
htf.Measurement("startup_time_ms")
.with_units(units.MILLISECOND)
.at_most(200)
.doc("Time to reach 90% rated speed"),
)
def no_load_test(test, psu, tachometer):
"""Run motor at rated voltage with no load attached."""
psu.set_voltage(24.0)
psu.output_on()
start = time.time()
# Wait for motor to reach steady state
time.sleep(2.0)
speed = tachometer.read_rpm()
current = psu.measure_current()
startup = tachometer.get_startup_time_ms()
test.measurements.no_load_speed_rpm = speed
test.measurements.no_load_current_a = current
test.measurements.startup_time_ms = startup
psu.output_off()Step 2: Capture Torque-Speed Curves
The torque-speed curve characterizes motor performance across its operating range. Capture it as a multi-dimensional measurement.
import openhtf as htf
from openhtf.util import units
import numpy as np
@htf.measures(
htf.Measurement("torque_speed_curve")
.with_dimensions(units.HERTZ) # RPM as dimension
.doc("Torque vs speed characteristic curve"),
htf.Measurement("rated_torque_nm")
.in_range(0.45, 0.55)
.doc("Torque at rated speed"),
htf.Measurement("efficiency_at_rated")
.in_range(0.80, 1.0)
.doc("Efficiency at rated operating point"),
)
def torque_curve_test(test, psu, dyno):
"""Sweep load from no-load to stall, capture torque at each point."""
psu.set_voltage(24.0)
psu.output_on()
time.sleep(1.0)
torque_points = []
speed_points = []
for load_pct in range(0, 105, 5):
dyno.set_load_percent(load_pct)
time.sleep(0.5) # Settle time
torque = dyno.read_torque_nm()
speed = dyno.read_rpm()
torque_points.append(torque)
speed_points.append(speed)
test.measurements.torque_speed_curve[speed] = torque
# Find rated operating point (closest to rated speed)
rated_idx = np.argmin(np.abs(np.array(speed_points) - 3000))
rated_torque = torque_points[rated_idx]
rated_speed = speed_points[rated_idx]
test.measurements.rated_torque_nm = rated_torque
# Calculate efficiency: P_mech / P_elec
p_mech = rated_torque * rated_speed * 2 * np.pi / 60
p_elec = 24.0 * psu.measure_current()
test.measurements.efficiency_at_rated = p_mech / p_elec
dyno.set_load_percent(0)
psu.output_off()Step 3: Add Vibration Analysis
Vibration testing catches bearing defects, rotor imbalance, and misalignment before they become field failures.
import openhtf as htf
from openhtf.util import units
import numpy as np
@htf.measures(
htf.Measurement("vibration_rms_g")
.at_most(0.5)
.doc("Overall vibration RMS in g"),
htf.Measurement("vibration_spectrum")
.with_dimensions(units.HERTZ)
.doc("Vibration frequency spectrum"),
htf.Measurement("dominant_frequency_hz")
.with_units(units.HERTZ)
.doc("Highest amplitude frequency component"),
)
def vibration_test(test, psu, accelerometer):
"""Measure vibration at rated speed."""
psu.set_voltage(24.0)
psu.output_on()
time.sleep(3.0) # Wait for steady state
# Capture 1 second of acceleration data at 10 kHz
raw_data = accelerometer.capture(duration_s=1.0, sample_rate=10000)
# RMS vibration
rms = np.sqrt(np.mean(raw_data ** 2))
test.measurements.vibration_rms_g = rms
# FFT for spectrum
fft_vals = np.abs(np.fft.rfft(raw_data))
freqs = np.fft.rfftfreq(len(raw_data), d=1/10000)
for freq, amplitude in zip(freqs[1:500], fft_vals[1:500]):
test.measurements.vibration_spectrum[freq] = amplitude
# Dominant frequency
peak_idx = np.argmax(fft_vals[1:]) + 1
test.measurements.dominant_frequency_hz = freqs[peak_idx]
psu.output_off()Step 4: Build the Full Test Sequence
Combine all motor tests into a single production sequence:
import openhtf as htf
from tofupilot import TofuPilotClient
def main():
test = htf.Test(
no_load_test,
torque_curve_test,
vibration_test,
)
test.add_output_callbacks(
TofuPilotClient().as_openhtf_callback(
procedure_id="motor-fct",
procedure_name="Motor Production FCT",
)
)
test.execute(test_start=htf.PhaseDescriptor.wrap(
lambda test: setattr(
test, "dut_id", input("Scan motor serial number: ")
)
))
if __name__ == "__main__":
main()Step 5: Monitor Production Trends
Once tests are running, use TofuPilot to catch drift before it causes failures:
- No-load speed trending down: Possible demagnetization or increased friction
- Current draw creeping up: Winding insulation degradation or bearing wear
- Vibration RMS increasing: Bearing failure progression
- Efficiency dropping: Look at both electrical and mechanical subsystems
Set measurement limits with margin. If your spec is 2850 to 3150 RPM, consider tighter production limits of 2900 to 3100 to catch drift early.
TofuPilot's control charts show these trends automatically. A shift in the mean or increasing standard deviation shows up before parts start failing.
Actuator-Specific Considerations
Linear actuators and servo motors need additional tests:
| Actuator Type | Additional Tests |
|---|---|
| Linear actuator | Stroke length, extension force, retraction speed |
| Servo motor | Position accuracy, settling time, step response |
| Stepper motor | Holding torque, step accuracy, resonance points |
| Solenoid | Pull-in voltage, drop-out voltage, response time |
The test structure is the same. Define measurements with limits, capture the data, let TofuPilot track everything.