element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Enchanted Objects
  • Challenges & Projects
  • Design Challenges
  • Enchanted Objects
  • More
  • Cancel
Enchanted Objects
Blog MagicHat - 6 - Heart beat detection
  • Blog
  • Forum
  • Documents
  • Polls
  • Files
  • Events
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: amgalbu
  • Date Created: 9 Apr 2015 3:45 PM Date Created
  • Views 1806 views
  • Likes 6 likes
  • Comments 14 comments
  • enchanted_objects
  • timer
  • heart-rate
  • magic_doctor_hat
  • arduino
Related
Recommended

MagicHat - 6 - Heart beat detection

amgalbu
amgalbu
9 Apr 2015

After building the heart beat sensor, it's time to process the output signal to get the actual heart beat

 

The heart pulse signal that comes out of a photoplethysmograph is an analog fluctuation in voltage, and it has a predictable wave shape as shown in figure below

 

image

 

My goal is to find successive moments of instantaneous heart beat and measure the time between, called the Inter Beat Interval (IBI). By following the predictable shape and pattern of the PPG wave, we are able to do just that.

When the heart pumps blood through the body, with every beat there is a pulse wave (kind of like a shock wave) that travels along all arteries to the very extremities of capillary tissue where the Pulse Sensor is attached. Actual blood circulates in the body much slower than the pulse wave travels. Let's follow events as they progress from point 'T' on the PPG below. A rapid upward rise in signal value occurs as the pulse wave passes under the sensor, then the signal falls back down toward the normal point. Sometimes, the dichrotic notch (downward spike) is more pronounced than others, but generally the signal settles down to background noise before the next pulse wave washes through. Since the wave is repeating and predictable, we could choose almost any recognizable feature as a reference point, say the peak, and measure the heart rate by doing math on the time between each peak. This, however, can run into false readings from the dichrotic notch, if present, and may be susceptible to inaccuracy from baseline noise as well. There are other good reasons not to base the beat-finding algorithm on arbitrary wave phenomena. Ideally, we want to find the instantaneous moment of the heart beat. This is important for accurate BPM calculation, Heart Rate Variability (HRV) studies, and Pulse Transit Time (PTT) measurement. .

 

image

 

Some heart researchers say it's when the signal gets to 25% of the amplitude, some say when it's 50% of the amplitude, and some say it's the point when the slope is steepest during the upward rise event. This version 1.1 of Pulse Sensor code is designed to measure the IBI by timing between moments when the signal crosses 50% of the wave amplitude during that fast upward rise. The BPM is derived every beat from an average of the previous 10 IBI times.

First off, it's important to have a regular sample rate with high enough resolution to get reliable measurement of the timing between each beat. To do this, we set up Timer2, an 8 bit hardware timer so that it throws an interrupt every other millisecond. That gives us a sample rate of 500Hz, and beat-to-beat timing resolution of 2mS. This will disable PWM output on pin 3 and 11

 

void interruptSetup(){
 TCCR1A = 0x00;
 TCCR1B = 0x0C; 
 OCR1A = 0x7C; 
 TIMSK1 = 0x02; 
 sei();
}

 

The register settings above tell Timer2 to go into CTC mode, and to count up to 124 (0x7C) over and over and over again. A prescaler of 256 is used to get the timing right so that it takes 2 milliseconds to count to 124. An interrupt flag is set every time Timer2 reaches 124, and a special function called an Interrupt Service Routine (ISR) that we wrote is run at the very next possible moment, no matter what the rest of the program is doing. sei() ensures that global interrupts are enabled.

 

The only other thing you will need is the correct ISR vector in the next step. ATmega32u4 devices use ISR(TIMER1_COMPA_vect)

So, when the Arduino is powered up and running with pulse sensor plugged into analog pin 0, it constantly (every 2 mS) reads the sensor value and looks for the heart beat. Here's how that works:

 

