Published

Portable Electrocardiograph (ECG)

A step by step tutorial that will guide you throught the complete development process. It also includes prototype testing. A success!

IntermediateFull instructions provided3 days38,231
Portable Electrocardiograph (ECG)

Things used in this project

Hardware components

Arduino Mega 2560
Arduino Mega 2560
×1
General Purpose Dual Op-Amp
Texas Instruments General Purpose Dual Op-Amp
×3
9V battery (generic)
9V battery (generic)
×3
9V Battery Clip
9V Battery Clip
×3
Tiga-Med Gold Electrodes
×3
Capacitor 18nF
×1
Capacitor 33nF
×1
Capacitor 1uF
×1
Capacitor 10uF
×1
Capacitor 20 uF
×1
Resistor 1k ohm
Resistor 1k ohm
×1
Resistor 10k ohm
Resistor 10k ohm
×2
Resistor 100k ohm
Resistor 100k ohm
×4
Resistor 200k ohm
×3
Resistor 1.1M ohm
×1
Resistor 3M ohm
×1

Software apps and online services

Arduino IDE
Arduino IDE
PartSim
Autodesk Circuits

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Code

Digital Filters

MATLAB
A IIR notch and Butterwoth high-pass filter.
No preview (download only).

ECG - Plot

Arduino
ECG code along with respective curve's visualization.
// -------------------------------------- Parameters ------------------------------------

const int analogInPin = A1;
int ledPin = 50;
int ledPin2 = 48;
int ledPin3 = 46;
int ledPin4 = 44;
int ledPin5 = 13;

// Originally collected signal (x, x1 and x2), notch filtered (y, y1, y2 and y3) and notch & low-pass filtered ECG signal (z, z1, z2 and z3).
// x = x(t); x1 = x(t-1); x1 = x(t-2). The same applies for y and z.
volatile double x = 0.0;
volatile double x1 = 0.0;
volatile double x2 = 0.0;

volatile double y = 0.0;
volatile double y1 = 0.0;
volatile double y2 = 0.0;
volatile double y3 = 0.0;

volatile double z = 0.0;
volatile double z1 = 0.0;
volatile double z2 = 0.0;
volatile double z3 = 0.0;

String bpm = "Normal Heart Rate";

double maxi;
double mini;

// Flag variable.
int aux = 0;

// Iterative variable.

int ii = 0;
int k = 0;
int l;

// Arrays
double timeVecto2 [512]; // Array which keeps 512 samples from the ECG signal (more or less, one cycle: sampling frequency = 500 Hz)
double timeVectorHigh2 [60]; // Saves 60 different instants (in ms) which corresponds to each R peak in the signal.
double res1;
double res2;
double resf;
double maxx = 0;
double minn = 10000;

// Defining combination of on and off pins to show each number.
const int numeral[10] = {
  B11111100, // 0
  B01100000, // 1
  B11011010, // 2
  B11110010, // 3
  B01100110, // 4
  B10110110, // 5
  B10111110, // 6
  B11100000, // 7
  B11111110, // 8
  B11110110, // 9
};

// Arduino pins used to connect all the 7 segments + dots.
const int segmentPins[] = { 22, 7, 8, 6, 5, 3, 2, 9};

// Number of digits allowed in the display.
const int numberofDigits = 4;

// Arduino pins used to connect all the 4 digits.
const int digitPins[numberofDigits] = {10, 11, 12, 4};
// --------------------------------------------------------------------------------------------------------- SETUP ---------------------------------------------------------------------------------------------------------

void setup() {

  Serial.begin(9600);

  pinMode(ledPin, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
  pinMode(ledPin4, OUTPUT);
  pinMode(ledPin5, OUTPUT);

  for (int i = 0; i < 8; i++)
    pinMode(segmentPins[i], OUTPUT); //set segment and DP pins to output

  //sets the digit pins as outputs
  for (int i = 0; i < numberofDigits; i++)
    pinMode(digitPins[i], OUTPUT);

  cli(); //stop interrupts

  //set timer1 interrupt at 500Hz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1Hz increments
  OCR1A = 3999;// = (16*10^6) / (8*500) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS11 bits for 8 prescaler
  TCCR1B |= (1 << CS11);
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);

  sei(); //allow interrupts

}

// ------------------------------------------------------------------------------------------------------ INTERRUPTOR ------------------------------------------------------------------------------------------------------

