Motor Telemetry and Safety Test Bench

Published Feb 17, 2026
 5 hours to build
 Intermediate

The Motor Test Bench is a high-precision diagnostic station built to capture real-time telemetry from motors mounted in a vertical orientation. By utilizing an ESP32 as the central processing unit, the system performs complex sensor fusion to calculate vertical thrust, rotational speed, and electrical efficiency while simultaneously monitoring structural health through high-frequency vibration analysis.

display image

Components Used

ESP32 WROOM
WiFi Development Tools - 802.11 ESP32 General Development Kit, embeds ESP32-WROOM-32E, 4MB flash.
1
MPU6050 Gyroscope and Accelerometer
MPU6050 (Gyroscope + Accelerometer + Temperature) is a combination of 3-axis Gyroscope, 3-axis Accelerometer and Temperature sensor with on-chip Digital Motion Processor (DMP). It is used in mobile devices, motion enabled games, 3D mice, Gesture (motion command) technology etc
1
Load Cell ADC HX711
Data Conversion IC Development Tools Grove - ADC for Load Cell (HX711)
1
ACS712 Hall Effect-Based Linear Current Sensor
ACS712 is a fully integrated hall effect-based linear current sensor IC.
1
Relay Module Single Channel
Relay Module Single Channel
1
Breadboard
Breadboard
1
Connecting Wire Jumper Wires
Connecting Wire Breadboard wires
20
HX710B
Air pressure sensor
1
TCRT5000 IR sensor
Measures RPM by detecting blades passing through an infrared beam
1
MOTOR
Any motor that you want to test
1
Acrylic/Plastic/Cardboard sheets
For base and body support
1
Male DC power adaptor
Use according to your motor and need (this project used 12V/1A)
1
Drone propeller
1
Motor Mounting Bracket
Use according to your motor
1
Description

Overview

The Motor Test Bench is a high-precision diagnostic station built to capture real-time telemetry from motors mounted in a vertical orientation. By utilizing an ESP32 as the central processing unit, the system performs complex sensor fusion to calculate vertical thrust, rotational speed, and electrical efficiency while simultaneously monitoring structural health through high-frequency vibration analysis. The vertical mounting configuration is critical for this project as it allows for the direct measurement of axial force and gravity-related torque, providing a more realistic simulation of drone flight or turbine operation than standard horizontal rigs.

The IoT integration is powered by the Blynk platform, which enables the test bench to function as a remote-access. The ESP32 pushes live sensor data across several virtual pins, including vertical weight from the load cell, motor RPM, peak vibration frequency, and current consumption. This remote monitoring capability is a vital safety feature, allowing the user to observe performance metrics and efficiency maps from a safe distance during high-stress testing. Furthermore, the system is designed with auto-retry logic that checks the status of sensors like the MPU6050 every five seconds; if a sensor fails due to high vibrations or electrical noise, the firmware attempts to re-initialize it automatically to ensure continuous data logging

Front View

                                              Front View

Purpose of the Project

The goal of this analyzer is to provide a comprehensive health and performance profile for motors that operate at extreme speeds.

  • Efficiency Mapping: It calculates the ratio between electrical power input and mechanical thrust output to find the motor's "peak efficiency".
  • Vibration Diagnostics: Using FFT analysis, the system identifies peak vibration frequencies to prevent mechanical resonance failures.
  • Safety Automation: It acts as a digital watchdog, automatically cutting power via a relay if current or vibration levels become dangerous.
  • Remote Testing: By using IoT, you can monitor high-RPM tests from a safe distance, reducing the risk associated with mechanical failures during testing.

     

Top-Side view

                                               Top-Side view

Connections

The wiring is designed to separate high-speed logic from the power-hungry motor components to ensure data integrity.

  • Vibration Mesurement: The MPU6050 is connected to GPIO 21 (SDA) and GPIO 22 (SCL).
  • RPM Input: The pulse sensor is attached to GPIO 18 and uses hardware interrupts for accuracy.
  • Load Cell (HX711): The data (DOUT) is on GPIO 25 and the clock (SCK) is on GPIO 26.
  • Air Pressure: Uses GPIO 27 (SCK) and GPIO 14 (OUT).
  • Current Monitoring: The ACS712 analog output is read on GPIO 34.
  • Safety Relay: Triggered via GPIO 4.

Working Principle

