Heart Beat Monitoring using PIC Microcontroller and Pulse Sensor

Published  October 11, 2018   18
Heart Beat Monitoring using PIC Microcontroller and Pulse Sensor

Heart Beat rate is most important parameter in monitoring any person’s health. In the modern era of wearable devices, there are lot of devices which can measure heartbeat, blood pressure, footsteps, calories burnt and lot of other things. These devices has pulse sensor inside them to sense the pulse rate. Today, we will also use a pulse sensor with PIC Microcontroller to count heart beat per minute and the Inter-Beat Interval, these values will be further displayed on 16x2 character LCD. We will use PIC16F877A PIC microcontroller in this project. We already interfaced pulse sensor with Arduino for Patient Monitoring System.

 

Required Components

  1. PIC16F877A microcontroller
  2. 20 Mhz Crystal
  3. 33pF capacitor 2 pcs
  4. 4.7k resistor 1 pcs
  5. 16x2 Character LCD
  6. 10K pot for contrast control of the LCD
  7. SEN-11574 Pulse sensor
  8. Velcro strap
  9. 5V Power adapter
  10. Breadboard and hookup wires

 

Pulse Sensor SEN-11574

To measure the heartbeat we need a pulse sensor. Here we have selected SEN-11574 pulse sensor which is easily available on online or offline stores. We used this sensor as there are sample codes provided from the manufacturer, but that is an Arduino code. We converted that code for our PIC microcontroller.

Pulse Sensor SEN-11574

 

The sensor is really small and perfect for reading heartbeat across earlobe or on the fingertip. It is 0.625” in diameter and 0.125” thick from the round PCB side.

 

This sensor provides an analog signal and the sensor can be driven with 3V or 5V, the current consumption of the sensor is 4 mA, which is great for mobile applications. The sensor comes with three wire with 24” long hookup cable and berg male header at the end. Also, the sensor comes with Velcro Finger Strap to wear it across fingertip.

 

Pulse Sensor schematic is also provided by the manufacturer and also available on sparkfun.com.

Pulse Sensor SEN-11574 Internal Structure

 

The sensor schematic consists optical heart-rate sensor, noise cancellation RC circuitry or filters, which can be seen in the schematic diagram. R2, C2, C1, C3 and an operational amplifier MCP6001 are used for reliable amplified analog output.

 

There are few other sensors for Heart Beat Monitoring but SEN-11574 pulse sensor is widely used in Electronics projects.

 

Circuit Diagram for Pulse Sensor interfacing with PIC Microcontroller

Heart Beat Monitoring Circuit Diagram using PIC Microcontroller and Pulse Sensor

 

Here we have connected the pulse sensor across a 2nd pin of the microcontroller unit. As the sensor provides analog data, we need to convert the analog data into digital signal by doing necessary calculations.

 

The Crystal oscillator of 20Mhz is connected across two OSC pins of the microcontroller unit with two ceramic 33pF capacitors. The LCD is connected across the RB port of the microcontroller.

PIC16F877A Heart Beat Monitoring Project

 

PIC16F877A Code Explanation for Heart Beat Monitor 

The code is a little bit complex for beginners. The manufacturer provided sample codes for the SEN-11574 sensor, but it was written for the Arduino platform. We need to convert the calculation for our microchip, PIC16F877A. Complete code is given at the end of this project with a Demonstration Video. And the supporting C files can be downloaded from here.

 

Our code flow is relatively simple and we made the steps using a switch case. As per the manufacturer, we need to get the data from the sensor in every 2 milliseconds. So, we used a timer interrupt service routine which will fire a function in every 2 milliseconds.

 

Our code flow in switch statement will go like this:

Case 1: Read the ADC

Case 2: Calculate the Heart Beat and IBI

Case 3: Show the heartbeat and IBI on LCD

Case 4: IDLE (Do nothing)

 

Inside the timer interrupt function, we change the state of the program to Case 1: Read the ADC on every 2 milliseconds.

