ESP32-S3 with I2C MEMS accelerometer mounted on an electric motor bearing housing for TinyML predictive maintenance

Figure 1: A rigid-mount MEMS accelerometer feeds vibration data to the ESP32-S3, where a locally running TinyML model detects deviations from the machine's healthy signature.

⚠️ INDUSTRIAL SAFETY: ROTATING MACHINES & MAINS VOLTAGE

  • Lockout / Tagout First: Never drill, tap, glue or bolt a sensor onto a running machine. Isolate the drive electrically, lock the disconnect, and verify zero energy (LOTO) before any physical installation.
  • Guards Stay On: Never remove a coupling guard, fan cover or belt guard to reach a sensor. Use a magnetic base or a threaded M6/M3 stud on an existing bearing-cap location, outside the rotating envelope.
  • Mains Awareness: Motor drives operate at 120/230/400 V and their DC links retain lethal charge after switch-off. Power the ESP32-S3 from an isolated 5 V supply, never from an unprotected mains buck converter on the same board.
  • This Is Not a Safety Interlock: An Edge Impulse anomaly score is a maintenance advisory, not a certified protection device. It must never replace a thermal overload, motor protection relay, E-stop or vibration trip switch.

Welcome back to TechFix Hub. Every failing motor lies to you — quietly. A bearing does not announce its death with a dramatic breakdown. It starts with a 0.3 mm spall, then a slightly higher vibration peak at a specific frequency, then a week of "that's probably normal", and finally a seized spindle, a burnt winding or a flooded pump.

Industrial vibration analysers solve this, but they cost thousands of dollars and assume an engineer walks the floor with them. What if the machine carried its own stethoscope — a $10 sensor, an ESP32-S3, and a neural network trained on its own healthy behaviour, running entirely offline?

That is exactly what this guide delivers. We will build a working TinyML predictive maintenance ESP32 node that learns what "normal" sounds like, watches for drift in real time, and raises a local alarm plus an MQTT notification to Home Assistant. No cloud subscription, no audio recording, no data leaving your workshop. If you've been searching for Edge Impulse vibration sensor projects, Maintenance prédictive DIY tutorials, or practical ESP32-S3 AI projects, this is the one to build.

🔬 1. Why Vibration Is the First Honest Symptom

Current and temperature only tell you that a motor is working harder. Vibration tells you which component is failing, because each mechanical defect creates a signature at a predictable frequency.

Start with the basics: convert RPM to frequency.

fRPM = RPM / 60   →   1800 RPM = 30 Hz (1×)
Fault Vibration Signature What You'll See in the FFT
Mass imbalance Centrifugal force once per revolution Rising 1× RPM peak, radial direction
Misalignment / bent shaft Twice-per-revolution bending Strong 2× RPM, often with 1× present
Mechanical looseness Non-linear support stiffness 0.5×, 1.5× and multiple sub-harmonics
Bearing outer/inner race defect Impact at BPFO / BPFI (geometry dependent) High-frequency resonances + sidebands, rising kurtosis
Cavitation / pump wear Broadband turbulence Wideband energy rise, no single dominant line

Here's the trick that makes DIY practical: you do not need to know BPFO geometry or hand-tune 40 frequency thresholds. Train a model on the machine's healthy spectrum, then ask it one question: "Does this window still look like healthy?" That's anomaly detection, and it's the right TinyML approach for maintenance.

📡 2. Hardware Choice: Don't Buy the Wrong Accelerometer

This is where 90% of vibration tutorials quietly fail. An IMU designed for a drone is not a condition-monitoring sensor.

  • ADXL345 (±2/4/8/16 g, up to 3200 Hz ODR): Ideal for this project. Cheap, I2C, low noise, and fast enough to resolve imbalance, misalignment and looseness up to ~1 kHz on a small motor or fan. We'll run it at 1600 Hz in this build.
  • ADXL372 / ICS-43432 + high-g path: Required if your goal is early bearing defect detection. Race defects excite resonances above 2–5 kHz; a low-bandwidth IMU simply cannot see them. Budget for these once the basic pipeline works.
  • MPU6050 (avoid for this): Excellent for attitude and gesture work, but its internal DLPF and higher noise floor smear the small spectral peaks you're trying to learn.
  • Why ESP32-S3? The LX7's vector instructions accelerate the DSP/NN inference, and 8 MB of Octal PSRAM lets you keep a raw vibration ring buffer, a feature tensor and the model alive without thrashing. An original ESP32 works too, but at a much lower inference rate.
