Back to All Blog Posts
Tutorial EltroNerd Engineering

Patient Heart Rate Monitoring System using AD8232

This project builds a patient heart-rate monitoring system using ECG (electrocardiogram) to measure cardiac electrical activity and EMG (electromyography) to monitor muscle activity. The system uses low-cost modules (AD8232 for ECG, MyoWare for EMG) connected to an ESP32.

Patient Heart Rate Monitoring System — ECG + EMG (Step-by-step)

Full guide: hardware, wiring, safe electrode placement, firmware, signal processing (ECG QRS detection, EMG RMS), IoT telemetry, calibration, and troubleshooting. Markdown-ready for README or project documentation.


1. Project summary

This project builds a patient heart-rate monitoring system using ECG (electrocardiogram) to measure cardiac electrical activity and EMG (electromyography) to monitor muscle activity. The system uses low-cost front-end modules (e.g., AD8232 for ECG, MyoWare for EMG) connected to an ESP32 for local display, processing, and IoT telemetry (MQTT). The device can provide:

  • Real-time ECG waveform visualization
  • Heart-rate (BPM) derived from ECG via QRS detection
  • EMG activity level (RMS / envelope) for muscle monitoring
  • Alerts for arrhythmia-like events, prolonged bradycardia/tachycardia
  • Data logging to cloud or local storage

⚠️ Medical & safety disclaimer: This is NOT a medical device. Use only for hobbyist/educational prototyping. Take electrical safety seriously: never connect the device to mains-powered equipment; for patient-proximate use prefer medical-grade isolation and certified designs.


2. Parts & BOM

  • ESP32 development board (Wi‑Fi)
  • AD8232 ECG module (or AD8233) — simple single-lead ECG front-end
  • MyoWare Muscle Sensor (EMG) or similar EMG preamp
  • Disposable ECG electrodes and lead wires
  • 3.3V supply (LiPo battery recommended) and common ground
  • OLED display (SSD1306) — optional
  • Breadboard, jumper wires, enclosure

Libraries / tools (Arduino/PlatformIO):

  • WiFi.h (built-in)
  • PubSubClient (MQTT)
  • Adafruit_GFX + Adafruit_SSD1306 (optional)
  • (Optional plotting library if streaming to local PC)

3. Safety & electrode placement

Safety first:

  • Use battery power (LiPo USB) to isolate from mains.
  • Do not touch electrodes or patient while device connected to external networks unless isolation verified.
  • Add series current-limiting resistors and isolation barrier for any clinical deployment.

Electrode placement (single-lead ECG typical):

  • RA (Right Arm) — right chest/shoulder area
  • LA (Left Arm) — left chest/shoulder area
  • RL (Right Leg, reference) — lower torso or right hip (ground/reference)

For EMG (e.g., forearm):

  • Place two active EMG electrodes over the target muscle belly ~2–3 cm apart
  • Place reference electrode on a bony area or nearby neutral spot

4. Wiring / Pinout (ESP32)

ECG (AD8232) connections:

  • AD8232 LO+ and LO- to indicate lead-off (optional)
  • AD8232 OUT → ESP32 analog input (e.g., A0 / GPIO 34)
  • AD8232 3.3V → 3.3V
  • GND → GND

EMG (MyoWare) connections:

  • MyoWare SIG → ESP32 analog input (e.g., GPIO 35)
  • 5V or 3.3V depending on module (use 3.3V for ESP32 compatibility)
  • GND → GND

Optional OLED (I²C): SDA → 21, SCL → 22, VCC → 3.3V, GND → GND

Use high-quality shielded leads for ECG if possible, and keep signal wiring short.


5. Hardware assembly (step-by-step)

  1. Mount AD8232 and MyoWare on the breadboard; wire their power rails to 3.3V and GND.
  2. Attach disposable ECG electrodes to patient and connect lead wires to AD8232 electrode pins (RA, LA, RL).
  3. Attach EMG electrodes to the muscle and connect to MyoWare.
  4. Connect AD8232 OUT to an ADC-capable pin on ESP32 (GPIO 34/35/36 etc.).
  5. Connect MyoWare SIG to another ADC pin.
  6. Connect OLED (optional) to I²C pins if using a display.
  7. Power the ESP32 via USB/battery. Verify no mains connection to patient.

6. Firmware (ESP32 sketch) — read ECG & EMG, detect heart beats, send MQTT