ISR(TIMER1_COMPA_vect) { // commands executed at each time interrupt

  // -------------------------------------------- Acquiring Data ------------------------------------------

  x = analogRead(analogInPin);

  // ----------------------------------------------- Filtering --------------------------------------------

  // Filtering around 40 and 60 Hz (notch).
  y = 0.2012 * x - 0.3256 * x1 + 0.2012 * x2 + 0.3256 * y1 + 0.5975 * y2;

  // Low-pass Filter 100 Hz.
  z = 0.0495 * y + 0.1486 * y1 + 0.1486 * y2 + 0.0495 * y3 + 1.1619 * z1 - 0.6959 * z2 + 0.1378 * z3;

  x2 = x1;
  x1 = x;

  y3 = y2;
  y2 = y1;
  y1 = y;

  z3 = z2;
  z2 = z1;
  z1 = z;

  // Saving z value
  timeVecto2[k] = z;

  k = (k + 1) % 512;

}

// --------------------------------------------------------------------------------------------------------- LOOP ---------------------------------------------------------------------------------------------------------

void loop() {

  // ------------------------------------------ Plotting ECG Curve ----------------------------------------

  Serial.print(5 * 1000 * z / 1023);

  Serial.println();

  // ----------------------------------------- Detecting Heart Beat ---------------------------------------
  maxi = 0;
  mini = 10000;

  showNumber(round(resf));

  for (l = 0; l < 512; l++) {

    if (maxi < timeVecto2[l]) {

      maxi = timeVecto2[l]; // Signal Maximum's amplitude detected during one cycle.

    }

    if (mini > timeVecto2[l]) {

      mini = timeVecto2[l]; // Signal Minimum's amplitude detected during one cycle.

    }

  }

  // ----------------------------------------- Detecting Heart Beat ---------------------------------------

  //

  if (maxi - mini < 400 && maxi - mini > 150) {

    digitalWrite(ledPin4, HIGH);

  } else {

    digitalWrite(ledPin4, LOW);
  }

  // DETECTING R PEAK.

  if ( z > 0.9 * maxi ) {

    if (aux == 0) {

      timeVectorHigh2[ii] = millis(); // Saving 30 R peak time instants.

      aux = 1;

      res1 = 60000 / (timeVectorHigh2[ii] - timeVectorHigh2[(ii - 1) % 60]);

      res2 = 60000 * 5 / (timeVectorHigh2[ii] - timeVectorHigh2[(ii - 5) % 60]);

      ii = (ii + 1) % 60;

    }
    digitalWrite(ledPin5, HIGH);   // sets the LED on.
  } else {
    digitalWrite(ledPin5, LOW);    // sets the LED off.
    aux = 0;
  }

  // ---------------------------------------- Calculating Heart Rate --------------------------------------

  if (res1 > 40 && res1 < 200 && res2 > 40 && res2 < 200) {

    if (ii > 2) {

      resf = 0.5 * res1 + 0.5 * res2; // Weighting heart rate shown in the 4 digits 7 segments display. 30 % to Instant heart rate and 70 % to an average along 30 peaks to stabilize the output value.

    }

  }



  // ----------------------------------- Detecting Unusual Conditions ---------------------------------

  // DETECTING SINUS HEART RATE (beats per minute - bpm).

  if (resf < 60) {

    bpm = "Sinus Bradycardia";

    digitalWrite(ledPin, LOW);
    digitalWrite(ledPin2, HIGH);
    digitalWrite(ledPin3, HIGH);

  } else if (resf >= 60 && resf < 100) {

    bpm = "Normal Heart Rate";

    digitalWrite(ledPin2, LOW);
    digitalWrite(ledPin, HIGH);
    digitalWrite(ledPin3, HIGH);

  } else {

    bpm = "Sinus Tachycardia";

    digitalWrite(ledPin3, LOW);
    digitalWrite(ledPin, HIGH);
    digitalWrite(ledPin2, HIGH);

  }

}

// -------------------------- 4 Digits 7 Segment Display (Auxiliar Functions) ------------------------

void showNumber (int number) {

  if (number == 0)
    showDigit (0, numberofDigits - 1); //display 0 in the rightmost digit.

  else {

    for (int digit = numberofDigits - 1; digit >= 0; digit--) {

      if (number > 0) {

        showDigit(number % 10, digit);

        number = number / 10;

      }

    }

  }

}

//Displays given number on a 7-segment display at the given digit position.
void showDigit (int number, int digit) {

  digitalWrite(digitPins[digit], HIGH);

  for (int segment = 1; segment < 8; segment++) {

    boolean isBitSet = bitRead(numeral[number], segment);

    isBitSet = ! isBitSet; // Remove this line if common cathode display.

    digitalWrite(segmentPins[segment], isBitSet);

  }

  delay(5);

  digitalWrite(digitPins[digit], LOW);

}