Component Specification Role
MCU ESP32-S3-DevKitC-1 (N8R8) Local feature extraction + inference
Sensor ADXL345 breakout, 3.3 V logic Tri-axial vibration acquisition
Mounting M3 stud / wax mount / mag base Rigid coupling to bearing housing
Local alarm WS2812B ring + passive buzzer Works even with WiFi disabled
Power Isolated 5 V / 2 A USB-C supply Galvanic separation from the drive

🔌 3. Wiring and the Mounting Rule That Makes or Breaks Accuracy

ADXL345      →   ESP32-S3
---------------------------
VCC          →   3.3 V
GND          →   GND
SDA          →   GPIO 8
SCL          →   GPIO 9
INT1         →   GPIO 6  (optional data-ready)

WS2812B DIN  →   GPIO 48
Buzzer I/O   →   GPIO 17 (via transistor)
      

Mount on the bearing housing, not on the plastic fan shroud. A MEMS sensor must be coupled to the mass whose motion you want to measure. Double-sided foam tape turns your sensor into a low-pass filter and adds its own resonance above a few hundred hertz.

  1. Best: A drilled and tapped M3/M6 stud directly on the bearing cap (machining done under LOTO).
  2. Good: A magnetic base on a flat steel motor body — repeatable, removable, stiff enough below ~2 kHz.
  3. Acceptable for learning: Hard wax or heavy-duty adhesive putty on a clean, flat surface. Never loose tape.
  4. Then: Label your axes. Radial vibration reveals imbalance; axial vibration reveals misalignment. Keep the orientation identical after re-mounting, or your model will see a "fault" that is just a rotated sensor.

🧠 4. Training the Model: Learn "Healthy", Don't Manufacture "Broken"

Classification requires examples of every failure mode. On a real machine you don't have those — and you should never deliberately damage production equipment to collect them. Instead, use anomaly detection.

  1. Acquire a representative baseline. Record at least 20–30 minutes of healthy operation, spread across real load states: cold start, warm idle, full load, partial load, and the normal on/off cycle. A model trained only at idle will alarm every time the machine reaches working temperature.
  2. Configure the impulse. In Edge Impulse: Sample frequency = 1600 Hz, Window length = 1024 samples (~0.64 s), Window increase = 512. Processing block: Spectral Analysis with FFT length = 256 → 6.25 Hz resolution, enough to separate a 30 Hz imbalance peak from a 60 Hz misalignment peak. Learning block: Anomaly (K-means).
  3. Use all three axes. Fold X, Y and Z into the same impulse. If your UI only allows one axis, sum the spectra rather than picking the "prettiest" one — bearing impacts often appear only axially.
  4. Validate honestly. Hold out 20% of healthy recordings as your test set. A model that scores 100% on training data but alarms on healthy test windows is overfit, not clever.
  5. Benchmark, then deploy. Run the model latency benchmark on the ESP32-S3 target. Aim for inference well below your window rate. Export the Arduino library and install the ZIP in the Arduino IDE.
  6. Prove it on a test rig first. A 12 V axial fan or small BLDC motor on a rigid plate is a safe sandbox. With power isolated, add a tiny piece of tape to one fan blade to create a mild imbalance. Watch the anomaly score climb. Do this on a sacrificial rig, never on a live production drive.

💻 5. The Firmware: Acquire, Infer, Alert — With Hysteresis

Two stages matter here. First, verify your acquisition path with the sketch below. Then connect the inference output to the alert logic.

A. Acquisition Sanity Check

#include <Wire.h>
#include <Adafruit_ADXL345_U.h>

Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);

void setup() {
  Serial.begin(115200);
  Wire.begin(8, 9);

  if (!accel.begin()) {
    Serial.println("ADXL345 not found. Check SDA/SCL and address.");
    while (1) delay(100);
  }

  accel.setRange(ADXL345_RANGE_8_G);
  accel.setDataRate(ADXL345_DATARATE_1600_HZ);
  accel.setBandRate(ADXL345_BAND_RATE_0); // widest bandwidth

  Serial.println("Acquiring at 1600 Hz. Sensor ID verified.");
}

