Category Archives: Biomedical

Medical hacks and technology

OpenBCI with Plessey EPIC Electrodes

Conventional electrodes on EMG/EEG/ECG systems require low contact impedance and therefore often skin preparation is needed. Disposable electrodes can become expensive over time and to improve conductivity, with many electrodes gel has to be used, which is annoying. Capacitive electrodes have the advantage of being reusable and due to their etremely low contact impedance they can make biopotential measurements unobtrusive by incorporating them into seats or measuring through clothing.


After coming across commercial capacitive electrodes I was curious how they perform and made a carrier board for the QFN electrodes. The devices require bipolar power supplies from +- 2.4 to 5V so they are compatible with the OpenBCI V3 boards which have +-2.5V. The PS25255 is the type I finally purchased because it is low power and low gain.





The things look very interesting. The actual contact surface has a mirror finish and looks like silicon. Strangely there is a gap between the electrode contact area and the rest of the package. I think this might cause problems because the electrode is designed to be in direct contact with skin and dirt might accumulate inside this gap.
The 4 contacts (VCC, VSS, GND and SIGNAL) are on the bottom side of the QFN package and therefore my approach of mounting them to the PCB is by having plated holes in the pads and soldering from the bottom side. Use sufficient amounts of flux and the solder reflows down to the contacts and you have a perfect connection.





As a connector a 4-pin JST-style connector with 1.25mm pin pitch was used. I mounted it in a right angle to make the sensor flat. Crimping the female connectors using a PA-09 crimping tool was a challenge because they are so tiny.
Finally, I assembled an adapter board to break out the + and – 2.5V of the OpenBCI ‘Cyton’ board (AGND is already broken out on the right angle header) and tried to acquire some ECG. The signal outputs of both electrodes were connected to channel 1 an its gain was lowered to 2x (the PS25255 electrode itself already has a gain of 10). Also the channel has to be disconnected from SRB1 in the channel settings to make it ‘bipolar’.








If placing the electrodes directly on the skin you get quite good results without any skin preparation. I was also easily able to acquire EEG by placing one eclectrode on the forehead and the other one on the neck. Hair distorts the signal too much.





After putting both electrodes into plastic bags I was still able to acquire an ECG signal, but the signal was very sensitive to movement. Attempts to measure through a t-shirt were only successful if strong pressure was applied.
In all my experiments I did not use a drien right leg circuit or the bias output of the OpenBCI. Instead, only the software notch filter was enabled.


As a conclusion, I’d say that this was an interesting experiment, but 20$ per electrode seems a bit much for my hobby grade experiments. The Plessey EPIC electrodes seem to be usable for ECG, but their inability to penetrate hair for multichannel EEG is a little disappointing. It’s hard to tell how long the will last due to their peculiar packeage design. I will try some active electrode designs from the OpenEEG project with the OpenBCI soon, since they are easy to make, cheap and you can attach a comb electrode to it to be able to acquire EEG through hair hopefully more reliably than with passive electrodes.

Pulse Oximeter Hack

The pulse oximeter is a very useful and affordable medical gadget. It incorporates a quick and easy way of measuring your heart rate as well as calculating the blood oxygen saturation in one go! After having a look inside the Contec CMS50C and CMS50D which are both MSP430-based devices with OLED displays, it quickly became apparent, which one is easier to hack to extract the data.




The CMS50C (the one with the color OLED, available for <15$ on the usual platforms) has a nice line of well accessible and labeled pads, among which is also a UART interface that is streaming all of the useful values such as 7bit photoplethismographic curve data, the "bar graph", the SPO2 and heart rate value, a "beat detection bit" and various diagnostic bits at a sampling rate of 60Hz. An overview over the entire communication protocol can be found here. The serial port settings are a bit unusual: 4800 baud, and EVEN parity, so watch out for that, but other than that it’s a quite clever protocol that is storing all this information in only 5 bytes.



















The most essential connections are GND, TX, RST, the right pin of the trigger pushbutton and Batt. After that you should be able to completely control the thing from a microcontroller and harvest the data.
A code sample for Teensy 3.x is found below:




//sketch for reading the CMS50C pulse oximeter data using a Teensy 3.x

#define TRIG_PULSEOX 3 // has to be pulled to 3.3V to start the CMS50C
#define RST_PULSEOX 15
//vars for pulseOx
char incomingPulseOxbyte;
unsigned int byteCntPulse = 0;
char pulseCurve_PulseOx = 0;
char SPO2;
char HR_PulseOx;
boolean lastBitPulseOx = 0; //the MSB of the heart rate byte is hidden in a different byte - the one that holds the bar graph data
unsigned long pulseOxTrigTmr=0;
unsigned long pulseOxTrigInt=600000; // length of the pulse that triggers the CMS50C
unsigned long pulseOxRstTmr=0;
unsigned long pulseOxRstInt=800000;

boolean triggeredOx = false;
boolean resettedOx = false;

//packet management bytes for the Amarino Android app
char startFlag = 18;
char ack = 19;
char delimiter = 59; //';' in case we use more than 1 channel


void setup() {
  // put your setup code here, to run once:
  Serial1.begin(115200); // Bluetooth port
  Serial3.begin(4800, SERIAL_8E1); // Pulse oximeter CONTEC CMS 50C, annoying fact: it has EVEN PARITY!

   // make the pins outputs:
  pinMode(RST_PULSEOX, OUTPUT);
  pinMode(TRIG_PULSEOX, OUTPUT);
  
  digitalWrite(RST_PULSEOX, HIGH);
  
  
  triggerOx(); //turn on the PulseOx

}

void loop() {

  
  // put your main code here, to run repeatedly:
   //waiting to pull TRIG_PULSEOX LOW again 
  if((micros()-pulseOxTrigTmr)>pulseOxTrigInt && triggeredOx == true){
    digitalWrite(TRIG_PULSEOX, LOW);
    triggeredOx=false;
  }
  

   readPulseOx();

}