ECG - Print

Arduino
ECG code along with respective printing.
// -------------------------------------- Parameters ------------------------------------

const int analogInPin = A1;
int ledPin = 50;
int ledPin2 = 48;
int ledPin3 = 46;
int ledPin4 = 44;
int ledPin5 = 13;

// Originally collected signal (x, x1 and x2), notch filtered (y, y1, y2 and y3) and notch & low-pass filtered ECG signal (z, z1, z2 and z3).
// x = x(t); x1 = x(t-1); x1 = x(t-2). The same applies for y and z.
volatile double x = 0.0;
volatile double x1 = 0.0;
volatile double x2 = 0.0;

volatile double y = 0.0;
volatile double y1 = 0.0;
volatile double y2 = 0.0;
volatile double y3 = 0.0;

volatile double z = 0.0;
volatile double z1 = 0.0;
volatile double z2 = 0.0;
volatile double z3 = 0.0;

String bpm = "Normal Heart Rate";

double maxi;
double maxi2;
double mini;
double sum;

// Flag variables.
int aux = 0;
int aux2 = 0;
int aux3 = 0;
double auxx;

// Iterative variable.
int ii = 0;
int k = 0;
int l;

// Arrays
double timeVecto2 [512]; // Array which keeps 512 samples from the ECG signal (more or less, one cycle: sampling frequency = 500 Hz)
double timeVectorHigh2 [60]; // Saves 30 different instants (in ms) which corresponds to each R peak in the signal.
double timeVectorHigh3 [60]; // Time duration between 30 different R peaks.
double timeVectorLow2 [60]; // Saves 30 different instants (in ms) which corresponds to each S peak in the signal.
double timeVector2Medium [60]; // Saves 30 different instants (in ms) which corresponds to each T peak in the signal.
double rt [60]; // 30 different RT intervals from the ECG signal.
double rs[60]; // 30 different RS intervals from the ECG signal.
double st[60]; // 30 different ST intervals from the ECG signal.
double rs2;
double st2;
double rsf;
double stf;
double res1;
double res2;
double resf;
double maxx = 0;
double minn = 10000;

// Defining combination of on and off pins to show each number.
const int numeral[10] = {
  B11111100, // 0
  B01100000, // 1
  B11011010, // 2
  B11110010, // 3
  B01100110, // 4
  B10110110, // 5
  B10111110, // 6
  B11100000, // 7
  B11111110, // 8
  B11110110, // 9
};

// Arduino pins used to connect all the 7 segments + dots.
const int segmentPins[] = { 22, 7, 8, 6, 5, 3, 2, 9};

// Number of digits allowed in the display.
const int numberofDigits = 4;

// Arduino pins used to connect all the 4 digits.
const int digitPins[numberofDigits] = {10, 11, 12, 4};
// --------------------------------------------------------------------------------------------------------- SETUP ---------------------------------------------------------------------------------------------------------

void setup() {

  Serial.begin(9600);

  pinMode(ledPin, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
  pinMode(ledPin4, OUTPUT);
  pinMode(ledPin5, OUTPUT);

  for (int i = 0; i < 8; i++)
    pinMode(segmentPins[i], OUTPUT); //set segment and DP pins to output

  //sets the digit pins as outputs
  for (int i = 0; i < numberofDigits; i++)
    pinMode(digitPins[i], OUTPUT);

  cli(); //stop interrupts

  //set timer1 interrupt at 500Hz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1Hz increments
  OCR1A = 3999;// = (16*10^6) / (8*500) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS11 bits for 8 prescaler
  TCCR1B |= (1 << CS11);
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);

  sei(); //allow interrupts

}

// ------------------------------------------------------------------------------------------------------ INTERRUPTOR ------------------------------------------------------------------------------------------------------

ISR(TIMER1_COMPA_vect) { // commands executed at each time interrupt

  // -------------------------------------------- Acquiring Data ------------------------------------------

  x = analogRead(analogInPin);

  // ----------------------------------------------- Filtering --------------------------------------------

  // Filtering around 40 and 60 Hz (notch).
  y = 0.2012 * x - 0.3256 * x1 + 0.2012 * x2 + 0.3256 * y1 + 0.5975 * y2;

  // Low-pass Filter 100 Hz.
  z = 0.0495 * y + 0.1486 * y1 + 0.1486 * y2 + 0.0495 * y3 + 1.1619 * z1 - 0.6959 * z2 + 0.1378 * z3;

  x2 = x1;
  x1 = x;

  y3 = y2;
  y2 = y1;
  y1 = y;

  z3 = z2;
  z2 = z1;
  z1 = z;

  // Saving z value
  timeVecto2[k] = z; 

  k = (k + 1) % 512;
  
}