void loop() {
  sensors_event_t event;
  accel.getEvent(&event);

  // RMS over a single sample is NOT a health metric.
  // This is only to confirm axis orientation and signal presence.
  Serial.printf("X:%.3f Y:%.3f Z:%.3f m/s2\n",
                event.acceleration.x,
                event.acceleration.y,
                event.acceleration.z);
  delay(10);
}
      

B. Inference + Debounced Alert Logic

Rename the include below to match your exported Edge Impulse project. The important engineering is not the inference call — it's what you do with the score.

#include "your_project_inferencing.h"   // rename to your exported library
#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_NeoPixel.h>

#define WS_PIN 48
#define WS_COUNT 12
Adafruit_NeoPixel ring(WS_COUNT, WS_PIN, NEO_GRB + NEO_KHZ800);

WiFiClient espClient;
PubSubClient mqtt(espClient);

// Tuning: start high (fewer false alarms), then lower gradually.
const float ALERT_THRESHOLD = 0.65;
const float CLEAR_THRESHOLD = 0.35;  // hysteresis band
const uint8_t CONFIRM_WINDOWS = 5;   // ~5 windows, not one spike

bool faultLatched = false;
uint8_t overCount = 0;
uint8_t underCount = 0;
uint32_t lastPublish = 0;
uint32_t sensorWatchdog = 0;

void setup() {
  Serial.begin(115200);
  ring.begin();
  ring.show();

  // WiFi + MQTT connect (use your own secure credentials / TLS)
  // WiFi.begin(...); mqtt.setServer("192.168.1.100", 1883);

  ei_printf("TinyML maintenance node ready.\n");
}

void loop() {
  ei_impulse_result_t result = {0};

  // ---------------------------------------------------------
  // Edge Impulse fills the feature buffer via its capture callback.
  // Wire that callback to your ADXL345 ring buffer (1600 Hz).
  // ---------------------------------------------------------
  int err = run_classifier(&result);
  if (err != EI_IMPULSE_OK) {
    mqtt.publish("machine/health", "SENSOR_OR_MODEL_ERROR");
    setRing(255, 120, 0);   // amber = fault in the NODE, not the motor
    return;
  }

  // For an Anomaly impulse, result.anomaly[] holds per-class scores.
  float score = 0.0f;
  for (size_t i = 0; i < result.anomaly_count; i++) {
    if (result.anomaly[i].value > score) score = result.anomaly[i].value;
  }

  // Debounce: a single transient (a door slamming nearby) is not a fault.
  if (score > ALERT_THRESHOLD) {
    overCount++; underCount = 0;
  } else if (score < CLEAR_THRESHOLD) {
    underCount++; overCount = 0;
  }

  if (!faultLatched && overCount >= CONFIRM_WINDOWS) {
    faultLatched = true;
    mqtt.publish("machine/alert", "VIBRATION_ANOMALY");
    ei_printf("ALERT: anomaly %.3f\n", score);
  }
  if (faultLatched && underCount >= CONFIRM_WINDOWS * 2) {
    faultLatched = false;   // recovery takes longer than tripping
    mqtt.publish("machine/alert", "CLEARED");
  }

  setRing(faultLatched ? 255 : 0, faultLatched ? 0 : 255, 0);

  if (millis() - lastPublish > 5000) {
    mqtt.publish("machine/anomaly_score", String(score, 3).c_str());
    lastPublish = millis();
  }
}

void setRing(uint8_t r, uint8_t g, uint8_t b) {
  for (int i = 0; i < WS_COUNT; i++) ring.setPixelColor(i, ring.Color(r, g, b));
  ring.show();
}
      

Three details that separate a demo from a deployment:

  • Hysteresis: separate trip and reset thresholds prevent chattering at the decision boundary.
  • Failure-open vs failure-safe: a sensor disconnect, I²C NACK or inference error must publish SENSOR_OR_MODEL_ERROR, never "healthy". Silence is not evidence of good health.
  • Local-first alarming: the RGB ring and buzzer work even if WiFi is down. Your maintenance node must not become blind because the router rebooted.