void readPulseOx() {
 
  //--------------------Pulse Oximeter data analysis----------------
  if (Serial3.available() > 0) {
    //pulseox data
    incomingPulseOxbyte = Serial3.read();
    if (byteCntPulse == 1) {
      //the second byte contains a 7 bit pulse wave value, Fs=60Hz
      pulseCurve_PulseOx = incomingPulseOxbyte;
      byteCntPulse++;
    } else if (byteCntPulse == 2) {
      //the 0-3rd bits of the third byte is the bar graph value .
      //the 6th bit is bit 7 of the heart rate value!
      if ( bitRead(incomingPulseOxbyte, 6)) {
        //if the heart rate is >= 128, this happens
        lastBitPulseOx = 1;
      } else
        lastBitPulseOx = 0;

      byteCntPulse++; //move on
    } else if (byteCntPulse == 3) {
      //
      if (lastBitPulseOx)
        //if the heart rate is >= 128, this happens
        HR_PulseOx = 128 + incomingPulseOxbyte;
      else
        HR_PulseOx = incomingPulseOxbyte;

      byteCntPulse++;
    } else if (byteCntPulse == 4) {
      //the last byte contains the most important SPO2 value
      SPO2 = incomingPulseOxbyte;
      byteCntPulse = 0; //we're done the data packet has 5 bytes in total
    } else {


      Serial1.print(startFlag);
      Serial1.print("P");
      Serial1.print(delimiter);
      Serial1.print(pulseCurve_PulseOx, DEC); //send the pulse curve value
      Serial1.print(delimiter);
      Serial1.print(HR_PulseOx, DEC); //send the heart rate
      Serial1.print(delimiter);
      Serial1.print(SPO2, DEC); //send SPO2 value!
      Serial1.print(ack);

    }

    //analyze the first byte. it has plenty of information about the data
    //the fourth bit of the first packet byte means "OK signal" if zero, otherwise it stands for "searching too long"
    //the seventh bit of the first byte has to be always set!!!!! (the only byte where this is the case) all other bytes have a bit 7 == 0
    if ( bitRead(incomingPulseOxbyte, 7)) {
      byteCntPulse++;
    }
  }

}

void triggerOx(){
//function to trigger the pulse oximeter button
   digitalWrite(TRIG_PULSEOX,HIGH);
   pulseOxTrigTmr=micros();
   triggeredOx=true;
 }


Better Blood Pressure Monitor Hack

After ordering the cheapest (wrist-type) blood pressure monitor I could find online (around 10$) to see how it performs in comparison to a “regular one”, I got a nice surprise.




I’ve hooked up a logic analyzer to as many testpoints on its PCB as possible and found out that it has a UART interface on board!! How convenient is that?
This ABP hack has proven to be quite impractical after all, although many people have shown interest in this this project and contacted me as it’s quite useful to have access to your blood pressure data and almost no open source blood pressure monitor projects exist.




 
















A sequence of bytes is sent from testpoint “P6” which turns out to be UART TXD @9600 baud 8N1. You can hook up ANY microcontroller to that and harvest the data easily. After a measurement is complete and successful, the device keeps sending the same byte sequence over and over again until powerdown.




This sequence is the one you see on the pictures: 255 – 254 – 008 – x – x – 0 – Syst. RR low byte – Syst. RR high byte – Diast. RR – heart rate.  As you can see the last 4 bytes of the frame is our desired data. For the systolic pressure we seem to require 2 bytes because 255 is apparently not enough. Sad but true 🙂 , then comes the diastolic pressure and finally the heart rate. The frame starts with 3 fixed bytes (255, 254 and 008 in decimal), followed by 2 variable bytes (haven’t figured out what those are for yet) and a zero byte followed by the data.




Throughout the measurement process itself constantly pressure data is streamed via UART. Each data packet begins with a ‘254’ (DEC) byte followed by one “cuff pressure” byte and one byte that represents the pulse oscillation.  Here’s what the data of one measuring cycle looks like if plotted over time:





























2 JST connectors were inserted into the enclosure. The 3 pin one gives you access to GND, the ON/OFF/START button to remotely trigger the device (just has to be pulled to GND) and finally to the TXD pin of the device. The 2 pin one is simply connected to the battery terminals to eliminate the need for batteries and power the thing from an external source.





Just look online for sphyngomanometers of similar appearance if you want to hack one too. This one has “CK” as a logo, but there are models around from different brands that look the same enclosure-wise. The exact model name of this device is “CK-101”