The system follows a specific logic flow to provide accurate diagnostics while the motor is vertically mounted:

  1. RPM & Thrust: The ESP32 calculates RPM by measuring the interval between blade detections. Simultaneously, the Load Cell measures the vertical force (thrust) in grams, which is smoothed via a moving average to filter out noise.
  2. Vibration Analysis: The MPU6050 collects 128 samples of acceleration. The code then runs a Fast Fourier Transform (FFT) to identify the "Major Peak," which represents the primary vibration frequency of the motor.
  3. Aerodynamics: By reading air pressure and comparing it to ambient density, the system calculates air pressure.
  4. Efficiency Math: The code calculates efficiency by comparing mechanical output (Thrust×Velocity) against electrical input (Voltage×Current).
  5. Active Safety: The system continuously checks if the vibration exceeds 6000 Hz or if the current exceeds 5 Amps (can be changed accordingly). If these limits are crossed, the relay is immediately triggered to "Danger" mode, shutting down the motor.

 

Anchor Point

                                           Anchor Point

Blynk IoT Connection

This project is fully integrated with Blynk IoT for remote data visualization.

  • Live Telemetry: Data is pushed to the Blynk app every second, including Weight (V0), RPM (V1), Vibration (V2), Current (V3), Pressure (V4), and Efficiency (V5).
  • E-Stop Status: Virtual Pin V6 monitors the safety state; if the system enters a "Danger" state, it reflects on the dashboard immediately.
  • Sensor Resilience: The firmware includes an auto-retry feature. If a sensor like the MPU6050 fails at startup, the system will attempt to reconnect to it every 5 seconds without needing a full reboot.

     

Blynk dash Board

                                                Blynk dash Board

Code

#define BLYNK_TEMPLATE_ID "enter your blynk tempelate ID"
#define BLYNK_TEMPLATE_NAME "enter your blynk template name "
#define BLYNK_PRINT Serial

#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include "arduinoFFT.h"
#include "HX711.h"

// =========================================
//  USER CONFIGURATION
// =========================================
char auth[] = "Blynk auth Token"; 
char ssid[] = "WiFi Name";        
char pass[] = "WiFi password";    

const float BATTERY_VOLTAGE = 12.0; 

// =========================================
//  SAFETY LIMITS
// =========================================
const float SAFETY_VIB_LIMIT_HZ = 6000.0;  
const float SAFETY_CURR_LIMIT_A = 5.0;  

// =========================================
//  PIN ASSIGNMENT
// =========================================
const int RPM_PIN = 18; 
const int AIR_SCK_PIN = 27; 
const int AIR_OUT_PIN = 14;
const int MPU_SDA_PIN = 21; 
const int MPU_SCL_PIN = 22;
const int CURRENT_PIN = 34; 
const int LC_DOUT_PIN = 25;
const int LC_SCK_PIN = 26;
const int RELAY_PIN = 4; 

// =========================================
//  GLOBAL VARIABLES
// =========================================
BlynkTimer timer; 

// --- RPM ---
const int blades = 2;
const unsigned long debounceMicros = 2000;
const unsigned long timeoutValue = 1000000;
volatile unsigned long lastPulseTime = 0;
volatile unsigned long pulseInterval = 0;
volatile bool newPulseAvailable = false;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

// --- Air Pressure ---
float PRES_SCALE_FACTOR = 450000.0;
float PRES_DEADZONE = 0.15;
const float AIR_DENSITY = 1.225;
long PRES_OFFSET = 0;

// --- Vibration ---
#define SAMPLES 128
#define SAMPLING_FREQUENCY 512 
ArduinoFFT<double> FFT = ArduinoFFT<double>();
unsigned int sampling_period_us;
unsigned long microseconds;
double vReal[SAMPLES];
double vImag[SAMPLES];
Adafruit_MPU6050 mpu;
bool mpu_active = false; 

// --- Current ---
const float CURR_SENSITIVITY = 0.066; 
const float ADC_VOLTAGE = 3.3;    
float curr_zeroPoint = 2048; 
float curr_filteredAdc = 0;

// --- Load Cell ---
HX711 scale;
float LC_CALIBRATION_FACTOR = 400.0; 
const int SMOOTHING_WINDOW = 10;
float lc_readings[SMOOTHING_WINDOW];
int lc_readIndex = 0;
float lc_total = 0;
float lc_average = 0;

// --- Blynk Data Variables ---
float blynk_weight = 0;    
int   blynk_rpm = 0;       
float blynk_vib = 0;       
float blynk_current = 0;   
float blynk_pressure = 0;  
float blynk_eff = 0;       
int   blynk_estop = 0;     

// --- Interrupt ---
void IRAM_ATTR bladeDetected() {
  unsigned long currentTime = micros();
  unsigned long rawInterval = currentTime - lastPulseTime;
  if (rawInterval > debounceMicros) {
    pulseInterval = rawInterval;
    lastPulseTime = currentTime;
    newPulseAvailable = true;
  }
}

