# Real-Time Arrhythmia Detection at the Edge: Deploying TinyML on ESP32 for Raw ECG Analysis

> Source: <https://dev.to/beck_moulton/real-time-arrhythmia-detection-at-the-edge-deploying-tinyml-on-esp32-for-raw-ecg-analysis-6ae>
> Published: 2026-06-29 00:24:00+00:00

In the world of wearable health technology, the holy grail has always been moving intelligence from the cloud to the edge. Waiting for a cloud server to analyze your heart rhythm is not just a latency issue—it's a privacy and battery life concern. Today, we are diving deep into **TinyML**, **Edge AI**, and **ECG signal processing** to build a real-time abnormality detector.

By leveraging **TensorFlow Lite for Microcontrollers** and the versatile **ESP32**, we can process raw electrocardiogram (ECG) data locally. This approach ensures low-latency detection of arrhythmias while keeping sensitive medical data on-device. If you've been looking to bridge the gap between high-level deep learning and low-level embedded systems, you're in the right place!

The pipeline involves capturing a high-frequency analog signal, cleaning it, and feeding it into a quantized Convolutional Neural Network (CNN). Here is how the data flows through our ESP32:

``` php
graph TD
    A[Raw ECG Signal/Sensor] -->|ADC Sampling| B(Preprocessing: Bandpass Filter)
    B --> C{Buffer Management}
    C -->|Windowed Segment| D[TFLite Micro Inference Engine]
    D --> E{CNN Model Classification}
    E -->|Normal| F[Log: Sinus Rhythm]
    E -->|Abnormal| G[Trigger Alert: Arrhythmia]
    G -->|Bluetooth/Wi-Fi| H[Mobile Dashboard]
```

To follow this advanced guide, you'll need:

Before we touch the C++ code, we need a model. Typically, we use the MIT-BIH Arrhythmia Database to train a 1D-CNN. The crucial step is **Post-Training Quantization**.

Since the ESP32 doesn't have a dedicated NPU, we convert our 32-bit float model into an **8-bit integer (INT8)** model. This reduces the size by 4x and speeds up inference significantly without a massive drop in accuracy.

We need to load the model as a byte array and set up the TFLite interpreter. Here is a simplified implementation of the inference loop.

```
#include <TensorFlowLite.h>
#include "model_data.h" // Your exported INT8 model
#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_interpreter.h"

// Constants for the ECG window
const int kTensorArenaSize = 30 * 1024; // 30KB Arena
uint8_t tensor_arena[kTensorArenaSize];

// TFLite globals
tflite::MicroInterpreter* interpreter = nullptr;
TfLiteTensor* input = nullptr;

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

    // 1. Load the model
    static tflite::MicroMutableOpResolver<5> resolver;
    resolver.AddConv2D();
    resolver.AddMaxPool2D();
    resolver.AddFullyConnected();
    resolver.AddSoftmax();
    resolver.AddReshape();

    static tflite::MicroInterpreter static_interpreter(
        tflite::GetModel(g_model_data), resolver, tensor_arena, kTensorArenaSize);
    interpreter = &static_interpreter;
    interpreter->AllocateTensors();

    input = interpreter->input(0);
}

void loop() {
    // 2. Simulate/Read ECG Data (Sampled at 125Hz or 250Hz)
    float sample = analogRead(34); 
    // ... [Preprocessing Logic: Normalization & Filtering] ...

    // 3. Fill the Input Tensor
    for (int i = 0; i < input->dims->data[1]; i++) {
        input->data.f[i] = processed_samples[i]; 
    }

    // 4. Run Inference
    TfLiteStatus invoke_status = interpreter->Invoke();
    if (invoke_status != kTfLiteOk) {
        Serial.println("Inference failed!");
        return;
    }

    // 5. Analyze Results
    float normal_score = interpreter->output(0)->data.f[0];
    float abnormal_score = interpreter->output(0)->data.f[1];

    if (abnormal_score > 0.8) {
        Serial.println("⚠️ Warning: Abnormal Heart Rhythm Detected!");
    }
}
```

When moving from a prototype to a production-grade wearable, you'll encounter challenges like signal noise from muscle movement (EMG) and power consumption. For those looking to implement robust noise-cancellation algorithms or more efficient memory management in TinyML deployments, I highly recommend checking out more production-ready examples.

Pro-Tip: For a deep dive into advanced signal processing patterns and optimizing TensorFlow Lite for mission-critical edge applications, visit the[WellAlly Tech Blog]. They provide excellent resources on scaling these localized AI models to enterprise-grade health solutions.

Raw ECG data is messy. You'll need a digital Bandpass Filter (usually 0.5Hz to 40Hz) to remove baseline wander and high-frequency noise. In C++, this can be implemented as a simple IIR filter to keep the computational overhead low on the ESP32.

```
// Example: Simple Low-pass filter component
float lowPass(float input, float prevOutput, float alpha) {
    return prevOutput + alpha * (input - prevOutput);
}
```

Deploying a CNN on an ESP32 for real-time ECG analysis isn't just a "cool project"—it's a glimpse into the future of decentralized healthcare. By processing data locally, we respect user privacy and reduce the load on our infrastructure.

**What's next for your TinyML journey?**

Happy hacking! 🚀💻

*If you enjoyed this tutorial, don't forget to ❤️ and bookmark it! For more advanced Edge AI content, keep an eye on my profile or visit the official WellAlly Blog.*