These devices perform reproducible values but you have to make sure to keep your wrist at heart level, as otherwise the values vary extremely. There is also literature that suggests that the wrist-type sphyngomanometers overestimate blood pressure because of distinct anatomical properties of the wrist arteries (https://www.ncbi.nlm.nih.gov/pubmed/15096904).




One application for an automatically triggered wrist sphyngomanometer I can think of is automated sleep measurement because during sleep your wrist is roughly at heart level anyway.

Homebrew OpenBCI V3 and Ultracortex

I’ve been intrigued by the ADS1299 chip by TI for a long time and after I came across the OpenBCI project and felt that my SMT skills were good enough, I finally decided to give the DIY approach a trial. The documentation is very good and almost complete and everything is open source, so get ready for an exciting blog post.
First off I downloaded Design Spark, which is the PCB editor the OpenBCI designs were made with. I used it to generate gerber files for getting the boards and solder paste stencils manufactured by OSH Park. By now they even have a function for importing Design Spark files.
Anyway, here are the Gerber files for the OpenBCI V3 8bit board. You should be able to place OSH orders using these:
OpenBCI 8bit OSH Park
For OSH Stencils:
OpenBCI 8bit OSH Stencils working
32bit Gerbers:
OpenBCI 32bit
32bit board stencil Gerbers:
OpenBCI 32bit Stencil
Daisy Board Gerbers (the 8bit board stencil partially fits the Daisy board, therefore I didn’t generate stencil Gerbers for that):
OpenBCI_Daisy

Note that there’s a bug on the Daisy PCB, which is described here.
luckily I knew about that issue and fixed it during the “pick and place” process prior to soldering.

I would recommend building the 32bit version right away since it is more capable (writes to SD, can be upgraded to 16 channels, fewer power rails, runs from one lithium cell, no need for 5V+, better microcontroller and last but not least: easier to build since it has fewer parts!).

Most parts were sourced from Digikey and Mouser. The SD card holder I found somewhere on ebay.
Also, I found picking and placing of tiny 0402 parts worked best with my DIY SMD vacuum tool and a thin needle. Especially where the components are densely packed (below the ADS1299) this method was very helpful. Before you begin, make sure to print out the OpenBCI_32bit_BOM and stick the component tapes with gluestick or sticky tape right next to the part indices. That way no parts get lost and you know exactly where each one belongs.

20160613_163743

20160613_164020

I had better results adding a drop a thin flux from a flux pen into the solder paste and mixing it. That way the solder paste is a bit smoother and reflows better. Remember: you can never use too much flux!

20160613_164152

20160613_164534

20160613_170951

The top side goes into the DIY reflow oven

20160613_171929

The bottom side is soldered with a hot air tool, being careful not to overheat the PCB.

20160613_175840

I’ve soldered 1.27mm header pins to the RFduino and female headers to the PCB because the wireless link is the bottleneck of the system. The headers allow to connect a different wireless module such as BT 2.1 or a USB-serial converter, which could be useful when planning to increase the sampling rate or using the system with Android devices.

20160613_185014

Using the PICKIT 3 programmer and MPLAB IPE (you can install ithe IPE without installing the IDE) to flash the Chipkit bootloader (can be found in the OpenBCI Github repository).

20160613_191010

The current draw of the 32bit board is about 60 mA

20160614_105347

My own dongle design:

20160614_105403

20160613_193246

Time to print the headware (“Ultracortex Nova/Supernova”)

The print was done on an Ultimaker 2 clone with the recommended slicer settings and turned out ok. It took about 12 hours and I experienced some problems while printing the first half. Some parts snapped off because the print head collided with plastic that was bending up during cooldown. This happened only during a few layers of the print (approx after one third) because the structure was not stable enough at that stage. I could repair the broken parts by filling the defects with hot glue.  When printing the second half I had an eye on the process and coud prevent that from happening.

20160616_075440

Used old PC tower cables to do the wiring. They were done in twisted pairs. All left electrodes are white and all right electrodes are colour-coded. That way you don’t need that many colors and nevertheless finding the corresponding electrode is easy.

20160617_103454

There are spring sets sold on the usual trading platform that contain the necessary springs to build the electrode inserts. The one below contains the “weak” as well as the “strong” spring for the “dummy inserts”. The weak spring had to be pulled apart a few mm to fit better.

20160701_092408

20160617_110640

20160617_145008

As you can see, I soldered the header of the Daisy board on the bottom side which takes up less space.

20160617_164954

OpenBCI parts <–this zip file contains my snap-on lid design that also covers the Daisy board as well as the earclip electrode design. Both parts can be found on Thingiverse as well:

http://www.thingiverse.com/thing:1635759

http://www.thingiverse.com/thing:1648317

20160620_080217

An 18650 holder with TP4056-based protection and charging circuit was placed on top to have a reliable power supply.

cover_on_daisy1

cover_on_daisy2

What you’re not being told: you have to print the “QUADSTAR” parts of the Ultracortex Nova/Supernova in PLA SOFT or any other elastomer filament. I had to modify my Prusa i3 extruder to prevent the soft 1.75 mm filament from kinking before entering into the bowden tube. This part fixes the issue: http://www.thingiverse.com/thing:1652091

Also I found that I had to turn off retractions because as soon as the filament passes the extruder drivewheel, it gets compressed and shoud not undergo this process twice because then it gets too damaged to be pushed into the bowden tube and extrusion simply stops at that point. Pain.

20160630_094022

Finished Iron Maiden, err…. I mean Ultracortex, wired for the 16 channel standard configuration according to the Processing GUI with as many “comfy inserts” as possible.

20160630_195026

20160630_195049

20160630_201633

One thing I noticed after using the Disposable / Reusable Dry EEG Electrode [TDE-200] was instant corrosion. Not that surprising given that two different metals (stainless steel M2 bolt and Ag/AgCl surface) come in contact. As you can see on the picture below on the left electrode the AgCl layer corroded away to that extent after only one day. As a comparison there’s a fresh electrode on the right. The only solution I see at the moment is to attach the electrodes only if they are needed.

20160701_081452

A slight modification was done to be able to add/remove electrodes more quickly: the electrode cables were soldered directly to the M2 nuts and superglued to the back of the electrode holder, so the nut stays attached to the plastic part even if there is no bolt pressing it down.

20160630_200402

Results:

Rocording with my eyes closed. Note the alpha peak in the FFT plot. As you can see not all channels are working. It’s not that easy getting 16 dry electrodes to work. I’m also having issues with channel 2 for some reason. Maybe there’s a bad solder joint somewhere on the PCB.

20160630_194627

Conclusions:

The OpenBCI V3 32bit + Daisy is a clean design and a good way to get 16 channels of electrophysiological data up and running and a great pplication for the ADS1299. The documentation is definitely good enough to build everything yourself, assuming you have some experience with SMT soldering. The DIY approach is also cheaper. I think you can get away with one third the price for the 32bit board and Daisy module, not including the cost of your labour of course. It’s a challenging project, but for me it was worth the effort. The DIY approach gives you the possibility to modify the design if you need to. I would like to use the potential of the ADS1299 more in the future and for example increase the sampling rate.
When it comes to the headware, my initial fascination faded a little when I discovered that the Ultracortex is quite painful to wear! The comb electrodes are no pleasure on your skin, let me tell you! But they do provide good signal quality almost instantaneously. The corrosion issue is also a significant one. I’m wondering how the signal quality changes as soon as the AgCl layer is gone completely. Maybe there’s a way of re-applying the layer by electrolysis in NaCl solution. This remains subject of further investigation. Other that those insights I find the design of the Ultracortex amazing! It’s a perfect example of great design specifically for 3D printing. Everything fits together perfectly well and looks great! Many thanks to the OpenBCI team for making their great work open source!

 

 

Simple ECG/EEG/EMG amplifier

20131207_195952




I’ve been experimenting with electrophysiology for some time and therefore the need to better understand the amplifier circuitry arose. I sampled some INA321 instrumentation amplifiers from Texas Instruments and started experimenting. After soldering them to a breakout board you can breadboard them.




The simple setup below can readily be attached to an analog to digital converter of a microcontroller and to 3 skin electrodes wherever you like to attach them 😉




Besides the instrumentation amp (chip on the left) only one additional dual low-noise opamp is used, which implements a buffer for reference voltage and a bessel filter for reducing mains line interference.



Eagle schmatic:




simplestEEG

Arduino ECG Monitor 2






Arduino ECG Monitor 2




This experimental setup is a combination of the the 3.3V OLED display setup and the EKG/EMG shield with improved code, which does averaging of 4 RR intervals in order to calculate the heart rate. Also an annoying QRS-beep is added 🙂



//Simple Arduino ECG monitor with SSD1306 OLED display
//Incorporates a simple QRS detection algorithm and heart rate calculation
//the interrupt-based code parts are based on the Olimex approach
//Requires the libraries included below!

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//connect the OLED display in the following way:
#define OLED_DC 11
#define OLED_CS 12
#define OLED_CLK 10
#define OLED_MOSI 9
#define OLED_RESET 13
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

#include <compat/deprecated.h>
#include <FlexiTimer2.h>
//http://www.arduino.cc/playground/Main/FlexiTimer2
#include <TimerOne.h>
//http://arduino.cc/playground/Code/Timer1

/*
Erklärung von cbi, sbi, outp und inp
Bei solchen Makros sollte man etwas mehr Klammern spendieren:

#define sbi(ADDRESS,BIT) ((ADDRESS) |= (1<<(BIT)))
#define cbi(ADDRESS,BIT) ((ADDRESS) &= ~(1<<(BIT)))
#define outp(VAL,ADRESS) ((ADRESS) = (VAL))
#define inp(VAL) (VAL)
The outb( ) function provides a C language interface to the machine instruction that writes a byte to an 8 bit I/O port using the I/O address space instead of the memory address space.
*/

// All definitions
#define NUMCHANNELS 6
#define HEADERLEN 4
#define PACKETLEN (NUMCHANNELS * 2 + HEADERLEN + 1)    //6*2+4+1
#define SAMPFREQ 256                   // ADC sampling rate 256
#define TIMER2VAL (1024/(SAMPFREQ))    // Set 256Hz sampling frequency
#define PWM_OUT 9                      // Number of pin used for generating CAL_SIG
#define PWMFREQ 10        //10Hz for Calibration signal             
//#define LED1  13

// Global constants and variables
char const channel_order[]= { 0, 1, 2, 3, 4, 5 };
volatile unsigned char TXBuf[PACKETLEN];  //The transmission packet
volatile unsigned char TXIndex;           //Next byte to write in the transmission packet.
volatile unsigned char CurrentCh;         //Current channel being sampled.

//~~~~~~~~~~
// Functions
//~~~~~~~~~~

/****************************************************/
/*  Function name: Toggle_LED1                      */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Switches-over LED1.                   */
/****************************************************/
//void Toggle_LED1(void){
//
// if((digitalRead(LED1))==HIGH){
//   digitalWrite(LED1,LOW);
//  }
//  else{
//   digitalWrite(LED1,HIGH);
//  }
//}
/****************************************************/
/*  Function name: setup                            */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Initializes all peripherals           */
/****************************************************/
void setup() {

 noInterrupts();  // Disable all interrupts before initialization
 
 // LED1
// pinMode(LED1, OUTPUT);  //Setup LED1 direction
// digitalWrite(LED1,LOW); //Setup LED1 state
 
 //Write packet header and footer
 TXBuf[0] = 0xa5;  //Sync 0
 TXBuf[1] = 0x5a;  //Sync 1
 TXBuf[2] = 2;     //Protocol version
 TXBuf[3] = 0;     //Packet counter
 
 // ADC
 // Timings for sampling of one 10-bit AD-value:
 // XTAL = 16000000MHz
 // prescaler > ((XTAL / 200kHz) = 80 =>
 // prescaler = 128 (ADPS2 = 1, ADPS1 = 1, ADPS0 = 1)
 // ADCYCLE = XTAL / prescaler = 125000Hz or 8 us/cycle
 // 14 (single conversion) cycles = 112 us
 // 26 (1st conversion) cycles = 208 us
 outb(ADMUX, 0);         //Select channel 0
 outb(ADCSRA, ((1<<ADPS2) | (1<<ADPS1)| (1<<ADPS0))); //Prescaler = 128, free running mode = off, interrupts off.
 sbi(ADCSRA, ADIF);  //Reset any pending ADC interrupts  
 sbi(ADCSRA, ADEN);  //Enable the ADC                    
 
 // Serial Port
 outb(UBRR0, 16);              //Set speed to 57600 bps     
 outb(UCSR0B, (1<<TXEN0));     //Enable USART Transmitter.
 
 // Timer1
 // It's used for calibration signal generation: CAL_SIG via PWM.
 // CAL_SIG is used like reference signal when setting-up SHIELD-EKG/EMG's channel gain
 // During normal operation this signal is not required so it can be disabled with uncommenting te row below!
 /*
 pinMode(PWM_OUT, OUTPUT);    //Set PWM_OUT direction
 digitalWrite(PWM_OUT,LOW);   //Set PWM_OUT state
 Timer1.initialize((1000000/(PWMFREQ))); // initialize timer1, and set a 1/10 second period = 10Hz ->freq. of cal signal should be 10-14Hz (schematic)
 Timer1.pwm(PWM_OUT, 512);             // setup pwm on pin 9, 50% duty cycle
 //Timer1.disablePwm(PWM_OUT); // Uncomment if CAL_SIG is not requiered
 */

 // Timer2
 // Timer2 is used for setting ADC sampling frequency.
 
/*****************************************************************
Methods of the FlexiTimer2 library:

FlexiTimer2::set(unsigned long units, double resolution, void (*f)())
    this function sets a time on units time the resolution for the overflow. Each overflow, "f" will be called. "f" has to be declared void with no parameters.
    E.g. units=1, resolution = 1.0/3000 will call f 3000 times per second, whereas it would be called only 1500 times per second when units=2.
FlexiTimer2::set(unsigned long ms, void (*f)())
    this function sets a time on ms (1/1000th of a second) for the overflow. Each overflow, "f" will be called. "f" has to be declared void with no parameters.
    Shorthand for calling the function above with resolution = 0.001.
FlexiTimer2::start()
    enables the interrupt.
FlexiTimer2::stop()
    disables the interrupt.
*******************************************************************/
 FlexiTimer2::set(TIMER2VAL, Timer2_Overflow_ISR); //TIMER2VAL was (1024/(SAMPFREQ)) in ms =4, SAMPLEFREQ was 256
 FlexiTimer2::start();  //enable the Interrupt....
 
 // MCU sleep mode = idle.
 outb(MCUCR,(inp(MCUCR) | (1<<SE)) & (~(1<<SM0) | ~(1<<SM1) | ~(1<<SM2)));
 
 interrupts();  // Enable all interrupts after initialization has been completed
  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC);
  // init done
 display.clearDisplay();   // clears the screen and buffer
  
  display.setTextSize(1);
  display.setTextColor(WHITE);
  
}
/****************************************************/
/*  Function name: Timer2_Overflow_ISR              */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Determines ADC sampling frequency.    */
/****************************************************/
void Timer2_Overflow_ISR()    //alle 4ms wird das ausgeführt
{
  // Toggle LED1 with ADC sampling frequency /2
  //Toggle_LED1();
 
  CurrentCh = 0;
  // Write header and footer:
  // Increase packet counter (fourth byte in header)
   //Write packet header and footer
 /**********zur Erinnerung: der Header**********
 TXBuf[0] = 0xa5;  //Sync 0
 TXBuf[1] = 0x5a;  //Sync 1
 TXBuf[2] = 2;     //Protocol version
 TXBuf[3] = 0;     //Packet counter
 ***********************************/
 TXBuf[3]++;
  //the whole packet is /6*2+4+1=17byte
  //Get state of switches on PD2..5, if any (last byte in packet).
  TXBuf[2 * NUMCHANNELS + HEADERLEN] = (inp(PIND) >> 2) &0x0F;  //2* NUMCHANNELS, weil jeder CHannel 2 byte hat damit 1024 reinpasst
 
  cbi(UCSR0B, UDRIE0); //Ensure Data Register Empty Interrupt is disabled.
  sbi(ADCSRA, ADIF);   //Reset any pending ADC interrupts
  sbi(ADCSRA, ADIE);   //Enable ADC interrupts. 
  sbi(ADCSRA, ADSC) ;  // Start conversion!!!
  //Next interrupt will be ISR(ADC_vect)
}