// --- Helper: Pressure Read with Timeout ---
long readPressureRaw() {
    unsigned long result = 0;
    unsigned long startTime = millis();
    while (digitalRead(AIR_OUT_PIN) == HIGH) {
      if (millis() - startTime > 50) return 0; // Timeout
    }
    for (int i = 0; i < 24; i++) {
      digitalWrite(AIR_SCK_PIN, HIGH); delayMicroseconds(1);
      digitalWrite(AIR_SCK_PIN, LOW);  delayMicroseconds(1);
      result = result << 1;
      if (digitalRead(AIR_OUT_PIN) == HIGH) result++;
    }
    digitalWrite(AIR_SCK_PIN, HIGH); delayMicroseconds(1);
    digitalWrite(AIR_SCK_PIN, LOW);
    long finalValue = result;
    if (result & 0x800000) finalValue |= 0xFF000000;
    return finalValue;
}

// =========================================
//  BLYNK SENDER
// =========================================
void sendSensorData() {
  Blynk.virtualWrite(V0, blynk_weight);   
  Blynk.virtualWrite(V1, blynk_rpm);      
  Blynk.virtualWrite(V2, blynk_vib);      
  Blynk.virtualWrite(V3, blynk_current);  
  Blynk.virtualWrite(V4, blynk_pressure); 
  Blynk.virtualWrite(V5, blynk_eff);      
  Blynk.virtualWrite(V6, blynk_estop);    
}

// =========================================
//  AUTO-RETRY SENSORS
// =========================================
void checkSensors() {
  // If Vibration sensor is dead, try to restart it
  if (!mpu_active) {
    if (mpu.begin()) {
      mpu.setAccelerometerRange(MPU6050_RANGE_4_G);
      mpu.setFilterBandwidth(MPU6050_BAND_260_HZ);
      mpu_active = true;
      Serial.println("[SYSTEM] MPU6050 Reconnected!");
    }
  }
}

// =========================================
//  SETUP
// =========================================
void setup() {
  Serial.begin(115200);
  
  Serial.println("Connecting to Blynk...");
  Blynk.begin(auth, ssid, pass);
  timer.setInterval(1000L, sendSensorData);
  timer.setInterval(5000L, checkSensors); // Retry dead sensors every 5s

  Serial.println("\n=== JET TURBINE ANALYZER + IOT STARTED ===");

  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW); 

  pinMode(RPM_PIN, INPUT); 
  attachInterrupt(digitalPinToInterrupt(RPM_PIN), bladeDetected, FALLING);

  pinMode(AIR_SCK_PIN, OUTPUT);
  pinMode(AIR_OUT_PIN, INPUT_PULLUP);
  digitalWrite(AIR_SCK_PIN, HIGH); delayMicroseconds(100); digitalWrite(AIR_SCK_PIN, LOW);
  
  long p_sum = 0;
  for (int i = 0; i < 10; i++) { 
    long val = readPressureRaw();
    if (val != 0) p_sum += val; 
    delay(20); 
  }
  PRES_OFFSET = p_sum / 10;

  Wire.begin(MPU_SDA_PIN, MPU_SCL_PIN);
  Wire.setClock(400000);
  if (!mpu.begin()) { 
    Serial.println("[ERROR] MPU6050 Not Found! Will retry later...");
    mpu_active = false;
  } else {
    mpu.setAccelerometerRange(MPU6050_RANGE_4_G);
    mpu.setFilterBandwidth(MPU6050_BAND_260_HZ);
    mpu_active = true;
  }
  sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));

  pinMode(CURRENT_PIN, INPUT);
  long c_sum = 0;
  for(int i=0; i<1000; i++) { c_sum += analogRead(CURRENT_PIN); delayMicroseconds(50); }
  curr_zeroPoint = c_sum / 1000.0;
  curr_filteredAdc = curr_zeroPoint;

  for (int i = 0; i < SMOOTHING_WINDOW; i++) lc_readings[i] = 0;
  scale.begin(LC_DOUT_PIN, LC_SCK_PIN);
  
  if (scale.is_ready()) {
    scale.set_scale(LC_CALIBRATION_FACTOR); 
    scale.tare(); 
  } else { 
    Serial.println("[ERROR] HX711 Not Found!"); 
  }

  Serial.println("=== SYSTEM READY ===");
}