📉 6. The Part Nobody Tells You: Concept Drift and False Alarms

Your model will drift. Belts stretch, grease ages, ambient temperature changes, and the machine you trained on in January is not mechanically identical to the one in July. Plan for it:

  • Log the spectrum, not just the score. Store the top 5 FFT bins plus RMS and kurtosis on SD card or via MQTT. When an alarm fires, you want to see where in the spectrum it came from.
  • Track the baseline median. If the healthy median slowly rises over three weeks, that is early degradation — or a mounting that has loosened. Check the mount before believing the model.
  • Schedule quarterly recalibration. Re-record a short healthy window after any maintenance, belt change, bearing replacement or sensor re-mount. Retrain; don't just lower the threshold to silence the alarm.
  • Respect the Nyquist limit. At 1600 Hz sampling you can only observe up to 800 Hz. Antialiasing and ODR selection matter. If you need ultrasonic bearing detection, change the sensor, not the code.
  • Add complementary channels cheaply. An INA219 on the DC bus plus an LM35/DS18B20 on the housing catches electrical and thermal faults that pure vibration misses. Multi-sensor fusion beats any single model.

🚀 7. The "TechFix Hub" Upgrade: From Alarm to Work Order

  • Home Assistant Health Card: Publish machine/anomaly_score, RMS and peak frequency as sensors. Build a Trend card so you see the degradation curve over weeks, not just a red icon today.
  • Automatic RUL Estimation: Fit a linear or exponential trend to the rolling 7-day anomaly median. Extrapolate to your trip threshold to estimate Remaining Useful Life in days. Treat it as an estimate, not a promise.
  • Multi-Machine Fleet: Each node publishes to machine/{id}/.... One ESP32-S3 per asset, one MQTT topic tree, one dashboard. Never train one shared model across different motors — their healthy signatures are as different as fingerprints.
  • OTA With Rollback: Models change. Sign your firmware and keep the previous partition bootable, exactly as covered in our ESP32 Secure Boot guide. A bad model update should never take your monitoring offline.
  • Battery + LoRa for Remote Assets: For a well pump or irrigation motor with no WiFi, move inference to the ESP32-S3, wake every 60 seconds, sample for one second, publish the score over LoRa, and sleep. The whole point of TinyML is that the answer fits in a few bytes.

🔧 8. Troubleshooting the Most Common Failure Modes

  1. Alarms immediately on a healthy motor: Your baseline didn't include full-load or warm-state operation, or the sensor is mounted on a flexible panel. Fix the mount and re-record before touching thresholds.
  2. Score stuck near zero even on a faulty rig: Sample rate mismatch between Edge Impulse configuration and the actual ADXL345 ODR. The model is receiving stretched or compressed spectra.
  3. Boot loop after enabling the model: Feature buffers and the TFLite arena are being allocated on internal heap. Enable PSRAM for the tensor arena in the generated ei_classifier_config.h.
  4. Everything works on USB, fails in the enclosure: Switching noise from the drive's VFD coupling into your I²C lines. Use shielded sensor cable, keep it away from motor leads, and add a 100 nF decoupler at the sensor's VCC pin.
  5. Intermittent I²C reads: Pull-ups missing or too weak for your cable length, or you're trying to run 400 kHz over 2 m of wire. Drop to 100 kHz and keep the bus short.

🔗 Build the Complete Industrial Maker Stack

✅ Conclusion

Predictive maintenance used to be a service you bought. With an ESP32-S3, a rigidly mounted MEMS accelerometer and an Edge Impulse anomaly model, it becomes a skill you own. The hardware is the easy part. The real engineering is in the mounting, the representativeness of your healthy baseline, the debounce logic, and your willingness to recalibrate when the machine changes. Get those right and you'll hear the bearing before it screams — and be the person who ordered the spare part on Tuesday instead of the one explaining the downtime on Friday.

🛠️ Building your own condition-monitoring node?

Tell us your motor type, RPM and accelerometer in the comments. The TechFix Hub community will help you choose a sample rate, a mounting method and a sensible starting threshold.

Keywords: TinyML predictive maintenance ESP32 Edge Impulse vibration sensor Maintenance prédictive DIY ESP32-S3 AI projects

Bottom Ad [Post Page]