ISR(TIMER2_COMPA_vect){ 
 Signal = analogRead(pulsePin); 
 sampleCounter += 2; 
 int N = sampleCounter - lastBeatTime;

 

This function is called every 2 milliseconds. First thing to do is to take an analog reading of the Pulse Sensor. Next, we increment the variable sampleCounter. The sampleCounter variable is used to keep track of time. The variable N will help avoid noise later.

Next, we keep track of the highest and lowest values of the PPG wave, to get an accurate measure of amplitude. Note that this processing will be also useful when we will try to measure blood pressure

 

    if(Signal < thresh && N > (IBI/5)*3){
      if (Signal < T){ 
        T = Signal;    
      }
    }
    if(Signal > thresh && Signal > P){  
      P = Signal; 
    }

 

Variable P and T hold peak and trough values, respectively. The thresh variable is initialized at 512 (middle of analog range) and changes during run time to track a point at 50% of amplitude as we will see later. There is a time period of 3/5 IBI that must pass before T gets updated as a way to avoid noise and false readings from the dicroic notch.

Now, let's check and see if we have a pulse.

 

if (N > 250){
if ( (Signal > thresh) && (Pulse == false) && (N > ((IBI/5)*3) ){  
 Pulse = true; 
 digitalWrite(pulsePin,HIGH);            
 IBI = sampleCounter - lastBeatTime;
 lastBeatTime = sampleCounter;      

 

Before we even consider looking for a heartbeat, a minimum amount of time has to pass. This helps avoid high frequency noise. 250 millisecond minimum N places an upper limit of 240 BPM. If you expect to have a higher BPM, adjust this accordingly and see a doctor. When the waveform rises past the thresh value, and 3/5 of the last IBI has passed, we have a pulse! Time to set the Pulse flag and turn on the pulsePin LED. Then we calculate the time since the last beat to get IBI, and update the lastBeatTime.

The next bit is used to make sure we begin with a realistic BPM value on startup.

    

  if(secondBeat){ 
   secondBeat = false; 
   for(int i=0; i<=9; i++){   
     rate[i] = IBI; 
   }
 } 
if(firstBeat){ 
   firstBeat = false; 
   secondBeat = true;
   sei(): 
   return;  
 }


The boolean firstBeat is initialized as true and secondBeat is initialized as false on start up, so the very first time we find a beat and get this far in the ISR, we get kicked out by the return; in the firstBeat conditional. That will end up throwing the first IBI reading away, cause it's lousy. The second time through, we can trust (more or less) the IBI, and use it to seed the rate[] array in order to start with a more accurate BPM. The BPM is derived from an average of the last 10 IBI values, hence the need to seed.

Here is the code to calculate BPM

 

  runningTotal = 0;
  for(int i=0; i<=8; i++){
    rate[i] = rate[i+1]; 
    runningTotal += rate[i];      
  }
 
  rate[9] = IBI; 
  runningTotal += rate[9]; 
  runningTotal /= 10; 
  BPM = 60000/runningTotal;
  QS = true;  
}                     



First, we grab a large variable, runningTotal, to collect the IBIs, then the contents of rate[] are shifted over and added to runnungTotal. The oldest IBI (11 beats ago) falls out of position 0, and the fresh IBI gets put into position 9. Then it's a simple process to average the array and calculate BPM. Last thing to do is to set the QS flag (short for Quantified Self, awesome kickstarter supporters!) so the rest of the program knows we have found the beat. That's it for the things to do when we find the beat.

There's a couple of other loose ends that need tying off before we're done, like finding the not-beat.

 

if (Signal < thresh && Pulse == true){ 
  digitalWrite(13,LOW);         
  Pulse = false; 
  amp = P - T; 
  thresh = amp/2 + T;            
  P = thresh; 
  T = thresh;
}



Pulse was declared true during the upward rise in Pulse Sensor signal when we found the beat, above, so when the signal crosses thresh going down, we can figure that the pulse is over. A little housekeeping in clearing pulsePin and the Pulse boolean. Then the amplitude of the wave that just passed is measured, and thresh is updated with the new 50% mark. P and T are reset to the new thresh. The algorithm is now primed and ready to find the next beat.

There's one more question to ask before the ISR is done. What if there are no beats?

 

if (N > 2500){    
  thresh = 512; 
  P = 512; 
  T = 512; 
  firstBeat = true; 
  secondBeat = false; 
  lastBeatTime = sampleCounter;
  }

 

If there is no beat event for 2.5 seconds, variables used to find the heartbeat are reinitialized to the start up values. Sort of a soft soft-reset. That's the end of the ISR!

By using Timer2 interrupt, our beat finding algorithm runs 'in the background' and automatically updates variable values , Here's a list of useful variables, and how often they are updated.

  • Signal: raw pulse sensor signal, read every 2 ms
  • IBI: time between heartbeats in ms, refreshed every time a beat detected
  • BPM: beats per minute, refreshed every time a beat is detected
  • QS: set true every time a beat is detected, must be cleared by user
  • Pulse: set true every time a beat is detected, must be cleared by ISR

 

There you have the basic beat finding code. Having all the vital variables updated automatically makes it easy do stuff in the loop() function.

  

void setup() {
  pinMode(13,OUTPUT);  
  pinMode(10,OUTPUT);  
  Serial.begin(115200);  
  interruptSetup();      
  //  analogReference(EXTERNAL);  
 }

 

Here we go. pulsePin is the analog pin number that Pulse Sensor purple wire is plugged into. You can change it if you need to. blinkPin will blink with the pulse. The fadeRate variable is used to provide an optional fading LED effect with every beat on fadePin (must be a PWM pin, but not 3 or 11). It looks nicer than the blink on pin 13. The other variables should look familiar to you now. They are declared volatile because they get used in the ISR and the other parts of the code. In the setup, pin directions are declared and the interruptSetup routine is run. The last line in setup is used only when you have a different voltage powering the Pulse Sensor than you are using to power the Arduino. Mostly, it should be commented out.

 

void loop() {
  if (QS == true){  
    fadeVal = 255;    
    QS = false; 
   }
   ledFadeToBeat();
   delay(20); 
}

 

Here's the loop function. Note the delay at the end. This loop will run every 20mS. Since Signal is updated automatically, you can do whatever you want with it as well, modulate a tone output on a speaker, etc.. Remember that the QS flag gets set when our ISR finds the heart beat, so by checking it we can know when the beat happens. Inside the if statement we set the fadeVal to maximum brightness and reset the QS flag for next time. the last thing to do before the delay is to fade the LED and then we're done with the loop.

 

void ledFadeToBeat() {
 fadeRate-= 15; 
 fadeRate= constrain(fadeRate,0,255);
 analogWrite(fadePin, fadeRate);            
 }


The ledFadeToBeat function is pretty simple too. It reduces the fadeRate variable by 15 (you can change this as needed), makes sure the fadeRate doesn't roll over into negative numbers,or get higher than 255, then uses analogWrite to set the brightness if the LED on fadePin.

  • Sign in to reply

Top Comments

  • amgalbu
    amgalbu over 10 years ago in reply to shabaz +1
    Hi The result shown are from the schematic from my previous post. The opamp is not a lm324 but an mcp6004. However i am experiencing some issue with noise on the power supply (a battery pack that probably…
  • neilk
    neilk over 10 years ago +1
    That is great signal processing As I said in my comment on part 5, I worked on pulse plethysmography in 1972 - we had to do all the processing to get a reliable heart rate in electronics.....oh boy!!!…
Parents
  • rohitrangwani
    rohitrangwani over 10 years ago

    I think you are using http://pulsesensor.com/ code here.

    Are you using same sensor or you have designed something of your own creation?

    BTW, overall project looks cool.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • rohitrangwani
    rohitrangwani over 10 years ago

    I think you are using http://pulsesensor.com/ code here.

    Are you using same sensor or you have designed something of your own creation?

    BTW, overall project looks cool.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
  • amgalbu
    amgalbu over 10 years ago in reply to rohitrangwani

    Hi

    yes the starting point is the pulsesensor

    I made some changes (for example I use an IR emitter and receiver instead of the green LED). you can find all the details in my previous posts

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Former Member
    Former Member over 9 years ago in reply to amgalbu

    This may be a silly question to ask but lets just say you want to analyse the pulse sensor its self and the waveform it produces. Where do you measure to get the output waves on the oscilloscope?

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • amgalbu
    amgalbu over 9 years ago in reply to Former Member

    Hi HB

    I connected the scope to the output of the amplifier

     

    Cheers

    Ambrogio

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Former Member
    Former Member over 9 years ago in reply to amgalbu

    Do you mean the output of the pulse sensor? What MCU are you using? and do you have a picture of your circuit?. I was just willing to see how you have designed this.

     

    Thanks!

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • amgalbu
    amgalbu over 9 years ago in reply to Former Member

    Hello

    you can find details about the circuit here

    MagicHat - 4 - Heart beat sensor

    and here

    MagicHat - 5 - Blood pressure sensor

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube