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Β 16Γ2 character LCD. We will useΒ PIC16F877AΒ PIC microcontroller in this project. We alreadyΒ interfaced pulse sensor with Arduino for Patient Monitoring System.
Required Components
- PIC16F877A microcontroller
- 20 Mhz Crystal
- 33pF capacitor 2 pcs
- 4.7k resistor 1 pcs
- 16Γ2 Character LCD
- 10K pot for contrast control of the LCD
- SEN-11574 Pulse sensor
- Velcro strap
- 5V Power adapter
- 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.
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.
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
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.
Code Explanation
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.
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.
/*
* 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
Read More Detail:Heart Beat Monitoring using PIC Microcontroller and Pulse Sensor