/****************************************************/
/*  Function name: ISR(ADC_vect)                    */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Reads ADC's current selected channel  */
/*            and stores its value into TXBuf. When */
/*            TXBuf is full, it starts sending.     */
/****************************************************/
ISR(ADC_vect)
{
 volatile unsigned char i;    //volatile??
 
 i = 2 * CurrentCh + HEADERLEN;  //also wird i auf 4 gesetzt wenn CurrentCh==0 und unten das 5. byte beschrieben,danach TxBuf[4] ([3] ist das letzte vom Header)
 TXBuf[i+1] = inp(ADCL);      //ADC data register LOW byte
 TXBuf[i] = inp(ADCH);        //ADC data register HIGH byte
 CurrentCh++;  
 if (CurrentCh < NUMCHANNELS)
 {
  outb(ADMUX, (channel_order[CurrentCh])); //Select the next channel.
  sbi(ADCSRA, ADSC) ;                   //Start conversion!!! (set ADSC-bit in ADCSRA-Register)
 }
 else
 {
   //this gets executed first....prior to the stuff above
  outb(ADMUX, channel_order[0]);      //Prepare next conversion, on channel 0.
  cbi(ADCSRA, ADIE);    //Disable ADC interrupts to prevent further calls to ISR(ADC_vect). oben hiess es sbi!!!!!!
  outb(UDR0, TXBuf[0]); //Send first Packet's byte: Sync 0
  sbi(UCSR0B, UDRIE0);  //USART Data Register Empty Interrupt Enable
  TXIndex = 1;          //Next interrupt will be ISR(USART_UDRE_vect)
 }
}