The code below samples ECG and EMG via ADC, performs simple filtering and QRS-like detection (Pan-Tompkins simplified), computes BPM, EMG RMS envelope, displays values on OLED, and publishes JSON to MQTT.


c
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// CONFIG
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASS = "YOUR_PASS";
const char* MQTT_SERVER = "test.mosquitto.org";
const char* MQTT_TOPIC = "patient/monitor";

// ADC pins
const int PIN_ECG = 34; // ADC1_CH6
const int PIN_EMG = 35; // ADC1_CH7

// OLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

WiFiClient net;
PubSubClient mqtt(net);

// Sampling
const int SAMPLE_RATE = 250; // Hz (ECG typical 200-500Hz)
const int SAMPLE_PERIOD_MS = 1000 / SAMPLE_RATE;

// Buffers for simple filters
const int BUF_SIZE = 512;
int ecgBuf[BUF_SIZE];
int emgBuf[BUF_SIZE];
int bufIdx = 0;

// Heartbeat detection state
unsigned long lastBeat = 0;
int bpm = 0;

void connectWiFi(){
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(200);
}

void reconnectMQTT(){
  while (!mqtt.connected()){
    mqtt.connect("ESP32Patient");
    delay(200);
  }
}

float analogToVoltage(int raw){
  return (raw / 4095.0) * 3.3; // 12-bit ADC on ESP32
}

// Simple bandpass IIR filter (2nd order) coefficients can be used — here we apply moving average + highpass
float highpass(float in, float alpha=0.95) {
  static float prev_in = 0, prev_out = 0;
  float out = alpha * (prev_out + in - prev_in);
  prev_in = in; prev_out = out;
  return out;
}

float lowpass(float in, float alpha=0.1) {
  static float s = 0;
  s = s + alpha * (in - s);
  return s;
}

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
  connectWiFi();
  mqtt.setServer(MQTT_SERVER, 1883);

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)){
    // proceed without OLED
  }
  display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE);
}

unsigned long lastSampleTime = 0;

void loop() {
  // sampling loop
  unsigned long now = millis();
  if (now - lastSampleTime >= SAMPLE_PERIOD_MS) {
    lastSampleTime = now;
    int ecgRaw = analogRead(PIN_ECG);
    int emgRaw = analogRead(PIN_EMG);

    // convert and preprocess
    float ecgV = analogToVoltage(ecgRaw);
    float emgV = analogToVoltage(emgRaw);

    // simple filters
    float ecgHP = highpass(ecgV);
    float ecgFiltered = lowpass(ecgHP, 0.05);

    // store in circular buffer for visualization or further processing
    ecgBuf[bufIdx] = (int)(ecgFiltered * 1000);
    emgBuf[bufIdx] = (int)(emgV * 1000);
    bufIdx = (bufIdx + 1) % BUF_SIZE;

    // QRS-like detection: detect large positive slopes
    static float prev = 0;
    float diff = ecgFiltered - prev;
    prev = ecgFiltered;

    const float THRESH = 0.4; // tune per sensor/patient (volts scale after filters)
    static bool inBeat = false;
    if (diff > THRESH && !inBeat) {
      unsigned long t = millis();
      if (lastBeat != 0) {
        bpm = (int)(60000.0 / (t - lastBeat));
      }
      lastBeat = t;
      inBeat = true;
    }
    if (diff < 0) inBeat = false;

    // EMG envelope: moving RMS
    static float emgRmsBuf[50];
    static int emgIdx = 0;
    emgRmsBuf[emgIdx] = emgV*emgV;
    emgIdx = (emgIdx+1)%50;
    float rmsSum = 0;
    for (int i=0;i<50;i++) rmsSum += emgRmsBuf[i];
    float emgRms = sqrt(rmsSum/50.0);

    // Update OLED
    if (display.width() > 0){
      display.clearDisplay();
      display.setCursor(0,0);
      display.setTextSize(2);
      display.print("BPM:"); display.println(bpm);
      display.setTextSize(1);
      display.print("EMG RMS:"); display.println(emgRms,3);
      display.display();
    }

    // Publish every second
    static unsigned long lastPub = 0;
    if (millis() - lastPub > 1000) {
      lastPub = millis();
      if (!mqtt.connected()) reconnectMQTT();
      String payload = "{\"bpm\": " + String(bpm) + ", \"emg_rms\": " + String(emgRms,3) + "}";
      mqtt.publish(MQTT_TOPIC, payload.c_str());
      Serial.println(payload);
    }
  }
}

