Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Diff: algorithm/algorithm.cpp
- Revision:
- 0:346a7fa07998
- Child:
- 1:e88f22c6c1b0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/algorithm/algorithm.cpp Wed Apr 20 21:46:06 2016 +0000
@@ -0,0 +1,378 @@
+/** \file algorithm.h ******************************************************
+*
+* Project: MAXREFDES117#
+* Filename: algorithm.c
+* Description: This module calculates the heart rate/SpO2 level
+*
+* Revision History:
+*\n 1-18-2016 Rev 01.00 SK Initial release.
+*\n
+*
+* --------------------------------------------------------------------
+*
+* This code follows the following naming conventions:
+*
+*\n char ch_pmod_value
+*\n char (array) s_pmod_s_string[16]
+*\n float f_pmod_value
+*\n int32_t n_pmod_value
+*\n int32_t (array) an_pmod_value[16]
+*\n int16_t w_pmod_value
+*\n int16_t (array) aw_pmod_value[16]
+*\n uint16_t uw_pmod_value
+*\n uint16_t (array) auw_pmod_value[16]
+*\n uint8_t uch_pmod_value
+*\n uint8_t (array) auch_pmod_buffer[16]
+*\n uint32_t un_pmod_value
+*\n int32_t * pn_pmod_value
+*
+* ------------------------------------------------------------------------- */
+/*******************************************************************************
+* Copyright (C) 2015 Maxim Integrated Products, Inc., All Rights Reserved.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a
+* copy of this software and associated documentation files (the "Software"),
+* to deal in the Software without restriction, including without limitation
+* the rights to use, copy, modify, merge, publish, distribute, sublicense,
+* and/or sell copies of the Software, and to permit persons to whom the
+* Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
+* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+* OTHER DEALINGS IN THE SOFTWARE.
+*
+* Except as contained in this notice, the name of Maxim Integrated
+* Products, Inc. shall not be used except as stated in the Maxim Integrated
+* Products, Inc. Branding Policy.
+*
+* The mere transfer of this software does not imply any licenses
+* of trade secrets, proprietary technology, copyrights, patents,
+* trademarks, maskwork rights, or any other form of intellectual
+* property whatsoever. Maxim Integrated Products, Inc. retains all
+* ownership rights.
+*******************************************************************************
+*/
+#include "algorithm.h"
+#include "mbed.h"
+
+void maxim_heart_rate_and_oxygen_saturation(uint32_t *un_ir_buffer , int32_t n_ir_buffer_length, uint32_t *un_red_buffer , int32_t *n_spo2, int8_t *ch_spo2_valid ,
+ int32_t *n_heart_rate , int8_t *ch_hr_valid)
+/**
+* \brief Calculate the heart rate and SpO2 level
+* \par Details
+* By detecting peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the ratio for the SPO2 is computed.
+* Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
+* Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each ratio.
+*
+* \param[in] *un_ir_buffer - IR sensor data buffer
+* \param[in] n_ir_buffer_length - IR sensor data buffer length
+* \param[in] *un_red_buffer - Red sensor data buffer
+* \param[out] *n_spo2 - Calculated SpO2 value
+* \param[out] *ch_spo2_valid - 1 if the calculated SpO2 value is valid
+* \param[out] *n_heart_rate - Calculated heart rate value
+* \param[out] *ch_hr_valid - 1 if the calculated heart rate value is valid
+*
+* \retval None
+*/
+{
+
+
+ uint32_t irMean ,onlyOnce ;
+ int32_t k ,iRatioCount;
+ int32_t i,s ,m, exact_ir_valley_locs_count ,middleIdx;
+ int32_t th1, n_npks,cMin;
+ int32_t ir_valley_locs[15] ;
+ int32_t exact_ir_valley_locs[15] ;
+ int32_t dx_peak_locs[15] ;
+ int32_t peakintervalSum;
+
+ int32_t yAC, xAC;
+ int32_t spo2calc;
+ int32_t yDCmax, xDCmax;
+ int32_t yDCmaxIdx, xDCmaxIdx;
+ int32_t ratio[5],ratioAverage;
+ int32_t nume, denom ;
+ // remove DC of ir signal
+ irMean =0;
+ for (k=0 ; k<n_ir_buffer_length ; k++ ) irMean += un_ir_buffer[k] ;
+ irMean =irMean/n_ir_buffer_length ;
+ for (k=0 ; k<n_ir_buffer_length ; k++ ) n_x[k] = un_ir_buffer[k] - irMean ;
+
+ // 4 pt Moving Average
+ for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
+ denom= ( n_x[k]+n_x[k+1]+ n_x[k+2]+ n_x[k+3]);
+ n_x[k]= denom/(int32_t)4;
+ }
+
+ // get difference of smoothed IR signal
+
+ for( k=0; k<BUFFER_SIZE-MA4_SIZE-1; k++)
+ n_dx[k]= (n_x[k+1]- n_x[k]);
+
+ // 2-pt Moving Average to n_dx
+ for(k=0; k< BUFFER_SIZE-MA4_SIZE-2; k++){
+ n_dx[k] = ( n_dx[k]+n_dx[k+1])/2 ;
+ }
+
+ // hamming window
+ // flip wave form so that we can detect valley with peak detector
+ for ( i=0 ; i<BUFFER_SIZE-HAMMING_SIZE-MA4_SIZE-2 ;i++){
+ s= 0;
+ for( k=i; k<i+ HAMMING_SIZE ;k++){
+ s -= n_dx[k] *uw_hamm[k-i] ;
+ }
+ n_dx[i]= s/ (int32_t)1146; // divide by sum of uw_hamm
+ }
+
+
+ th1=0; // threshold calculation
+ for ( k=0 ; k<BUFFER_SIZE-HAMMING_SIZE ;k++){
+ th1 += ((n_dx[k]>0)? n_dx[k] : ((int32_t)0-n_dx[k])) ;
+ }
+ th1= th1/ ( BUFFER_SIZE-HAMMING_SIZE);
+ // peak location is acutally index for sharpest location of raw signal since we flipped the signal
+ maxim_find_peaks( dx_peak_locs, &n_npks, n_dx, BUFFER_SIZE-HAMMING_SIZE, th1, 8, 5 );//peak_height, peak_distance, max_num_peaks
+
+ peakintervalSum =0;
+ if (n_npks>=2){
+ for (k=1; k<n_npks; k++)
+ peakintervalSum += (dx_peak_locs[k] -dx_peak_locs[k -1] ) ;
+ peakintervalSum =peakintervalSum/(n_npks-1);
+ *n_heart_rate =(int32_t)( 6000/ peakintervalSum );// beats per minutes
+ //prlongf(">>> *n_heart_rate= %d \n", *n_heart_rate) ;
+ *ch_hr_valid = 1;
+ }
+ else {
+ *n_heart_rate = -999;
+ *ch_hr_valid = 0;
+ }
+
+ for ( k=0 ; k<n_npks ;k++)
+ ir_valley_locs[k]= dx_peak_locs[k] +HAMMING_SIZE /2;
+
+
+ // raw value : RED(=y) and IR(=X)
+ // we need to assess DC and AC value of ir and red PPG.
+ for (k=0 ; k<n_ir_buffer_length ; k++ ) {
+ n_x[k] = un_ir_buffer[k] ;
+ n_y[k] = un_red_buffer[k] ;
+ }
+
+ // find precise min near ir_valley_locs
+ exact_ir_valley_locs_count =0;
+ for ( k=0 ; k<n_npks ;k++){
+ onlyOnce =1;
+ m=ir_valley_locs[k];
+ cMin= 16777216;//2^24;
+ if (m+5 < BUFFER_SIZE-HAMMING_SIZE && m-5 >0){
+
+ for(i= m-5;i<m+5; i++)
+ if (n_x[i]<cMin){
+ if (onlyOnce >0){
+ onlyOnce =0;
+ }
+ cMin= n_x[i] ;
+ exact_ir_valley_locs[k]=i;
+ }
+ if (onlyOnce ==0) exact_ir_valley_locs_count ++ ;
+ }
+ }
+ if (exact_ir_valley_locs_count <2 ){
+ *n_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range
+ *ch_spo2_valid = 0;
+ return;
+ }
+ // 4 pt MA
+ for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
+ n_x[k]=( n_x[k]+n_x[k+1]+ n_x[k+2]+ n_x[k+3])/(int32_t)4;
+ n_y[k]=( n_y[k]+n_y[k+1]+ n_y[k+2]+ n_y[k+3])/(int32_t)4;
+
+ }
+
+ //using exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio
+ //finding AC/DC maximum of raw ir * red between two valley locations
+ ratioAverage =0;
+ iRatioCount = 0;
+
+ for(k=0; k< 5; k++) ratio[k]=0;
+ for (k=0; k< exact_ir_valley_locs_count; k++){
+ if (exact_ir_valley_locs[k] > BUFFER_SIZE ){
+ *n_spo2 = -999 ; // do not use SPO2 since valley loc is out of range
+ *ch_spo2_valid = 0;
+ return;
+ }
+ }
+ // find max between two valley locations
+ // and use ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2
+
+ for (k=0; k< exact_ir_valley_locs_count-1; k++){
+ yDCmax= -16777216 ;
+ xDCmax= - 16777216;
+ // printf("range=%d: %d\n ", exact_ir_valley_locs[k], exact_ir_valley_locs[k+1]);
+ if (exact_ir_valley_locs[k+1]-exact_ir_valley_locs[k] >10){
+ for (i=exact_ir_valley_locs[k]; i< exact_ir_valley_locs[k+1]; i++){
+
+ if (n_x[i]> xDCmax) {xDCmax =n_x[i];xDCmaxIdx =i; }
+ if (n_y[i]> yDCmax) {yDCmax =n_y[i];yDCmaxIdx=i;}
+ }
+ yAC= (n_y[exact_ir_valley_locs[k+1]] - n_y[exact_ir_valley_locs[k] ] )*(yDCmaxIdx -exact_ir_valley_locs[k]); //red
+ yAC= n_y[exact_ir_valley_locs[k]] + yAC/ (exact_ir_valley_locs[k+1] - exact_ir_valley_locs[k]) ;
+
+
+ yAC= n_y[yDCmaxIdx] - yAC; // subracting linear DC compoenents from raw
+ xAC= (n_x[exact_ir_valley_locs[k+1]] - n_x[exact_ir_valley_locs[k] ] )*(xDCmaxIdx -exact_ir_valley_locs[k]); // ir
+ xAC= n_x[exact_ir_valley_locs[k]] + xAC/ (exact_ir_valley_locs[k+1] - exact_ir_valley_locs[k]);
+ xAC= n_x[yDCmaxIdx] - xAC; // subracting linear DC compoenents from raw
+ nume=( yAC *xDCmax)>>7 ; //prepare X100 to preserve floating value
+ denom= ( xAC *yDCmax)>>7;
+ if (denom>0 && iRatioCount <5 && nume != 0)
+ {
+ ratio[iRatioCount]= (nume*100)/denom ; //formular is ( yAC *xDCmax) / ( xAC *yDCmax) ;
+ iRatioCount++;
+ }
+ }
+
+ // prlongf("ratio[%d]= %d exact_ir_valley_locs[k] =%d , exact_ir_valley_locs[%d] =%d \n",k, ratio[k] ,exact_ir_valley_locs[k] ,k+1, exact_ir_valley_locs[k+1] ) ;
+ // prlongf("nume= %d ,denom= %d yAC = %d, xDCmax = %d, xAC= %d, yDCmax = %d\n",nume, denom, yAC ,xDCmax ,xAC ,yDCmax );
+
+ }
+
+ maxim_sort_ascend(ratio, iRatioCount);
+ middleIdx= iRatioCount/2;
+
+ if (middleIdx >1)
+ ratioAverage =( ratio[middleIdx-1] +ratio[middleIdx])/2; // use median
+ else
+ ratioAverage = ratio[middleIdx ];
+
+ if( ratioAverage>2 && ratioAverage <184){
+ spo2calc= uch_spo2_table[ratioAverage] ;
+ *n_spo2 = spo2calc ;
+ *ch_spo2_valid = 1;// float_SPO2 = -45.060*ratioAverage* ratioAverage/10000 + 30.354 *ratioAverage/100 + 94.845 ; // for comparison with table
+ }
+ else{
+ *n_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range
+ *ch_spo2_valid = 0;
+ }
+
+
+}
+
+
+void maxim_find_peaks( int32_t *n_locs, int32_t *n_npks, int32_t *n_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num )
+/**
+* \brief Find peaks
+* \par Details
+* Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE
+*
+* \retval None
+*/
+{
+ maxim_peaks_above_min_height( n_locs, n_npks, n_x, n_size, n_min_height );
+ maxim_remove_close_peaks( n_locs, n_npks, n_x, n_min_distance );
+ *n_npks = min( *n_npks, n_max_num );
+}
+
+void maxim_peaks_above_min_height( int32_t *n_locs, int32_t *n_npks, int32_t *n_x, int32_t n_size, int32_t n_min_height )
+/**
+* \brief Find peaks above n_min_height
+* \par Details
+* Find all peaks above MIN_HEIGHT
+*
+* \retval None
+*/
+{
+ int32_t i = 1, width;
+ *n_npks = 0;
+
+ while (i < n_size-1){
+ if (n_x[i] > n_min_height && n_x[i] > n_x[i-1]){ // find left edge of potential peaks
+ width = 1;
+ while (i+width < n_size && n_x[i] == n_x[i+width]) // find flat peaks
+ width++;
+ if (n_x[i] > n_x[i+width] && (*n_npks) < 15 ){ // find right edge of peaks
+ n_locs[(*n_npks)++] = i;
+ // for flat peaks, peak location is left edge
+ i += width+1;
+ }
+ else
+ i += width;
+ }
+ else
+ i++;
+ }
+}
+
+
+void maxim_remove_close_peaks( int32_t *n_locs, int32_t *n_npks, int32_t *n_x, int32_t n_min_distance )
+/**
+* \brief Remove peaks
+* \par Details
+* Remove peaks separated by less than MIN_DISTANCE
+*
+* \retval None
+*/
+{
+
+ int32_t i, j, old_npks, dist;
+
+ /* Order peaks from large to small */
+ maxim_sort_indices_descend( n_x, n_locs, *n_npks );
+
+ for ( i = -1; i < *n_npks; i++ ){
+ old_npks = *n_npks;
+ *n_npks = i+1;
+ for ( j = i+1; j < old_npks; j++ ){
+ dist = n_locs[j] - ( i == -1 ? -1 : n_locs[i] ); // lag-zero peak of autocorr is at index -1
+ if ( dist > n_min_distance || dist < -n_min_distance )
+ n_locs[(*n_npks)++] = n_locs[j];
+ }
+ }
+
+ // Resort indices longo ascending order
+ maxim_sort_ascend( n_locs, *n_npks );
+}
+
+void maxim_sort_ascend(int32_t *n_x, int32_t n_size)
+/**
+* \brief Sort array
+* \par Details
+* Sort array in ascending order (insertion sort algorithm)
+*
+* \retval None
+*/
+{
+ int32_t i, j, temp;
+ for (i = 1; i < n_size; i++) {
+ temp = n_x[i];
+ for (j = i; j > 0 && temp < n_x[j-1]; j--)
+ n_x[j] = n_x[j-1];
+ n_x[j] = temp;
+ }
+}
+
+void maxim_sort_indices_descend( int32_t *n_x, int32_t *n_indx, int32_t n_size)
+/**
+* \brief Sort indices
+* \par Details
+* Sort indices according to descending order (insertion sort algorithm)
+*
+* \retval None
+*/
+{
+ int32_t i, j, temp;
+ for (i = 1; i < n_size; i++) {
+ temp = n_indx[i];
+ for (j = i; j > 0 && n_x[temp] > n_x[n_indx[j-1]]; j--)
+ n_indx[j] = n_indx[j-1];
+ n_indx[j] = temp;
+ }
+}
+