So, in the main function, we defined the program state and all the switch cases.

void main() {
    system_init();
    main_state = READ_ADC;      
    while (1) {
        switch (main_state) {
            case READ_ADC:
            {                
                adc_value = ADC_Read(0); // 0 is the channel number
                main_state = CALCULATE_HEART_BEAT;                
                break;
            }
            case CALCULATE_HEART_BEAT:
            {
                calculate_heart_beat(adc_value);
                main_state = SHOW_HEART_BEAT;
                break;
            }
            case SHOW_HEART_BEAT:
            {
                if (QS == true) { // A Heartbeat Was Found
                    // BPM and IBI have been Determined
                    // Quantified Self "QS" true when Arduino finds a heartbeat                    
                    QS = false; // reset the Quantified Self flag for next time

                    // 0.9 used for getting better data. actually should not be used
                    BPM = BPM * 0.9;
                    IBI = IBI / 0.9;
                    lcd_com(0x80);
                    lcd_puts("BPM:-     ");
                    lcd_print_number(BPM);
                    lcd_com(0xC0);
                    lcd_puts("I.B.I:-   ");
                    lcd_print_number(IBI);                    
                }
            }
                main_state = IDLE;
                break;
       
        case IDLE:
        {            
            break;
        }
        default:
        {

        }
    }
}
}

 

We are using two hardware peripherals of the PIC16F877A: Timer0 and ADC.

Inside the timer0.c file,

TMR0 = (uint8_t)(tmr0_mask & (256-(((2 *_XTAL_FREQ)/(256*4))/1000)));

 

This calculation is providing the 2 milliseconds timer interrupt.  The calculation formula is

// TimerCountMax - (((delay(ms) * Focs(hz)) / (PreScale_Val * 4)) / 1000)

 

If we see the timer_isr function, it is-

void timer_isr() {
    main_state = READ_ADC;    
}

In this function the program state is changed to READ_ADC in every 2ms.

 

Then the CALCULATE_HEART_BEAT function is taken from the Arduino example code.