/****************************************************/
/*  Function name: ISR(USART_UDRE_vect)             */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Sends remaining part of the Packet.   */
/****************************************************/
ISR(USART_UDRE_vect){
 
 outb(UDR0, TXBuf[TXIndex]);  //Send next byte
 TXIndex++;
 /******hier also***
 ch0hb = TxBuf[4];
 ch0lb = TxBuf[5];
 *******************/

 
 if (TXIndex == PACKETLEN)    //See if we're done with this packet
 {
   cbi(UCSR0B, UDRIE0);    //USART Data Register Empty Interrupt Disable
                              //Next interrupt will be Timer2_Overflow_ISR()
 }
}


//function for fusion of the ADCL and ADCH byte

unsigned int weiterverarbeitung(volatile unsigned char high_byte, volatile unsigned char low_byte)
{
 unsigned int value = ((high_byte&0x0f)*256)+(low_byte);
 return(value);
}

/****************************************************/
/*  Function name: loop                             */
/*  Parameters                                      */
/*    Input   :  last 2 channel bytes of the packet */
/*    Output  :  to display                         */
/*    Action: Draws ECG, detects QRS, calculates HR */
/****************************************************/
unsigned long Start, Finished = 0;
int heart_rate[4];
int heart_rate_avg;
float RR_interval = 0.0;
unsigned int Delay = 2;
unsigned int QRS_counter = 0;
int thisdot = 0;
int prevdot = 0;



void loop() {
 
  //"heart rate"
  display.setCursor(1,52);          
  display.print("heart rate:");
  display.display(); // show it
  
  //show heart rate once per screen
  if(heart_rate_avg<220)
      {
          display.setCursor(80,52);          
          display.print(heart_rate_avg);
          display.display(); // show it

      }
 
  //draw the actual graph: (128 = display width)
  for(int i=0; i<128; i++)
  {
    
    Finished = 0;
    //get the ADC value and scale it to the higth of the display
    unsigned int val = weiterverarbeitung(TXBuf[14],TXBuf[15]);  //using A5 and extracting the last 2 channel bytes out of the packet
    unsigned int y = map (val, 0, 1023, 64, 0);      //oben=0!!
    thisdot = y;
    
    //calculate the graph slope for QRS detection
    //slope can be negative so it has to be an SIGNED int
    int slope = prevdot - thisdot;
    
    //QRS complex detected above a certain threshold
     if (slope >= 8 && Start == 0)
      {
        //QRS Beep, use Pin 6 to not interfere with Timer 2!!
        tone(6, 2000, 50); 
        //start "stop watch"
        Start = millis();   
      }
      else if(slope >= 8 && Start > 0)
    {
      //QRS Beep
      tone(6, 2000, 50); 
      //stop
      Finished = millis();
      //calculate a RR interval
      RR_interval = Finished - Start;
              
      if(RR_interval>=150)  //refractory period, RR-intervals should be longer than this (filter method)
        {
          RR_interval = RR_interval/1000;  //convert to seconds
          heart_rate[QRS_counter] = 60/RR_interval; //collect 4 intervals
          QRS_counter ++;   
          
          //averaging calculation
          if(QRS_counter >= 3){
            for(int j = 0; j<4; j++){
              heart_rate_avg += heart_rate[j];
            }
            heart_rate_avg /= 4;
            QRS_counter = 0;
          }
        }
      //reset Start value for time measurement
      Start = 0;
      
    }
      
    //Draw graph  
    display.drawPixel(i, y, WHITE);
    display.display();
    delay(Delay);
    
    prevdot = thisdot;
    thisdot = 0;
    slope = 0; 
  } 
display.clearDisplay();
 
}

Compact Arduino ECG Monitor






20120803_000613




As a medical professional I’ve always been interested in experimenting with biosignals. I’ve made a couple of attempts to build the analog part of a simple ECG amplifier, but encountered some obstacles. Some day I’ve found this well-designed ECG/EMG shield by Olimex which uses an instrumentation amplifier and fits on an Arduino board. It comes with some sample code as well. I was suprised that it uses old C commands such as cbi, sbi, outp() and inp() but I learned a lot while going through the code and understanding it. It was originally written to interface with the ElectricGuru EEG-software via the serial port and processes the input signal of up to 6 of those shields, since they can be stacked together easily.




First of all I wanted to play with the ECG signal and since I had just figured out how to use a KS0108 GLCD, I just combined both to make this neat biofeedback device. One obstacle I came across when trying to connect the LCD to a microcontroller is figuring out which model it is! So take a look at the data sheet or whatever, because there are 3 types of those 20pin displays! The glcd library documentation on contains a table that visualizes the pinouts. Because there are many wires you have to mess around with, I simply soldered a female header on a Proto Shield and hard wired everything.




Another function I wanted to implement is QRS-detection. QRS complexes are those pointed, high spikes in an ECG which represent the electrical activity in the main muscular mass of the heart (myocardium). Their detection is quite easy by measuring the biggest slopes and and can give a more or less reliable representation of one cycle of cardial action. My code calculates the time between 2 detected “heartbeats” (RR interval) and displays that as the heart rate. This has the advantage over averaging a couple of heartbeats that you can directly observe how your heartbeats vary.




Many interestings things can be done with this kind of setup. The next step will be logging the ECG by either writing it to a SD card or sending the data to a PC via bluetooth and logging there (patient insulation from mains powered devices and less disturbing influence from the mains line). This is a very powerful tool for diagnostic purposes. For example arrythmias that occur under certain circumstances can be diagnosed or heart rate variability can be measured, which can give you information on the influence of the autonomic nervous system on cardial action…




Interrupt-based code, modified for use with the glcd:

/**********************************************************/
/* Demo program for:                                      */
/*    Board: SHIELD-EKG/EMG + Olimexino328                */
/*  Manufacture: OLIMEX                                   */
/*  COPYRIGHT (C) 2012                                    */
/*  Designed by:  Penko Todorov Bozhkov                   */
/*   Module Name:   Sketch                                */
/*   File   Name:   ShieldEkgEmgDemo.pde                  */
/*   Revision:  initial                                   */
/*   Date: 01.02.2012                                     */
/*   Built with Arduino C/C++ Compiler, version: 1.0      */

/*EXTENDED BY insanity wolf */

/*This version is for monitoring the signal directly on a KS0108 graphical LCD display*/

/**********************************************************/
/**********************************************************
Purpose of this programme is to give you an easy way to
connect Olimexino328 to ElectricGuru(TM), see:
http://www.realization.org/page/topics/electric_guru.htm
where you'll be able to observe yours own EKG or EMG signal.
It is based on:
***********************************************************
* ModularEEG firmware for one-way transmission, v0.5.4-p2
* Copyright (c) 2002-2003, Joerg Hansmann, Jim Peters, Andreas Robinson
* License: GNU General Public License (GPL) v2
***********************************************************
For proper communication packet format given below have to be supported:
///////////////////////////////////////////////
////////// Packet Format Version 2 ////////////
///////////////////////////////////////////////
// 17-byte packets are transmitted from Olimexino328 at 256Hz,
// using 1 start bit, 8 data bits, 1 stop bit, no parity, 57600 bits per second.

// Minimial transmission speed is 256Hz * sizeof(Olimexino328_packet) * 10 = 43520 bps.

struct Olimexino328_packet
{
  uint8_t    sync0;        // = 0xa5
  uint8_t    sync1;        // = 0x5a
  uint8_t    version;    // = 2 (packet version)
  uint8_t    count;        // packet counter. Increases by 1 each packet.
  uint16_t    data[6];    // 10-bit sample (= 0 - 1023) in big endian (Motorola) format.
  uint8_t    switches;    // State of PD5 to PD2, in bits 3 to 0.
};
*/
/**********************************************************/
#include <glcd.h>
//http://www.arduino.cc/playground/Code/GLCDks0108

#include "fonts/allFonts.h"         // system and arial14 fonts are used
#include "bitmaps/allBitmaps.h"       // all images in the bitmap dir
gText textArea;              // a text area to be defined later in the sketch


#include <compat/deprecated.h>
#include <FlexiTimer2.h>
//http://www.arduino.cc/playground/Main/FlexiTimer2
#include <TimerOne.h>
//http://arduino.cc/playground/Code/Timer1

/*
Erklärung von cbi, sbi, outp und inp
Bei solchen Makros sollte man etwas mehr Klammern spendieren:

#define sbi(ADDRESS,BIT) ((ADDRESS) |= (1<<(BIT)))
#define cbi(ADDRESS,BIT) ((ADDRESS) &= ~(1<<(BIT)))
#define outp(VAL,ADRESS) ((ADRESS) = (VAL))
#define inp(VAL) (VAL)
The outb( ) function provides a C language interface to the machine instruction that writes a byte to an 8 bit I/O port using the I/O address space instead of the memory address space.
*/

// All definitions
#define NUMCHANNELS 6
#define HEADERLEN 4
#define PACKETLEN (NUMCHANNELS * 2 + HEADERLEN + 1)    //6*2+4+1
#define SAMPFREQ 256                   // ADC sampling rate 256
#define TIMER2VAL (1024/(SAMPFREQ))    // Set 256Hz sampling frequency
#define PWM_OUT 9                      // Number of pin used for generating CAL_SIG
#define PWMFREQ 10        //10Hz for Calibration signal             
#define LED1  13