// --------------------------------------------------------------------------------------------------------- LOOP ---------------------------------------------------------------------------------------------------------

void loop() {

  // ------------------------------------------ Plotting ECG Curve ----------------------------------------


//  Serial.print(" ");
//  Serial.print(5 * 1000 * z / 1023);

  
//  Serial.println();

  // ----------------------------------------- Detecting Heart Beat ---------------------------------------
  maxi = 0;
  maxi2 = 0;
  mini = 10000;

  showNumber(round(resf));

  for (l = 0; l < 512; l++) {

    if (maxi < timeVecto2[l]) {

      maxi = timeVecto2[l]; // Signal Maximum's amplitude detected during one cycle.

    }

    if (mini > timeVecto2[l]) {

      mini = timeVecto2[l]; // Signal Minimum's amplitude detected during one cycle.

    }

    if (timeVecto2[l] < (maxi + mini) / 2 && timeVecto2[l] - timeVecto2[(l - 2)%512] > 0 && timeVecto2[l] - timeVecto2[(l - 2)%512] < 2 && abs(timeVecto2[l] - timeVecto2[(l - 10)%512]) < 5 ) {

      maxi2 = timeVecto2[l]; // Emprirical treshold for T peak detection.
      
    }

  }

  // ----------------------------------------- Detecting Heart Beat ---------------------------------------

  if (maxi - mini < 400 && maxi - mini > 100) {

    digitalWrite(ledPin4, HIGH);

  } else {

    digitalWrite(ledPin4, LOW);
  }

  // DETECTING R PEAK.

  if ( z > 0.9 * maxi ) {

    if (aux == 0) {

      timeVectorHigh2[ii] = millis(); // Saving 30 R peak time instants.
      timeVectorHigh3[(ii - 1)%60] = timeVectorHigh2[ii] - timeVectorHigh2[(ii - 1)%60];

      aux = 1;

      res1 = 60000 / timeVectorHigh3[(ii - 1)%60];

      res2 = 60000 * 5 / (timeVectorHigh2[ii] - timeVectorHigh2[(ii-5)%60]);

      
      
      // Print of the acquired data
      Serial.print("tS: ");
      Serial.print( timeVectorLow2[ii]);
      Serial.print("  ");
      Serial.print("tT: ");
      Serial.print( auxx);
      Serial.print("  ");
      Serial.print("tRS: ");
      Serial.print( rs[ii]);
      Serial.print("  ");
      Serial.print("tST: ");
      Serial.print( st[ii]);
      Serial.print("  ");
      Serial.print(" HR");
      Serial.print( res1);
      Serial.print("  ");
      
      Serial.println();
      Serial.print("ii: ");
      Serial.print( ii);
      Serial.print("  ");
      Serial.print("tR: ");
      Serial.print( timeVectorHigh2[ii]);
      Serial.print("  ");

      ii = (ii + 1) % 60;
    }
    digitalWrite(ledPin5, HIGH);   // sets the LED on.
  } else {
    digitalWrite(ledPin5, LOW);   // sets the LED off.     
      aux = 0;
  }

  // DETECTING S PEAK.

  if ( z < 1.3 * (mini)) {

    if(aux2 == 0){
      timeVectorLow2[ii] = millis(); // Saving 30 S peak time instants.
//      Serial.print("tS: ");
//      Serial.print( timeVectorLow2[ii]);
//      Serial.print(" ");
 
      aux2 = 1;
    } 
    
  } else {

    if(timeVectorLow2[ii] == 0){
      timeVectorLow2[ii] = timeVectorLow2[(ii-1)%60];
    }
    
    aux2 = 0;
  }

  //Serial.print(rs[3]);
  //Serial.println();


  // DETECTING T PEAK.

  if ( z > 0.95*maxi2 && z < 0.8*maxi) {

    auxx = millis();
    
    if (aux3 == 0 && (auxx - timeVectorHigh2[(ii-1)%60]) > 200) {

      timeVector2Medium[ii] = auxx;
//      Serial.print("tT: ");
//      Serial.print( auxx);
//      Serial.print(" ");
//      aux3 = 1;
      }
    aux3 = 1;
    
  } else {
    
    if(timeVector2Medium[ii] == 0){
      timeVector2Medium[ii] = timeVector2Medium[(ii-1)%60];
    }
    
    aux3 = 0;    
  }


  // ---------------------------------------- Calculating Heart Rate --------------------------------------

  if (res1 > 40 && res1 < 200 && res2 > 40 && res2 < 200) {

    resf = 0.5 * res1 + 0.5 * res2; // Weighting heart rate shown in the 4 digits 7 segments display. 50 % to Instant heart rate and 50 % to an average along 5 peaks to stabilize the output value.
  }

  // ----------------------------------- Detecting Unusual Conditions ---------------------------------

  // DETECTING SINUS HEART RATE (beats per minute - bpm).

  if (resf < 60) {

    bpm = "Sinus Bradycardia";

    digitalWrite(ledPin, LOW);
    digitalWrite(ledPin2, HIGH);
    digitalWrite(ledPin3, HIGH);

  } else if (resf >= 60 && resf < 100) {

    bpm = "Normal Heart Rate";

    digitalWrite(ledPin2, LOW);
    digitalWrite(ledPin, HIGH);
    digitalWrite(ledPin3, HIGH);

  } else {

    bpm = "Sinus Tachycardia";

    digitalWrite(ledPin3, LOW);
    digitalWrite(ledPin, HIGH);
    digitalWrite(ledPin2, HIGH);

  }

  // Serial.print(bpm);
  // Serial.print('\n');

  // DETECTING SINUS ARRHYTHMIA

  if (timeVectorHigh3[(ii - 2)%60] > maxx && ii > 1 && timeVectorHigh3[(ii - 2)%60] < 1200) {

    maxx = timeVectorHigh3[(ii - 2)%60];

  }

  if (timeVectorHigh3[(ii - 2)%60] < minn && ii > 1 && timeVectorHigh3[(ii - 2)%60] > 320) {

    minn = timeVectorHigh3[(ii - 2)%60];

  }

  if (maxx - minn > 160 && maxx - minn < 300 && bpm == "Sinus Tachycardia") {

    //    Serial.print("Arrhythmia");
    //    Serial.println();

  } else {

    //    Serial.print("Normal Heart Rate");
    //    Serial.println();

  }

  // ----------------------------------- Showing Heart Rate Over Time ---------------------------------

  //Serial.println(resf);

  // ----------------------------------- RS Interval ---------------------------------

  if (abs(timeVectorHigh2 [(ii - 1) % 60] - timeVectorLow2[ii]) < 120) {

    rs [ii] = abs(timeVectorHigh2 [(ii - 1) % 60] - timeVectorLow2[ii]);

    sum = 0;

    for (l = 0; l < 60; l++) {

      sum += rs[l];

    }

    rs2 = sum / 60;

    rsf = 0.3 * rs2 + 0.7 * rs[ii];

  } else {

    rs [ii] = rs[(ii - 1) % 60];

  }

  // ----------------------------------- ST Interval ---------------------------------

  if (abs(timeVector2Medium[ii] - timeVectorLow2[ii]) < 350 && abs(timeVector2Medium[ii] - timeVectorLow2[ii]) > 100) {

    st [ii] = abs(timeVector2Medium [ii] - timeVectorLow2[ii]);

    sum = 0;

    for (l = 0; l < 60; l++) {

      sum += st[l];

    }

    st2 = sum / 60;
    stf = 0.3 * st2 + 0.7 * st[ii];

  } else {

    st [ii] = st[(ii - 1) % 60];

  }

}


// -------------------------- 4 Digits 7 Segment Display (Auxiliar Functions) ------------------------

void showNumber (int number) {

  if (number == 0)
    showDigit (0, numberofDigits - 1); //display 0 in the rightmost digit.

  else {

    for (int digit = numberofDigits - 1; digit >= 0; digit--) {

      if (number > 0) {

        showDigit(number % 10, digit);

        number = number / 10;

      }

    }

  }

}

//Displays given number on a 7-segment display at the given digit position.
void showDigit (int number, int digit) {

  digitalWrite(digitPins[digit], HIGH);

  for (int segment = 1; segment < 8; segment++) {

    boolean isBitSet = bitRead(numeral[number], segment);

    isBitSet = ! isBitSet; // Remove this line if common cathode display.

    digitalWrite(segmentPins[segment], isBitSet);

  }

  delay(5);

  digitalWrite(digitPins[digit], LOW);

}

Credits

Thanks to Gonçalo Frazão.

Comments