Skip to content
Test Types & Methods

Motor and Actuator Testing with TofuPilot

Learn how to automate motor and actuator production tests, capture torque, speed, and vibration data, and track results with TofuPilot.

JJulien Buteau
intermediate12 min readMarch 14, 2026

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 TypeWhat It MeasuresWhy It Matters
No-load speedRPM at rated voltage, no torqueWinding and magnet integrity
Stall torqueMaximum torque at zero RPMMechanical strength
Current drawAmps at no-load, rated load, stallEfficiency, winding shorts
Back-EMFGenerated voltage when spun externallyMagnet and winding quality
VibrationAcceleration spectrum at operating speedBearing health, balance
Insulation resistanceMegohm reading winding-to-caseSafety, dielectric integrity

Prerequisites

  • Python 3.8+ with openhtf and tofupilot installed
  • 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.

motor_noload_test.py
import openhtf as htffrom openhtf.util import unitsimport 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.

motor_torque_curve.py
import openhtf as htffrom openhtf.util import unitsimport 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.

motor_vibration_test.py
import openhtf as htffrom openhtf.util import unitsimport 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:

motor_production_test.py
import openhtf as htffrom tofupilot import TofuPilotClientdef 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()

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 TypeAdditional Tests
Linear actuatorStroke length, extension force, retraction speed
Servo motorPosition accuracy, settling time, step response
Stepper motorHolding torque, step accuracy, resonance points
SolenoidPull-in voltage, drop-out voltage, response time

The test structure is the same. Define measurements with limits, capture the data, let TofuPilot track everything.

More Guides

Put this guide into practice