// Global constants and variables
char const channel_order[]= { 0, 1, 2, 3, 4, 5 };
volatile unsigned char TXBuf[PACKETLEN];  //The transmission packet
volatile unsigned char TXIndex;           //Next byte to write in the transmission packet.
volatile unsigned char CurrentCh;         //Current channel being sampled.

//~~~~~~~~~~
// Functions
//~~~~~~~~~~

/****************************************************/
/*  Function name: Toggle_LED1                      */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Switches-over LED1.                   */
/****************************************************/
void Toggle_LED1(void){

 if((digitalRead(LED1))==HIGH){
   digitalWrite(LED1,LOW);
  }
  else{
   digitalWrite(LED1,HIGH);
  }
}

/****************************************************/
/*  Function name: setup                            */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Initializes all peripherals           */
/****************************************************/
void setup() {

 noInterrupts();  // Disable all interrupts before initialization
 
 // LED1
 pinMode(LED1, OUTPUT);  //Setup LED1 direction
 digitalWrite(LED1,LOW); //Setup LED1 state
 
 //Write packet header and footer
 TXBuf[0] = 0xa5;  //Sync 0
 TXBuf[1] = 0x5a;  //Sync 1
 TXBuf[2] = 2;     //Protocol version
 TXBuf[3] = 0;     //Packet counter
 
 // ADC
 // Timings for sampling of one 10-bit AD-value:
 // XTAL = 16000000MHz
 // prescaler > ((XTAL / 200kHz) = 80 =>
 // prescaler = 128 (ADPS2 = 1, ADPS1 = 1, ADPS0 = 1)
 // ADCYCLE = XTAL / prescaler = 125000Hz or 8 us/cycle
 // 14 (single conversion) cycles = 112 us
 // 26 (1st conversion) cycles = 208 us
 outb(ADMUX, 0);         //Select channel 0
 outb(ADCSRA, ((1<<ADPS2) | (1<<ADPS1)| (1<<ADPS0))); //Prescaler = 128, free running mode = off, interrupts off.
 sbi(ADCSRA, ADIF);  //Reset any pending ADC interrupts  
 sbi(ADCSRA, ADEN);  //Enable the ADC                    
 
 // Serial Port
 outb(UBRR0, 16);              //Set speed to 57600 bps     
 outb(UCSR0B, (1<<TXEN0));     //Enable USART Transmitter.
 
 // Timer1
 // It's used for calibration signal generation: CAL_SIG via PWM.
 // CAL_SIG is used like reference signal when setting-up SHIELD-EKG/EMG's channel gain
 // During normal operation this signal is not required so it can be disabled with uncommenting te row below!
 /*
 pinMode(PWM_OUT, OUTPUT);    //Set PWM_OUT direction
 digitalWrite(PWM_OUT,LOW);   //Set PWM_OUT state
 Timer1.initialize((1000000/(PWMFREQ))); // initialize timer1, and set a 1/10 second period = 10Hz ->freq. of cal signal should be 10-14Hz (schematic)
 Timer1.pwm(PWM_OUT, 512);             // setup pwm on pin 9, 50% duty cycle
 //Timer1.disablePwm(PWM_OUT); // Uncomment if CAL_SIG is not requiered
 */

 // Timer2
 // Timer2 is used for setting ADC sampling frequency.
 
/*****************************************************************
Methods of the FlexiTimer2 library:

FlexiTimer2::set(unsigned long units, double resolution, void (*f)())
    this function sets a time on units time the resolution for the overflow. Each overflow, "f" will be called. "f" has to be declared void with no parameters.
    E.g. units=1, resolution = 1.0/3000 will call f 3000 times per second, whereas it would be called only 1500 times per second when units=2.
FlexiTimer2::set(unsigned long ms, void (*f)())
    this function sets a time on ms (1/1000th of a second) for the overflow. Each overflow, "f" will be called. "f" has to be declared void with no parameters.
    Shorthand for calling the function above with resolution = 0.001.
FlexiTimer2::start()
    enables the interrupt.
FlexiTimer2::stop()
    disables the interrupt.
*******************************************************************/
 FlexiTimer2::set(TIMER2VAL, Timer2_Overflow_ISR); //TIMER2VAL was (1024/(SAMPFREQ)) in ms =4, SAMPLEFREQ was 256
 FlexiTimer2::start();  //enable the Interrupt....
 
 // MCU sleep mode = idle.
 outb(MCUCR,(inp(MCUCR) | (1<<SE)) & (~(1<<SM0) | ~(1<<SM1) | ~(1<<SM2)));
 
 interrupts();  // Enable all interrupts after initialization has been completed
 
 GLCD.Init();
 GLCD.SelectFont(System5x7, BLACK); // font for the default text area
}

/****************************************************/
/*  Function name: Timer2_Overflow_ISR              */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Determines ADC sampling frequency.    */
/****************************************************/
void Timer2_Overflow_ISR()    //alle 4ms wird das ausgeführt
{
  // Toggle LED1 with ADC sampling frequency /2
  Toggle_LED1();
 
  CurrentCh = 0;
  // Write header and footer:
  // Increase packet counter (fourth byte in header)
   //Write packet header and footer
 /**********zur Erinnerung: der Header**********
 TXBuf[0] = 0xa5;  //Sync 0
 TXBuf[1] = 0x5a;  //Sync 1
 TXBuf[2] = 2;     //Protocol version
 TXBuf[3] = 0;     //Packet counter
 ***********************************/
 TXBuf[3]++;
  //the whole packet is /6*2+4+1=17byte
  //Get state of switches on PD2..5, if any (last byte in packet).
  TXBuf[2 * NUMCHANNELS + HEADERLEN] = (inp(PIND) >> 2) &0x0F;  //2* NUMCHANNELS, weil jeder CHannel 2 byte hat damit 1024 reinpasst
 
  cbi(UCSR0B, UDRIE0); //Ensure Data Register Empty Interrupt is disabled.
  sbi(ADCSRA, ADIF);   //Reset any pending ADC interrupts
  sbi(ADCSRA, ADIE);   //Enable ADC interrupts.
  sbi(ADCSRA, ADSC) ;  // Start conversion!!!
  //Next interrupt will be ISR(ADC_vect)
}