void calculate_heart_beat(int adc_value) {
    Signal = adc_value;
    sampleCounter += 2; // keep track of the time in mS with this variable
    int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise

    //  find the peak and trough of the pulse wave
    if (Signal < thresh && N > (IBI / 5)*3) { // avoid dichrotic noise by waiting 3/5 of last IBI
        if (Signal < T) { // T is the trough
            T = Signal; // keep track of lowest point in pulse wave
        }
    }
………….
………………………..

 

Further, the complete code is given below and well explained by the comments. This heart beat sensor data can be further uploaded to the cloud and monitored over the internet from anywhere, which thus makes it IoT based Heart Beat Monitoring system, follow the link to learn more.

Download Supporting C files for this PIC Pulse Sensor Project from here.

Code

/*
 * File:   main.c
 * Author: Sourav Gupta
 * By:- circuitdigest.com
 * Created on September 30, 2018, 2:26 PM
 */

// PIC16F877A Configuration Bit Settings

// 'C' source line config statements

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF         // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3/PGM pin has PGM function; low-voltage programming enabled)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#include <xc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "supporing_cfile\lcd.h"
#include "supporing_cfile\eusart1.h"
#include "supporing_cfile\adc.h"
#include "supporing_cfile\tmr0.h"

/*
 Hardware related definition
 */
#define _XTAL_FREQ 200000000 //Crystal Frequency, used in delay

/*
 Program Flow related definition
 */

#define READ_ADC  1
#define CALCULATE_HEART_BEAT 2
#define SHOW_HEART_BEAT 3
#define IDLE 0
#define DEFAULT -1

volatile int rate[10]; // array to hold last ten IBI values
volatile unsigned long sampleCounter = 0; // used to determine pulse timing
volatile unsigned long lastBeatTime = 0; // used to find IBI
volatile int P = 512; // used to find peak in pulse wave, seeded
volatile int T = 512; // used to find trough in pulse wave, seeded
volatile int thresh = 530; // used to find instant moment of heart beat, seeded
volatile int amp = 0; // used to hold amplitude of pulse waveform, seeded
volatile bool firstBeat = true; // used to seed rate array so we startup with reasonable BPM
volatile bool secondBeat = false; // used to seed rate array so we startup with reasonable BPM

volatile int BPM; // int that holds raw Analog in 0. updated every 2mS
volatile int Signal; // holds the incoming raw data
volatile int IBI = 600; // int that holds the time interval between beats! Must be seeded!
volatile bool Pulse = false; // "True" when User's live heartbeat is detected. "False" when not a "live beat".
volatile bool QS = false; // becomes true when finds a beat.

int main_state = -1;
int adc_value = 0;

int tune = 0;
/*
 Other Specific definition
 */
void system_init(void);

void calculate_heart_beat(int adc_value) {

    Signal = adc_value;

    sampleCounter += 2; // keep track of the time in mS with this variable
    int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise

    //  find the peak and trough of the pulse wave
    if (Signal < thresh && N > (IBI / 5)*3) { // avoid dichrotic noise by waiting 3/5 of last IBI
        if (Signal < T) { // T is the trough
            T = Signal; // keep track of lowest point in pulse wave
        }
    }

    if (Signal > thresh && Signal > P) { // thresh condition helps avoid noise
        P = Signal; // P is the peak
    } // keep track of highest point in pulse wave

    //  NOW IT'S TIME TO LOOK FOR THE HEART BEAT
    // signal surges up in value every time there is a pulse
    if (N > 250) { // avoid high frequency noise
        if ((Signal > thresh) && (Pulse == false) && (N > (IBI / 5)*3)) {
            Pulse = true; // set the Pulse flag when we think there is a pulse
            IBI = sampleCounter - lastBeatTime; // measure time between beats in mS
            lastBeatTime = sampleCounter; // keep track of time for next pulse

            if (secondBeat) { // if this is the second beat, if secondBeat == TRUE
                secondBeat = false; // clear secondBeat flag
                int i;
                for (i = 0; i <= 9; i++) { // seed the running total to get a realisitic BPM at startup
                    rate[i] = IBI;
                }
            }

            if (firstBeat) { // if it's the first time we found a beat, if firstBeat == TRUE
                firstBeat = false; // clear firstBeat flag
                secondBeat = true; // set the second beat flag
                //pulse_tmr_handle = bsp_harmony_start_tmr_cb_periodic(PULSE_CHECK_TIME_INTERVAL, 0, pulse_read_cb); // enable interrupts again
                return; // IBI value is unreliable so discard it
            }

            // keep a running total of the last 10 IBI values
            uint16_t runningTotal = 0; // clear the runningTotal variable
            int i;
            for (i = 0; i <= 8; i++) { // shift data in the rate array
                rate[i] = rate[i + 1]; // and drop the oldest IBI value
                runningTotal += rate[i]; // add up the 9 oldest IBI values
            }

            rate[9] = IBI; // add the latest IBI to the rate array
            runningTotal += rate[9]; // add the latest IBI to runningTotal
            runningTotal /= 10; // average the last 10 IBI values
            BPM = 60000 / runningTotal; // how many beats can fit into a minute? that's BPM!
            QS = true; // set Quantified Self flag
            // QS FLAG IS NOT CLEARED INSIDE THIS ISR
        }
    }
if (Signal < thresh && Pulse == true) { // when the values are going down, the beat is over
        Pulse = false; // reset the Pulse flag so we can do it again
        amp = P - T; // get amplitude of the pulse wave
        thresh = amp / 2 + T; // set thresh at 50% of the amplitude
        P = thresh; // reset these for next time
        T = thresh;
    }

    if (N > 2500) { // if 2.5 seconds go by without a beat
        thresh = 530; // set thresh default
        P = 512; // set P default
        T = 512; // set T default
        lastBeatTime = sampleCounter; // bring the lastBeatTime up to date
        firstBeat = true; // set these to avoid noise
        secondBeat = false; // when we get the heartbeat back
    }

}

 

void main() {

    system_init();
    main_state = READ_ADC;        
    while (1) {
        switch (main_state) {
            case READ_ADC:
            {                
                adc_value = ADC_Read(0);
                main_state = CALCULATE_HEART_BEAT;                
                break;
            }
            case CALCULATE_HEART_BEAT:
            {
                calculate_heart_beat(adc_value);
                main_state = SHOW_HEART_BEAT;
                break;
            }
            case SHOW_HEART_BEAT:
            {
                if (QS == true) { // A Heartbeat Was Found
                    // BPM and IBI have been Determined
                    // Quantified Self "QS" true when arduino finds a heartbeat                    
                    QS = false; // reset the Quantified Self flag for next time

                    // 0.9 used for getting better data. actually should not be used
                    //BPM = BPM * 0.9;
                   // IBI = IBI / 0.9;                    
                    //IBI = IBI * 2;
                   // tune = BPM / 2;
                    //lcd_com(0x01);                    
                    lcd_com(0x80);                  
                    lcd_puts("BPM:-     ");
                    lcd_print_number(BPM);
                    lcd_puts (" ");
                    lcd_com(0xC0);
                    lcd_puts("I.B.I:-   ");
                    lcd_print_number(IBI);
                    lcd_puts (" ");
                    
                }
            }

                main_state = IDLE;
                break;
        
        case IDLE:
        {            
            break;
        }
        default:
        {

        }
    }
}
}
/*
 This Function is for system initializations.
 */

void system_init(void){
    TRISB = 0x00;
    lcd_init(); // This will initialize the lcd
    TMR0_Initialize();
    TMR0_StartTimer();
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();   
    ADC_Init();
}

/*
 * Custom timer callback function
 */

void timer_isr() {
    main_state = READ_ADC;    
}

void interrupt INTERRUPT_InterruptManager (void)
{
    // interrupt handler
    if(INTCONbits.TMR0IE == 1 && INTCONbits.TMR0IF == 1)
    {
        TMR0_ISR();
    }
}

Video

Have any question realated to this Article?

Ask Our Community Members

Comments

Excuse me, how can I write those .h file ( #include "supporing_cfile\lcd.h"
#include "supporing_cfile\eusart1.h"
#include "supporing_cfile\adc.h"
#include "supporing_cfile\tmr0.h")
or Could you give me those .h file, I'm at beginning of learning PIC. Thank you so much!!!!! 

Chào Trang, cậu đã viết được file.h ở bên trên chưa! Tớ đang cố viết nhưng chưa thành công, nếu cậu có các file này, có thể chia sẻ cho tớ được không?

Cám ơn cậu rất nhiều!

#include <xc.h>
 
#define LCDPORT PORTD
#define RS PORTDbits.RD0
#define RW PORTDbits.RD1
#define E PORTDbits.RD2
 
 
 
bit  status=0;
#define lcd_delay 300
 
#define LCDMaxLines 2
#define LCDMaxChars 16
#define LineOne 0x80
#define LineTwo 0xc0
 
#define BlankSpace ' '
 
 
void delay(unsigned int j)
{
unsigned int i=0;
for(i=0;i<j;i++);
}
 
void lcd_init_write(unsigned char a)
{
RS=0;
RW=0;
LCDPORT=a;
E=1;
delay(lcd_delay);
E=0;
}
 
 
void lcd_com(unsigned char a)
{
unsigned char temp;
if(status)
{
status=0;
goto next;
}
RS=0;
next:
RW=0;
temp=a;
temp&=0xf0; // Mask Lower 4 Bits
LCDPORT&=0x0f; // Make No Affect on 0ther Port Pins
LCDPORT|=temp; // Send Higher Nibble to LCDPORT
E=1;
delay(lcd_delay); //Send Enable Signal to LCD
E=0;
temp=a<<4; //Left Shift Byte Four Times
temp&=0xf0; // Mask Higher 4 Bits
LCDPORT&=0x0f;          // Make No Affect on 0ther Port Pins
LCDPORT|=temp; // Send Lower Nibble to LCDPORT
E=1;
delay(lcd_delay); // Send Enable Signal to LCD
E=0;
}
 
void lcd_data(unsigned char a)
{
status=1;
RS=1;
lcd_com(a);
}
void lcd_init(void)
{
delay(lcd_delay);
lcd_init_write(0x30);   //Special Sequence:Write Function Set.
delay(lcd_delay);
lcd_init_write(0x30); //Special Sequence:Write Function Set.
delay(lcd_delay);
lcd_init_write(0x30); //Special Sequence:Write Function Set.
delay(lcd_delay);
lcd_init_write(0x20);   // 0x20 for 4-bit
delay(lcd_delay);
lcd_com(0xC);
//lcd_com(0x28);         //Display Off, Cursor Off, Blink Off
delay(lcd_delay);
lcd_com(4);    // Clear Screen & Returns the Cursor Home
delay(lcd_delay);
lcd_com(0x85);       
delay(lcd_delay);       
lcd_com(6);           //Inc cursor to the right when writing and don’t shift screen
delay(lcd_delay);
lcd_com(1);
delay(lcd_delay);
}
void lcd_puts(char *str)
{
  unsigned int i=0;
for(;str[i]!=0;i++)
lcd_data(str[i]);
}
 
void hex2lcd(unsigned char hex){
char temp1,temp2;
temp1 = hex;
temp2=0;
do{
temp1 = temp1-100;
if(temp1>=0)
temp2++;
} while(temp1>=0);
if(temp2>0)
lcd_data(temp2+0x30);
temp2=0;
temp1 = temp1+100;
do{
temp1 = temp1-10;
if(temp1>=0)
temp2++;
} while(temp1>=0);
lcd_data(temp2+0x30);
temp2 = temp1+10;
lcd_data(temp2+0x30);
}
void LCD_ScrollMessage(char *msg_ptr)
{
  unsigned char i,j;
 
  for(i=0;msg_ptr[i];i++)        //Loop to display the string complete
    {                            //each time 16 chars are displayed and
                                 //pointer is incremented to point to next char
 
      lcd_com(LineOne);                   //Move the Cursor to first line
 
    for(j=0;j<LCDMaxChars && msg_ptr[i+j];j++)//loop to Display first 16 Chars
  lcd_data(msg_ptr[i+j]);
  delay(500);                 //or till Null char
 
for(j=j; j<LCDMaxChars; j++)               //If the chars are below 16
      lcd_data(BlankSpace);              //then display blank spaces
      delay(500);
    }
}

Please, can u share files supporing_cfile.h

I am studying PIC.

Thank you very much!

 

can you please send me the supporting files which you have attached in the main program

#include "supporing_cfile\lcd.h"
#include "supporing_cfile\eusart1.h"
#include "supporing_cfile\adc.h"
#include "supporing_cfile\tmr0.h"

Thank you

Sir, Please provide the header files for

#include "supporing_cfile\lcd.h"
#include "supporing_cfile\eusart1.h"
#include "supporing_cfile\adc.h"
#include "supporing_cfile\tmr0.h"

Sir, please send me below heade files
(#include "supporing_cfile\lcd.h"
#include "supporing_cfile\eusart1.h"
#include "supporing_cfile\adc.h"
#include "supporing_cfile\tmr0.h")

Hello, please help me and send me these files 

#include "supporing_cfile\lcd.h"
#include "supporing_cfile\eusart1.h"
#include "supporing_cfile\adc.h"
#include "supporing_cfile\tmr0.h"