Heart rate sensors are an interesting way to use IR LEDs (Infared Light-Emitting Diodes) and photo diodes to find a hearts' pulse. Amazingly, the intensity of light received through the photo diode is actually different depending on how much blood is in your arteries. In this blog we will cover a photodiode's basic operation, and how to use our Heart rate 3 click library to find beats per minute.
Photodiodes
Photodiodes are used for detecting light. They use built-in lenses and optical filters to measure the light absorbed by the surface area. The photodiode then takes the amount of light absorbed and converts it to an electric current that can be used to measure the intensity of light being absorbed. When you shine an Infared LED through your finger the amount of light that is reflected back into the photo diode can be measured. The way that the Heart rate 3 senses pulses in your finger, is by measuring the difference in intensity over time. Every time your heart pulses, the arteries in your finger are filled with blood and therefore the intensity of the light decreases.
Heart rate 3
Heartrate 3 uses three LEDs and a photodiode to detect the intensity of light returned. It also includes the AFE4404 by TI that includes a 6-bit programmable LED current for all three LEDs, and individual DC offset subtraction DAC at TIA (Transimpedance Amplifier) input for each LED and ambient phase which allows us to increase the accuracy of the sensor and remove the noise from the signal. Along with hardware, there is also a way to average the 24-bit output from the photodiode before sending to the BPM algorithm for better noise cancellation. To use the Heart rate 3 it is very easy, here is a code example of calibrating the sampling frequency of the BPM function to ensure accuracy.
#include#include "heartrate_3.h" #include "resources.h" // HeartRate 3 GPIO sbit HR3_RST at GPIOC_ODR.B2; void system_setup( void ); void setup_interrupt(); void InitTimer2(); char uart_text[20] = {0}; uint64_t int_count = 0; //Used by timer to calibrate sampling freq. void main() { //Local Declarations uint16_t rate = 0; char txt[15] = {0}; system_setup(); // GPIO / HeartRate 3 / UART / I2C Setups Delay_ms(200); while(1) { rate = hr3_get_heartrate(); //Write to UART IntToStr( rate, uart_text ); UART1_Write_Text( uart_text ); UART1_Write_Text( "rn" ); } } void system_setup( void ) { //Local Declarations dynamic_modes_t dynamic_modes; uint8_t address = 0x58; //Set up dynamic modes for Heart Rate 3 Initialization dynamic_modes.transmit = trans_dis; //Transmitter disabled dynamic_modes.curr_range = led_double; //LED range 0 - 100 dynamic_modes.adc_power = adc_on; //ADC on dynamic_modes.clk_mode = osc_mode; //Use internal Oscillator dynamic_modes.tia_power = tia_off; //TIA off dynamic_modes.rest_of_adc = rest_of_adc_off; //Rest of ADC off dynamic_modes.afe_rx_mode = afe_rx_normal; //Normal Receiving on AFE dynamic_modes.afe_mode = afe_normal; //Normal AFE functionality //GPIO setup GPIO_Digital_Output( &GPIOC_BASE, _GPIO_PINMASK_2 ); GPIO_Digital_Input( &GPIOA_BASE, _GPIO_PINMASK_0 ); GPIO_Digital_Input( &GPIOD_BASE, _GPIO_PINMASK_10 ); //UART Initialize UART1_Init( 9600 ); UART1_Write_Text( "UART is Initializedrn" ); //Toggle Reset pin HR3_RST = 0; Delay_us(50); HR3_RST = 1; //I2C Initialize I2C1_Init_Advanced( 400000, &_GPIO_MODULE_I2C1_PB67 ); UART1_Write_Text( "I2C Initializedrn" ); //Heart Rate 3 InitializePhotodiodes hr3_init( address, &dynamic_modes ); // Initialize HR algorithm values to 0 initStatHRM(); // Setup interrupt handler setup_interrupt(); // Used to calibrate / test sampling frequency InitTimer2(); } void setup_interrupt() { GPIO_Digital_Output(&GPIOE_BASE, _GPIO_PINMASK_HIGH); // Enable digital output on PORTD GPIOE_ODR = 0xAAAA; GPIO_Digital_Input(&GPIOD_BASE, _GPIO_PINMASK_10); RCC_APB2ENR.AFIOEN = 1; // Enable clock for alternate pin functions AFIO_EXTICR3 = 0x0300; // PD10 as External interrupt EXTI_RTSR = 0x00000400; // Set interrupt on Rising edge EXTI_IMR |= 0x00000400; // Set mask NVIC_IntEnable(IVT_INT_EXTI15_10); // Enable External interrupt EnableInterrupts(); // Enables the processor interrupt. } void ExtInt() iv IVT_INT_EXTI15_10 ics ICS_AUTO { EXTI_PR.B10 = 1; // clear flag int_count++; statHRMAlgo( hr3_get_led1_amb1_val() ); // Give led1 ambient value to heartrate function. ( 100 times a second / 100hz ) } // This timer was used to calibrate sampling frequency for heartrate.. // Timing is right on and when MCU was at room temp, ADC_RDY was triggered // exactly 100 times a second. ( what we want ) void InitTimer2() // 1 second { RCC_APB1ENR.TIM2EN = 1; TIM2_CR1.CEN = 0; TIM2_PSC = 1124; TIM2_ARR = 63999; NVIC_IntEnable(IVT_INT_TIM2); TIM2_DIER.UIE = 1; TIM2_CR1.CEN = 1; } void Timer2_interrupt() iv IVT_INT_TIM2 { TIM2_SR.UIF = 0; sprintl( uart_text, "Counter: %llu rn", int_count ); UART1_Write_Text( uart_text ); int_count = 0; }
Every time that the all three LEDs have finished sampling and converting the ADC_RDY interupt is triggered, and using the ExtInt function we are incrementing a count variable and sending the LED1 ambient light data to the heart rate algorithm. Every 1 second a timer interrupt is triggered, capturing that count variable, displaying it on the terminal through serial port, and then resetting the counter to 0. If the timing is done correctly for the conditions of the heart rate 3, the counter should increment to 100 every 1 second. A way we can calibrate the frequency of samples we send to the algorithm, we can add a divider to the interrupt that sends the data to the algorithm like this...
void ExtInt() iv IVT_INT_EXTI15_10 ics ICS_AUTO { EXTI_PR.B10 = 1; // clear flag //If we are getting 200 samples a second, we simply //only send data every other interrupt if( int_count == 2 ) { statHRMAlgo( hr3_get_led1_amb1_val() ); int_count = 0; } else int_count++; }
If we are getting something like 120 samples a second, we will need to change the actual timing of the sampling and converting on the MCU. This can be done with functions in the hardware layer, and referencing the datasheet. By default, when you initialize the Heart rate 3, the PRF (Pulse Rate Frequency) divider is set to 1 and the timing values are set to a default value defined in the datasheet. If we want to divide the PRF by more than 1, we simply divide all of the timings by that same number, for example here is the init function while the divider is set to 1.
//Clock Timing for CLKDIV_PRF = 1 hr3_set_led2_start_end( 0, 399 ); hr3_set_led2_sample_start_end( 80, 399 ); hr3_set_adc_reset0_start_end( 401, 407 ); hr3_set_led2_convert_start_end( 408, 1467 ); hr3_set_led3_start_stop( 400, 799 ); hr3_set_led3_sample_start_end( 480, 799 ); hr3_set_adc_reset1_start_end( 1469, 1475 ); hr3_set_led3_convert_start_end( 1476, 2535 ); hr3_set_led1_start_end( 800, 1199 ); hr3_set_led1_sample_start_end( 880, 1199 ); hr3_set_adc_reset2_start_end( 2537, 2543 ); hr3_set_led1_convert_start_end( 2544, 3603 ); hr3_set_amb1_sample_start_end( 1279, 1598 ); hr3_set_adc_reset3_start_end( 3605, 3611 ); hr3_set_amb1_convert_start_end( 3612, 4671 ); hr3_set_pdn_cycle_start_end( 5471, 39199 ); hr3_set_prpct_count( 39999 ); hr3_set_int_clk_div( 1 );
And here is an example of when the PRF divider is set to 5.
//Clock Timing for CLKDIV_PRF = 5 hr3_set_led2_start_end( 0, 79 ); hr3_set_led2_sample_start_end( 16, 79 ); hr3_set_adc_reset0_start_end( 80, 81 ); hr3_set_led2_convert_start_end( 81, 293 ); hr3_set_led3_start_stop( 80, 159 ); hr3_set_led3_sample_start_end( 96, 159 ); hr3_set_adc_reset1_start_end( 293, 295 ); hr3_set_led3_convert_start_end( 295, 507 ); hr3_set_led1_start_end( 160, 239 ); hr3_set_led1_sample_start_end( 176, 239 ); hr3_set_adc_reset2_start_end( 507, 508 ); hr3_set_led1_convert_start_end( 508, 720 ); hr3_set_amb1_sample_start_end( 255, 319 ); hr3_set_adc_reset3_start_end( 721, 722 ); hr3_set_amb1_convert_start_end( 722, 934 ); hr3_set_pdn_cycle_start_end( 1094, 7839 ); hr3_set_prpct_count( 7999 ); hr3_set_int_clk_div( 5 );
With this calibration technique, the great heart rate algorithm, and a steady finger on the click board it is easy to find your heart rate. In these examples I have sent the LED1 ambient values to the algorithm for higher accuracy. In some cases, it would be better to send LED1 values instead, but that will have to be determined by the user.
Source code can be found on either Libstock or Github.
Summary
Heart rate sensors are awesome, and with a good library they can be very accurate at finding pulses without much noise. Heart rate 3 is a great example of using one of the most accurate heart rate sensors out there, and a great library to produce a heart rate with ease.