Firmware (ESP32)

Below is the Arduino code with explanation.

#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
  • These are libraries. WiFi for internet, PubSubClient for MQTT, Adafruit libraries for OLED.
const int PIN_ECG = 34; // ECG sensor pin
const int PIN_EMG = 35; // EMG sensor pin
  • We tell the ESP32 which pins the sensors are connected to.
const int SAMPLE_RATE = 250; // 250 samples per second
const int SAMPLE_PERIOD_MS = 1000 / SAMPLE_RATE;
  • ECG signals need fast sampling. 250 Hz is good enough.
unsigned long lastBeat = 0; // store last heartbeat time
int bpm = 0; // beats per minute
  • Variables to calculate BPM.

Filtering Functions

float highpass(float in, float alpha=0.95) { ... }
float lowpass(float in, float alpha=0.1) { ... }
  • ECG signals have noise. These are simple filters:

    • High-pass removes slow drift (baseline wander).
    • Low-pass removes high-frequency noise.

Loop

int ecgRaw = analogRead(PIN_ECG);
int emgRaw = analogRead(PIN_EMG);
  • Read analog values from sensors.
float ecgFiltered = lowpass(highpass(ecgV));
  • Apply filters to clean the ECG.
float diff = ecgFiltered - prev;
if (diff > THRESH && !inBeat) { ... }
  • Detect a QRS complex (heartbeat peak) by checking if the slope (difference) is big enough.
  • When a beat is found, calculate time since last beat → convert to BPM.
float emgRms = sqrt(rmsSum/50.0);
  • For EMG, calculate RMS (Root Mean Square) over 50 samples → shows muscle activity strength.

Display

display.print("BPM:"); display.println(bpm);
display.print("EMG RMS:"); display.println(emgRms,3);
  • Show BPM and EMG values on OLED.

MQTT

String payload = "{\"bpm\": " + String(bpm) + ", \"emg_rms\": " + String(emgRms,3) + "}";
mqtt.publish(MQTT_TOPIC, payload.c_str());
  • Send JSON data (BPM + EMG RMS) to cloud via MQTT.

7. How the algorithms work

ECG (QRS detection)

  • ECG front-end (AD8232) outputs a conditioned signal centered around mid-supply. After AC-coupling / highpass and lowpass smoothing, the algorithm looks for rapid positive slopes (derivative) that correspond to the QRS complex.
  • A very simple slope-threshold detector is shown above. For more reliability use Pan–Tompkins algorithm (bandpass -> derivative -> squaring -> moving-window integration -> adaptive threshold).

EMG (RMS envelope)

  • Raw EMG is high-frequency. Compute a short-window RMS (e.g., 50 samples at 250Hz → 200ms window) to derive muscle activity level.
  • Threshold the RMS to detect contractions or classify activity.

8. Calibration & tuning

  • ECG amplitude/thresholds: Adjust THRESH and filter time constants based on observed ECG amplitude. Test at rest first.
  • Sampling rate: Use 250–500 Hz for reliable ECG; higher sampling improves QRS timing resolution.
  • EMG RMS window: 100–250ms windows are common for muscle activation detection.

9. Testing & validation

  1. Place electrodes on a healthy volunteer and verify waveform on serial plotter or local display.
  2. Compare BPM against a reference (pulse oximeter or manual pulse) and tune detection thresholds.
  3. Introduce muscle contractions and verify EMG RMS rises accordingly.
  4. Test lead-off detection and add logic to alert when electrodes disconnect.

10. Troubleshooting

  • Flatline or noisy ECG: Check electrode contact, lead placement, grounding, and shielding. Ensure AD8232 reference is properly connected.
  • Excessive motion artifact: Reduce cable movement, use belt/elastic to secure electrodes, apply digital filters, or discard data during large motion.
  • EMG too noisy or low: Check electrode spacing and placement over muscle belly; verify module gain setting.
  • MQTT not connecting: Verify Wi‑Fi credentials and broker accessibility. Consider using secure MQTT with authentication for production.

11. Power, isolation & enclosure

  • For patient safety, use battery power and avoid any direct connection to mains.
  • In a clinical setting, medical isolation amplifiers and certified designs are mandatory.
  • Enclose electronics in a patient-safe housing and route only electrode leads through insulated openings.

Try this project in your browser

Compile code and simulate hardware output with EltroNerd Cloud IDE.

Launch Cloud IDE