/****************************************************/
/*  Function name: ISR(ADC_vect)                    */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Reads ADC's current selected channel  */
/*            and stores its value into TXBuf. When */
/*            TXBuf is full, it starts sending.     */
/****************************************************/
ISR(ADC_vect)
{
 volatile unsigned char i;    //volatile??
 
 i = 2 * CurrentCh + HEADERLEN;  //also wird i auf 4 gesetzt wenn CurrentCh==0 und unten das 5. byte beschrieben,danach TxBuf[4] ([3] ist das letzte vom Header)
 TXBuf[i+1] = inp(ADCL);      //ADC data register LOW byte
 TXBuf[i] = inp(ADCH);        //ADC data register HIGH byte
 CurrentCh++;  
 if (CurrentCh < NUMCHANNELS)
 {
  outb(ADMUX, (channel_order[CurrentCh])); //Select the next channel.
  sbi(ADCSRA, ADSC) ;                   //Start conversion!!! (set ADSC-bit in ADCSRA-Register)
 }
 else
 {
   //this gets executed first....prior to the stuff above
  outb(ADMUX, channel_order[0]);      //Prepare next conversion, on channel 0.
  cbi(ADCSRA, ADIE);    //Disable ADC interrupts to prevent further calls to ISR(ADC_vect). oben hiess es sbi!!!!!!
  outb(UDR0, TXBuf[0]); //Send first Packet's byte: Sync 0
  sbi(UCSR0B, UDRIE0);  //USART Data Register Empty Interrupt Enable
  TXIndex = 1;          //Next interrupt will be ISR(USART_UDRE_vect)
 }
}

/****************************************************/
/*  Function name: ISR(USART_UDRE_vect)             */
/*  Parameters                                      */
/*    Input   :  No                                */
/*    Output  :  No                                 */
/*    Action: Sends remaining part of the Packet.   */
/****************************************************/
ISR(USART_UDRE_vect){
 
 outb(UDR0, TXBuf[TXIndex]);  //Send next byte
 TXIndex++;
 /******hier also***
 ch0hb = TxBuf[4];
 ch0lb = TxBuf[5];
 *******************/

 
 if (TXIndex == PACKETLEN)    //See if we're done with this packet
 {
   cbi(UCSR0B, UDRIE0);    //USART Data Register Empty Interrupt Disable
                              //Next interrupt will be Timer2_Overflow_ISR()
 }
}


//function for fusion of the ADCL and ADCH byte

unsigned int weiterverarbeitung(volatile unsigned char high_byte, volatile unsigned char low_byte)
{
 unsigned int value = ((high_byte&0x0f)*256)+(low_byte);
 return(value);
}

/****************************************************/
/*  Function name: loop                             */
/*  Parameters                                      */
/*    Input   :  last 2 channel bytes of the packet */
/*    Output  :  to display                         */
/*    Action: Draws ECG, detects QRS, calculates HR */
/****************************************************/
unsigned long Start, Finished = 0;
float heart_rate = 0.0;
float RR_interval = 0.0;
unsigned int Delay = 9;
int thisdot = 0;
int prevdot = 0;






void loop() {
 

 
  for(int i=0; i<GLCD.Width; i++)
  {
    
    Finished = 0;
    unsigned int val = weiterverarbeitung(TXBuf[14],TXBuf[15]);  //using A5 and extracting the last 2 channel bytes out of the packet
    unsigned int y = map (val, 0, 1023, 64, 0);      //oben=0!!
    thisdot = y;
    
    //slope can be negative so it has to be an SIGNED int
    int slope = prevdot - thisdot;
    
     if (slope >= 8 && Start == 0)
      {
        Start = millis();   
      }
      else if(slope >= 8 && Start > 0)
    {
      
      Finished = millis();
        RR_interval = Finished - Start;
              
      if(RR_interval>=150)  //refractory period
        {
          RR_interval = RR_interval/1000;  //convert to seconds
          heart_rate = 60/RR_interval;    
          
        }

      Start = 0;
      
    }
      
            
          if(heart_rate<220)
              {
              GLCD.CursorToXY(GLCD.CenterX, 2);
              GLCD.print(heart_rate);
        
              }
    
    
    //Draw graph  
    GLCD.SetDot(i,y,BLACK);
    delay(Delay);
    
    prevdot = thisdot;
    thisdot = 0;
    slope = 0;
 
  }
 
 
GLCD.ClearScreen();
 
}





In the code window below you can see the simple version of the code which is only suitable for visualization on the glcd and has an inconstant sampling rate since it reads the ADC from the main loop.

/**********************************************************/
//Simple ECG monitor program 
/**********************************************************/
#include <glcd.h>
//http://www.arduino.cc/playground/Code/GLCDks0108

#include "fonts/allFonts.h"         // system and arial14 fonts are used
#include "bitmaps/allBitmaps.h"       // all images in the bitmap dir
gText textArea;              // a text area to be defined later in the sketch

void setup(){
  GLCD.Init();
  GLCD.SelectFont(System5x7, BLACK); // font for the default text area

}

unsigned long Start, Finished = 0;
float heart_rate = 0.0;
float RR_interval = 0.0;
unsigned int Delay = 9;
int thisdot = 0;
int prevdot = 0;


void loop() {
 
  for(int i=0; i<GLCD.Width; i++)
  {
    
    Finished = 0;
    unsigned int val = analogRead(A5);  //using A5
    unsigned int y = map (val, 0, 1023, 64, 0);      //oben=0!!
    thisdot = y;
    
    //slope can be negative so it has to be an SIGNED int
    int slope = prevdot - thisdot;
    
     if (slope >= 8 && Start == 0)
      {
        Start = millis();   
      }
      else if(slope >= 8 && Start > 0)
    {
      
        Finished = millis();
        RR_interval = Finished - Start;
              
        if(RR_interval>=150)  //refractory period
          {
            RR_interval = RR_interval/1000;  //convert to seconds
            heart_rate = 60/RR_interval;    
            
          }

      Start = 0;
      
    }
      
            
    if(heart_rate<220)
        {
        GLCD.CursorToXY(GLCD.CenterX, 2);
        GLCD.print(heart_rate);
  
        }
     
    //Draw graph  
    GLCD.SetDot(i,y,BLACK);
    delay(Delay);
    
    prevdot = thisdot;
    thisdot = 0;
    slope = 0;
 
  }
 
GLCD.ClearScreen();
 
}

Here is the wiring configuration and the display type I’ve used:
20160728_080753