// =========================================
//  MAIN LOOP
// =========================================
void loop() {
  Blynk.run();
  timer.run();

  // 1. VIBRATION (Skipped if sensor missing)
  double peakFrequency = 0;
  if (mpu_active) {
    for(int i=0; i<SAMPLES; i++) {
        microseconds = micros();
        sensors_event_t a, g, temp;
        mpu.getEvent(&a, &g, &temp);
        double totalAccel = sqrt(pow(a.acceleration.x, 2) + pow(a.acceleration.y, 2) + pow(a.acceleration.z, 2));
        vReal[i] = totalAccel - 9.81;
        vImag[i] = 0; 
        while(micros() - microseconds < sampling_period_us){ }
    }
    FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD);
    FFT.complexToMagnitude(vReal, vImag, SAMPLES);
    peakFrequency = FFT.majorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
    if (peakFrequency < 19.0) peakFrequency = 0.0;
  }

  // 2. RPM
  unsigned int rpm = 0;
  static bool rpm_stopped = false;
  unsigned long intervalCopy = 0;
  bool dataReady = false;
  unsigned long lastTimeCopy = 0;
  portENTER_CRITICAL(&timerMux);
  if (newPulseAvailable) { intervalCopy = pulseInterval; newPulseAvailable = false; dataReady = true; }
  lastTimeCopy = lastPulseTime;
  portEXIT_CRITICAL(&timerMux);
  if ((micros() - lastTimeCopy) > timeoutValue) { rpm_stopped = true; rpm = 0; }
  else if (dataReady && intervalCopy > 0) { 
    rpm = round((60000000.0 / intervalCopy) / blades);
  }

  // 3. AIR PRESSURE
  float kPa = 0;
  float velocity = 0;
  long rawValue = readPressureRaw(); 
  if (rawValue != 0) {
    kPa = (rawValue - PRES_OFFSET) / PRES_SCALE_FACTOR;
    if (kPa <= PRES_DEADZONE) kPa = 0.00;
    if (kPa > 0) velocity = sqrt((2 * (kPa * 1000.0)) / AIR_DENSITY);
  }

  // 4. CURRENT
  int rawAdc = analogRead(CURRENT_PIN);
  float alpha = 0.05;
  curr_filteredAdc = (alpha * rawAdc) + ((1.0 - alpha) * curr_filteredAdc);
  float voltage = (curr_filteredAdc / 4095.0) * ADC_VOLTAGE;
  float zeroVolts = (curr_zeroPoint / 4095.0) * ADC_VOLTAGE;
  float current_amps = (voltage - zeroVolts) / CURR_SENSITIVITY;
  if (current_amps < 0.30) current_amps = 0.0;

  // 5. LOAD CELL
  float load_weight_g = 0;
  if (scale.is_ready()) {
    float raw_mass = scale.get_units(1); 
    lc_total = lc_total - lc_readings[lc_readIndex];
    lc_readings[lc_readIndex] = raw_mass;
    lc_total = lc_total + lc_readings[lc_readIndex];
    lc_readIndex = lc_readIndex + 1;
    if (lc_readIndex >= SMOOTHING_WINDOW) lc_readIndex = 0;
    load_weight_g = lc_total / SMOOTHING_WINDOW;
    if (load_weight_g < 0) load_weight_g = 0.0;
  }

  // 6. EFFICIENCY
  float efficiency_percent = 0.0;
  float thrust_newtons = load_weight_g * 0.00981; 
  float numerator = thrust_newtons * velocity;     
  float denominator = BATTERY_VOLTAGE * current_amps; 
  if (denominator > 1.0) { efficiency_percent = (numerator / denominator) * 100.0; }

  // 7. SAFETY
  bool danger = (peakFrequency > SAFETY_VIB_LIMIT_HZ) || (current_amps > SAFETY_CURR_LIMIT_A);
  digitalWrite(RELAY_PIN, danger ? HIGH : LOW);
  
  blynk_weight = load_weight_g;
  blynk_rpm = rpm;
  blynk_vib = peakFrequency;
  blynk_current = current_amps;
  blynk_pressure = kPa;           
  blynk_eff = efficiency_percent;
  blynk_estop = danger ? 1 : 0;   
  
  static unsigned long lastPrint = 0;
  if (millis() - lastPrint > 500) {
    lastPrint = millis();
    Serial.print("RPM:"); Serial.print(rpm);
    Serial.print(" | Wt:"); Serial.print(load_weight_g, 0);
    Serial.print(" | Pres:"); Serial.print(kPa, 2);
    Serial.print(" | Vib:"); Serial.print(peakFrequency, 0);
    Serial.print(" | Curr:"); Serial.print(current_amps, 2);
    Serial.print(" | Eff%:"); Serial.println(efficiency_percent, 1);
  }
}

Conclusion

The Motor Test Bench is a robust tool that bridges the gap between simple motor testing and professional analysis. By focusing on a vertical mounting setup, it provides unique insights into thrust-to-weight ratios and axial vibration that horizontal benches often miss. With integrated safety protocols and a real-time IoT dashboard, this project ensures that high-speed motor testing is not only informative but also significantly safer.

Codes
Comments
Ad