STM32学习值传感器篇——max30102心率血氧传感器

Malak ·
更新时间:2024-11-13
· 898 次阅读

这个传感器让我了解了很多,以前使用的单片机,基本没有遇见过堆栈溢出的问题,这个传感器让我遇到了,在此记录调试心得。

首先把所有驱动文件都贴出来了

心率血氧算法代码

algorithm.h

/** \file algorithm.h ****************************************************** * * Project: MAXREFDES117# * Filename: algorithm.h * Description: This module is the heart rate/SpO2 calculation algorithm header file * * 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. ******************************************************************************* */ #ifndef ALGORITHM_H_ #define ALGORITHM_H_ #include "stm32l1xx_hal.h" #define true 1 #define false 0 #define FS 100 #define BUFFER_SIZE (FS* 5) #define HR_FIFO_SIZE 7 #define MA4_SIZE 4 // DO NOT CHANGE #define HAMMING_SIZE 5// DO NOT CHANGE #define min(x,y) ((x) < (y) ? (x) : (y)) void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer , int16_t n_ir_buffer_length, uint32_t *pun_red_buffer , int16_t *pn_spo2, int8_t *pch_spo2_valid , int16_t *pn_heart_rate , int8_t *pch_hr_valid); void maxim_find_peaks( int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num ); void maxim_peaks_above_min_height( int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height ); void maxim_remove_close_peaks( int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance ); void maxim_sort_ascend( int32_t *pn_x, int32_t n_size ); void maxim_sort_indices_descend( int32_t *pn_x, int32_t *pn_indx, int32_t n_size); #endif /* ALGORITHM_H_ */

algorithm.c

/** \file algorithm.c ****************************************************** * * Project: MAXREFDES117# * Filename: algorithm.cpp * Description: This module calculates the heart rate/SpO2 level * * * -------------------------------------------------------------------- * * This code follows the following naming conventions: * * char ch_pmod_value * char (array) s_pmod_s_string[16] * float f_pmod_value * int32_t n_pmod_value * int32_t (array) an_pmod_value[16] * int16_t w_pmod_value * int16_t (array) aw_pmod_value[16] * uint16_t uw_pmod_value * uint16_t (array) auw_pmod_value[16] * uint8_t uch_pmod_value * uint8_t (array) auch_pmod_buffer[16] * uint32_t un_pmod_value * int32_t * pn_pmod_value * * ------------------------------------------------------------------------- */ /******************************************************************************* * Copyright (C) 2016 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" const uint16_t auw_hamm[31]={ 41, 276, 512, 276, 41 }; //Hamm= long16(512* hamming(5)'); //uch_spo2_table is computed as -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ; const uint8_t uch_spo2_table[184]={ 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97, 97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91, 90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81, 80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67, 66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29, 28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5, 3, 2, 1 } ; static int32_t an_dx[ BUFFER_SIZE-MA4_SIZE]; // delta static int32_t an_x[ BUFFER_SIZE]; //ir static int32_t an_y[ BUFFER_SIZE]; //red void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer, int16_t n_ir_buffer_length, uint32_t *pun_red_buffer, int16_t *pn_spo2, int8_t *pch_spo2_valid, int16_t *pn_heart_rate, int8_t *pch_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] *pun_ir_buffer - IR sensor data buffer * \param[in] n_ir_buffer_length - IR sensor data buffer length * \param[in] *pun_red_buffer - Red sensor data buffer * \param[out] *pn_spo2 - Calculated SpO2 value * \param[out] *pch_spo2_valid - 1 if the calculated SpO2 value is valid * \param[out] *pn_heart_rate - Calculated heart rate value * \param[out] *pch_hr_valid - 1 if the calculated heart rate value is valid * * \retval None */ { uint32_t un_ir_mean ,un_only_once ; int32_t k ,n_i_ratio_count; int32_t i, s, m, n_exact_ir_valley_locs_count ,n_middle_idx; int32_t n_th1, n_npks,n_c_min; int32_t an_ir_valley_locs[15] ; int32_t an_exact_ir_valley_locs[15] ; int32_t an_dx_peak_locs[15] ; int32_t n_peak_interval_sum; int32_t n_y_ac, n_x_ac; int32_t n_spo2_calc; int32_t n_y_dc_max, n_x_dc_max; int32_t n_y_dc_max_idx, n_x_dc_max_idx; int32_t an_ratio[5],n_ratio_average; int32_t n_nume, n_denom ; // remove DC of ir signal un_ir_mean =0; for (k=0 ; k<n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ; un_ir_mean =un_ir_mean/n_ir_buffer_length ; for (k=0 ; k<n_ir_buffer_length ; k++ ) an_x[k] = pun_ir_buffer[k] - un_ir_mean ; // 4 pt Moving Average for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){ n_denom= ( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3]); an_x[k]= n_denom/(int32_t)4; } // get difference of smoothed IR signal for( k=0; k<BUFFER_SIZE-MA4_SIZE-1; k++) an_dx[k]= (an_x[k+1]- an_x[k]); // 2-pt Moving Average to an_dx for(k=0; k< BUFFER_SIZE-MA4_SIZE-2; k++){ an_dx[k] = ( an_dx[k]+an_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 -= an_dx[k] *auw_hamm[k-i] ; } an_dx[i]= s/ (int32_t)1146; // divide by sum of auw_hamm } n_th1=0; // threshold calculation for ( k=0 ; k0)? an_dx[k] : ((int32_t)0-an_dx[k])) ; } n_th1= n_th1/ ( BUFFER_SIZE-HAMMING_SIZE); // peak location is acutally index for sharpest location of raw signal since we flipped the signal maxim_find_peaks( an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE-HAMMING_SIZE, n_th1, 8, 5 );//peak_height, peak_distance, max_num_peaks n_peak_interval_sum =0; if (n_npks>=2){ for (k=1; k<n_npks; k++) n_peak_interval_sum += (an_dx_peak_locs[k]-an_dx_peak_locs[k -1]); n_peak_interval_sum=n_peak_interval_sum/(n_npks-1); *pn_heart_rate=(int32_t)(6000/n_peak_interval_sum);// beats per minutes *pch_hr_valid = 1; } else { *pn_heart_rate = -999; *pch_hr_valid = 0; } for ( k=0 ; k<n_npks ;k++) an_ir_valley_locs[k]=an_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++ ) { an_x[k] = pun_ir_buffer[k] ; an_y[k] = pun_red_buffer[k] ; } // find precise min near an_ir_valley_locs n_exact_ir_valley_locs_count =0; for(k=0 ; k<n_npks ;k++){ un_only_once =1; m=an_ir_valley_locs[k]; n_c_min= 16777216;//2^24; if (m+5 0){ for(i= m-5;i<m+5; i++) if (an_x[i]0){ un_only_once =0; } n_c_min= an_x[i] ; an_exact_ir_valley_locs[k]=i; } if (un_only_once ==0) n_exact_ir_valley_locs_count ++ ; } } if (n_exact_ir_valley_locs_count <2 ){ *pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range *pch_spo2_valid = 0; return; } // 4 pt MA for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){ an_x[k]=( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3])/(int32_t)4; an_y[k]=( an_y[k]+an_y[k+1]+ an_y[k+2]+ an_y[k+3])/(int32_t)4; } //using an_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 n_ratio_average =0; n_i_ratio_count =0; for(k=0; k< 5; k++) an_ratio[k]=0; for (k=0; k BUFFER_SIZE ){ *pn_spo2 = -999 ; // do not use SPO2 since valley loc is out of range *pch_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; k10){ for (i=an_exact_ir_valley_locs[k]; i n_x_dc_max) {n_x_dc_max =an_x[i];n_x_dc_max_idx =i; } if (an_y[i]> n_y_dc_max) {n_y_dc_max =an_y[i];n_y_dc_max_idx=i;} } n_y_ac= (an_y[an_exact_ir_valley_locs[k+1]] - an_y[an_exact_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_exact_ir_valley_locs[k]); //red n_y_ac= an_y[an_exact_ir_valley_locs[k]] + n_y_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]) ; n_y_ac= an_y[n_y_dc_max_idx] - n_y_ac; // subracting linear DC compoenents from raw n_x_ac= (an_x[an_exact_ir_valley_locs[k+1]] - an_x[an_exact_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_exact_ir_valley_locs[k]); // ir n_x_ac= an_x[an_exact_ir_valley_locs[k]] + n_x_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]); n_x_ac= an_x[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw n_nume=( n_y_ac *n_x_dc_max)>>7 ; //prepare X100 to preserve floating value n_denom= ( n_x_ac *n_y_dc_max)>>7; if (n_denom>0 && n_i_ratio_count 1) n_ratio_average =( an_ratio[n_middle_idx-1] +an_ratio[n_middle_idx])/2; // use median else n_ratio_average = an_ratio[n_middle_idx ]; if( n_ratio_average>2 && n_ratio_average <184){ n_spo2_calc= uch_spo2_table[n_ratio_average] ; *pn_spo2 = n_spo2_calc ; *pch_spo2_valid = 1;// float_SPO2 = -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ; // for comparison with table } else{ *pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range *pch_spo2_valid = 0; } } void maxim_find_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_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( pn_locs, pn_npks, pn_x, n_size, n_min_height ); maxim_remove_close_peaks( pn_locs, pn_npks, pn_x, n_min_distance ); *pn_npks = min( *pn_npks, n_max_num ); } void maxim_peaks_above_min_height(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_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, n_width; *pn_npks = 0; while (i n_min_height && pn_x[i] > pn_x[i-1]){ // find left edge of potential peaks n_width = 1; while (i+n_width pn_x[i+n_width] && (*pn_npks) < 15 ){ // find right edge of peaks pn_locs[(*pn_npks)++] = i; // for flat peaks, peak location is left edge i += n_width+1; } else i += n_width; } else i++; } } void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_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, n_old_npks, n_dist; /* Order peaks from large to small */ maxim_sort_indices_descend( pn_x, pn_locs, *pn_npks ); for ( i = -1; i < *pn_npks; i++ ){ n_old_npks = *pn_npks; *pn_npks = i+1; for ( j = i+1; j n_min_distance || n_dist < -n_min_distance ) pn_locs[(*pn_npks)++] = pn_locs[j]; } } // Resort indices longo ascending order maxim_sort_ascend( pn_locs, *pn_npks ); } void maxim_sort_ascend(int32_t *pn_x,int32_t n_size) /** * \brief Sort array * \par Details * Sort array in ascending order (insertion sort algorithm) * * \retval None */ { int32_t i, j, n_temp; for (i = 1; i 0 && n_temp < pn_x[j-1]; j--) pn_x[j] = pn_x[j-1]; pn_x[j] = n_temp; } } void maxim_sort_indices_descend(int32_t *pn_x, int32_t *pn_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, n_temp; for (i = 1; i 0 && pn_x[n_temp] > pn_x[pn_indx[j-1]]; j--) pn_indx[j] = pn_indx[j-1]; pn_indx[j] = n_temp; } }

 ,max30102驱动代码

max30102.h

#ifndef __MAX30102_H #define __MAX30102_H #include "stm32l1xx_hal.h" #include "max_iic.h" ////////////////////////////////////////////////////////////////////////////////// #define MAX30102_INT PBin(9) #define I2C_WR 0 /* 写控制bit */ #define I2C_RD 1 /* 读控制bit */ #define max30102_WR_address 0xAE #define I2C_WRITE_ADDR 0xAE #define I2C_READ_ADDR 0xAF //register addresses #define REG_INTR_STATUS_1 0x00 #define REG_INTR_STATUS_2 0x01 #define REG_INTR_ENABLE_1 0x02 #define REG_INTR_ENABLE_2 0x03 #define REG_FIFO_WR_PTR 0x04 #define REG_OVF_COUNTER 0x05 #define REG_FIFO_RD_PTR 0x06 #define REG_FIFO_DATA 0x07 #define REG_FIFO_CONFIG 0x08 #define REG_MODE_CONFIG 0x09 #define REG_SPO2_CONFIG 0x0A #define REG_LED1_PA 0x0C #define REG_LED2_PA 0x0D #define REG_PILOT_PA 0x10 #define REG_MULTI_LED_CTRL1 0x11 #define REG_MULTI_LED_CTRL2 0x12 #define REG_TEMP_INTR 0x1F #define REG_TEMP_FRAC 0x20 #define REG_TEMP_CONFIG 0x21 #define REG_PROX_INT_THRESH 0x30 #define REG_REV_ID 0xFE #define REG_PART_ID 0xFF void max30102_init(void); void max30102_reset(void); uint8_t max30102_Bus_Write(uint8_t Register_Address, uint8_t Word_Data); uint8_t max30102_Bus_Read(uint8_t Register_Address); void max30102_FIFO_ReadWords(uint8_t Register_Address,uint16_t Word_Data[][2],uint8_t count); void max30102_FIFO_ReadBytes(uint8_t Register_Address,uint8_t* Data); void maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data); void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data); void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led); #endif

max30102.c

#include "max30102.h" uint8_t max30102_Bus_Write(uint8_t Register_Address, uint8_t Word_Data) { /* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */ /* 第1步:发起I2C总线启动信号 */ I2C_Start(); /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ I2C_SendByte(max30102_WR_address | I2C_WR); /* 此处是写指令 */ /* 第3步:发送ACK */ if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第4步:发送字节地址 */ I2C_SendByte(Register_Address); if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第5步:开始写入数据 */ I2C_SendByte(Word_Data); /* 第6步:发送ACK */ if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 发送I2C总线停止信号 */ I2C_Stop(); return 1; /* 执行成功 */ cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */ /* 发送I2C总线停止信号 */ I2C_Stop(); return 0; } uint8_t max30102_Bus_Read(uint8_t Register_Address) { uint8_t data; /* 第1步:发起I2C总线启动信号 */ I2C_Start(); /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ I2C_SendByte(max30102_WR_address | I2C_WR); /* 此处是写指令 */ /* 第3步:发送ACK */ if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第4步:发送字节地址, */ I2C_SendByte((uint8_t)Register_Address); if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第6步:重新启动I2C总线。下面开始读取数据 */ I2C_Start(); /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ I2C_SendByte(max30102_WR_address | I2C_RD); /* 此处是读指令 */ /* 第8步:发送ACK */ if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第9步:读取数据 */ { data = I2C_ReceiveByte(0); /* 读1个字节 */ I2C_NoAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */ } /* 发送I2C总线停止信号 */ I2C_Stop(); return data; /* 执行成功 返回data值 */ cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */ /* 发送I2C总线停止信号 */ I2C_Stop(); return 0; } void max30102_FIFO_ReadWords(uint8_t Register_Address,uint16_t Word_Data[][2],uint8_t count) { uint8_t i=0; uint8_t no = count; uint8_t data1, data2; /* 第1步:发起I2C总线启动信号 */ I2C_Start(); /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ I2C_SendByte(max30102_WR_address | I2C_WR); /* 此处是写指令 */ /* 第3步:发送ACK */ if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第4步:发送字节地址, */ I2C_SendByte((uint8_t)Register_Address); if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第6步:重新启动I2C总线。下面开始读取数据 */ I2C_Start(); /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ I2C_SendByte(max30102_WR_address | I2C_RD); /* 此处是读指令 */ /* 第8步:发送ACK */ if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第9步:读取数据 */ while (no) { data1 = I2C_ReceiveByte(0); I2C_Ack(); data2 = I2C_ReceiveByte(0); I2C_Ack(); Word_Data[i][0] = (((uint16_t)data1 << 8) | data2); // data1 = I2C_ReceiveByte(0); I2C_Ack(); data2 = I2C_ReceiveByte(0); if(1==no) I2C_NoAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */ else I2C_Ack(); Word_Data[i][1] = (((uint16_t)data1 << 8) | data2); no--; i++; } /* 发送I2C总线停止信号 */ I2C_Stop(); cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */ /* 发送I2C总线停止信号 */ I2C_Stop(); } void max30102_FIFO_ReadBytes(uint8_t Register_Address,uint8_t* Data) { max30102_Bus_Read(REG_INTR_STATUS_1); max30102_Bus_Read(REG_INTR_STATUS_2); /* 第1步:发起I2C总线启动信号 */ I2C_Start(); /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ I2C_SendByte(max30102_WR_address | I2C_WR); /* 此处是写指令 */ /* 第3步:发送ACK */ if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第4步:发送字节地址, */ I2C_SendByte((uint8_t)Register_Address); if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第6步:重新启动I2C总线。下面开始读取数据 */ I2C_Start(); /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ I2C_SendByte(max30102_WR_address | I2C_RD); /* 此处是读指令 */ /* 第8步:发送ACK */ if (I2C_WaitAck() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第9步:读取数据 */ Data[0] = I2C_ReceiveByte(1); Data[1] = I2C_ReceiveByte(1); Data[2] = I2C_ReceiveByte(1); Data[3] = I2C_ReceiveByte(1); Data[4] = I2C_ReceiveByte(1); Data[5] = I2C_ReceiveByte(0); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */ /* 发送I2C总线停止信号 */ I2C_Stop(); cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */ /* 发送I2C总线停止信号 */ I2C_Stop(); } void max30102_init(void) { max30102_reset(); if(max30102_Bus_Write(REG_INTR_ENABLE_1,0xc0)==0) { printf("1"); } // INTR setting HAL_Delay(10); if(max30102_Bus_Write(REG_INTR_ENABLE_2,0x00)==0) { printf("2"); } HAL_Delay(10); if(max30102_Bus_Write(REG_FIFO_WR_PTR,0x00)==0) { printf("3"); } //FIFO_WR_PTR[4:0] HAL_Delay(10); if(max30102_Bus_Write(REG_OVF_COUNTER,0x00)==0) //OVF_COUNTER[4:0] { printf("4"); } HAL_Delay(10); if(max30102_Bus_Write(REG_FIFO_RD_PTR,0x00)==0) //FIFO_RD_PTR[4:0] { printf("5"); } HAL_Delay(10); if(max30102_Bus_Write(REG_FIFO_CONFIG,0x0f)==0) //sample avg = 1, fifo rollover=false, fifo almost full = 17 { printf("6"); } HAL_Delay(10); if(max30102_Bus_Write(REG_MODE_CONFIG,0x03)==0) //0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED { printf("7"); } HAL_Delay(10); if(max30102_Bus_Write(REG_SPO2_CONFIG,0x27)==0) // SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS) { printf("8"); } HAL_Delay(10); if(max30102_Bus_Write(REG_LED1_PA,0x24)==0) //Choose value for ~ 7mA for LED1 { printf("9"); } HAL_Delay(10); if(max30102_Bus_Write(REG_LED2_PA,0x24)==0) // Choose value for ~ 7mA for LED2 { printf("10"); } HAL_Delay(10); if(max30102_Bus_Write(REG_PILOT_PA,0x7f)==0) // Choose value for ~ 25mA for Pilot LED { printf("11"); } HAL_Delay(10); } void max30102_reset(void) { max30102_Bus_Write(REG_MODE_CONFIG,0x40); max30102_Bus_Write(REG_MODE_CONFIG,0x40); } void maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data) { IIC_Write_One_Byte(I2C_WRITE_ADDR,uch_addr,uch_data); } void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data) { IIC_Read_One_Byte(I2C_WRITE_ADDR,uch_addr,puch_data); } void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led) { uint32_t un_temp; unsigned char uch_temp; char ach_i2c_data[6]; *pun_red_led=0; *pun_ir_led=0; //read and clear status register maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp); maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp); IIC_ReadBytes(I2C_WRITE_ADDR,REG_FIFO_DATA,(uint8_t *)ach_i2c_data,6); un_temp=(unsigned char) ach_i2c_data[0]; un_temp<<=16; *pun_red_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[1]; un_temp<<=8; *pun_red_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[2]; *pun_red_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[3]; un_temp<<=16; *pun_ir_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[4]; un_temp<<=8; *pun_ir_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[5]; *pun_ir_led+=un_temp; *pun_red_led&=0x03FFFF; //Mask MSB [23:18] *pun_ir_led&=0x03FFFF; //Mask MSB [23:18] }

iic驱动代码

max_iic.h

#ifndef _MAX_IIC_H #define _MAX_IIC_H #include "stm32l1xx_hal.h" #include "stdint.h" #include "main.h" #define SCL_H HAL_GPIO_WritePin(SCL_GPIO_Port, SCL_Pin, GPIO_PIN_SET) #define SCL_L HAL_GPIO_WritePin(SCL_GPIO_Port, SCL_Pin, GPIO_PIN_RESET) #define SDA_H HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_SET) #define SDA_L HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_RESET) #define MAX_INT_H HAL_GPIO_WritePin(MAX_INT_GPIO_Port, MAX_INT_Pin, GPIO_PIN_SET) #define SCL_read HAL_GPIO_ReadPin(SCL_GPIO_Port,SCL_Pin) #define SDA_read HAL_GPIO_ReadPin(SDA_GPIO_Port,SDA_Pin) #define MAX_INT_read HAL_GPIO_ReadPin(MAX_INT_GPIO_Port,MAX_INT_Pin) #define bool int #define TRUE 1 #define FALSE 0 void I2C_delay(void); bool I2C_Start(void); void I2C_Stop(void) ; void I2C_Ack(void); void I2C_NoAck(void); uint8_t I2C_WaitAck(void); void I2C_SendByte(uint8_t SendByte); uint8_t I2C_ReceiveByte(uint8_t ack); bool I2C_Write(uint8_t dev,uint8_t WriteAddr,uint8_t WriteData); bool I2C_Write2(uint8_t WriteAddr,uint8_t WriteData); uint8_t I2C_Read(uint8_t dev,uint8_t WriteAddr); uint8_t I2C_Read2(uint8_t WriteAddr) ; void I2C_DevRead(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *rbuf); void I2C_DevWrite(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *wbuf); void IIC_WriteBytes(uint8_t WriteAddr,uint8_t* data,uint8_t dataLength); void IIC_ReadBytes(uint8_t deviceAddr, uint8_t writeAddr,uint8_t* data,uint8_t dataLength); void IIC_Read_One_Byte(uint8_t daddr,uint8_t addr,uint8_t* data); void IIC_Write_One_Byte(uint8_t daddr,uint8_t addr,uint8_t data); #endif

 max_iic.c

#include "max_iic.h" void I2C_delay(void) { uint16_t i=160; //Set delay time value while(i) { i--; } } /** * @brief I2C Start * @param None * @retval None */ bool I2C_Start(void) { SDA_H; I2C_delay(); SCL_H; I2C_delay(); if(!SDA_read)return FALSE; //SDA Always low return FALSE SDA_L; I2C_delay(); if(SDA_read) return FALSE; //SDA Always high return FALSE SCL_L; I2C_delay(); return TRUE; } /** * @brief I2C Stop * @param None * @retval None */ void I2C_Stop(void) { SCL_L; I2C_delay(); SDA_L; I2C_delay(); SCL_H; I2C_delay(); SDA_H; I2C_delay(); } /** * @brief I2C Ack * @param None * @retval None */ void I2C_Ack(void) { SCL_L; I2C_delay(); SDA_L; I2C_delay(); SCL_H; I2C_delay(); SCL_L; I2C_delay(); } /** * @brief I2C No Ack * @param None * @retval None */ void I2C_NoAck(void) { SCL_L; I2C_delay(); SDA_H; I2C_delay(); SCL_H; I2C_delay(); SCL_L; I2C_delay(); } /** * @brief Wait Ack * @param None * @retval bool FALSE:1--->no ACK * TRUE :0--->ACK */ uint8_t I2C_WaitAck(void) { uint8_t re; SCL_L; I2C_delay(); SDA_H; I2C_delay(); SCL_H; I2C_delay(); if(SDA_read) { re=1; } else re=0; SCL_L; return re; } /** * @brief Send one Byte * @param uint8_t SendByte * @retval None */ void I2C_SendByte(uint8_t SendByte) { uint8_t i=8; while(i--) { SCL_L; I2C_delay(); if((SendByte&0x80)) { SDA_H; } else { SDA_L; } SendByte<<=1; I2C_delay(); SCL_H; I2C_delay(); } SCL_L; } /** * @brief Receive one Byte * @param uint8_t ack * @retval receive receive one byte */ uint8_t I2C_ReceiveByte(uint8_t ack) { unsigned char i=8,receive=0; SDA_H; while(i--) { receive<<=1; SCL_L; I2C_delay(); SCL_H; I2C_delay(); if(SDA_read) { receive|=0x01; } } SCL_L; if (!ack) I2C_NoAck(); //Send Nack else I2C_Ack(); //Send ack return receive; } /** * @brief Write a Byte to the device * @param uint8_t WriteAddr,uint8_t WriteData * @retval bool FALSE: 0 * TRUE : 1 */ bool I2C_Write(uint8_t dev,uint8_t WriteAddr,uint8_t WriteData) { I2C_Start(); I2C_SendByte(dev); //Send write cmd I2C_WaitAck(); I2C_SendByte(WriteAddr); //Send addr I2C_WaitAck(); I2C_SendByte(WriteData); //Send data I2C_WaitAck(); I2C_Stop(); //iic stop return TRUE; } /********************************************************************/ bool I2C_Write2(uint8_t WriteAddr,uint8_t WriteData) { if (!I2C_Start()) return FALSE; I2C_SendByte(0x10);//设置器件地址+段地址 if (!I2C_WaitAck()) { I2C_Stop(); return FALSE; } I2C_SendByte(WriteAddr); //设置段内地址 I2C_WaitAck(); I2C_SendByte(WriteData); I2C_WaitAck(); I2C_Stop(); return TRUE; } /************************************************************************/ /** * @brief Read a byte from the device * @param uint8_t WriteAddr * @retval temp Return the read byte */ uint8_t I2C_Read(uint8_t dev,uint8_t WriteAddr) { uint8_t temp=0; I2C_Start(); I2C_SendByte(dev); //Send write cmd I2C_WaitAck(); I2C_SendByte(WriteAddr); //Send addr I2C_WaitAck(); I2C_Start(); I2C_SendByte(dev|1); //Send read cmd I2C_WaitAck(); temp=I2C_ReceiveByte(0); I2C_Stop(); return temp; } /*********************************************************************************/ //读出1串数据 uint8_t I2C_Read2(uint8_t WriteAddr) { uint8_t tempDat=0; if (!I2C_Start()) return FALSE; I2C_SendByte(0x77);//设置器件地址+段地址 if (!I2C_WaitAck()) { I2C_Stop(); return FALSE; } I2C_SendByte(WriteAddr); //设置低起始地址 I2C_WaitAck(); I2C_Start(); I2C_SendByte(0x77 | 0x01); I2C_WaitAck(); tempDat = I2C_ReceiveByte(0); I2C_Stop(); return tempDat; } /** * @brief Read continuously * @param uint8_t devaddr device addr * uint8_t addr Start addr * uint8_t len read data length * uint8_t *rbuf read data buf * @retval None */ void I2C_DevRead(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *rbuf) { int i=0; I2C_Start(); I2C_SendByte(devaddr); if(!I2C_WaitAck()) { I2C_Stop(); return ; } I2C_SendByte(addr); //address ++ if(!I2C_WaitAck()) { I2C_Stop(); return ; } I2C_Start(); I2C_SendByte(devaddr|0x01); if(!I2C_WaitAck()) { I2C_Stop(); return ; } for(i=0; i<len; i++) { if(i==len-1) { rbuf[i]=I2C_ReceiveByte(0); //The last byte does not answer } else rbuf[i]=I2C_ReceiveByte(1); } I2C_Stop( ); } /** * @brief Write continuously * @param uint8_t devaddr device addr * uint8_t addr Start addr * uint8_t len read data length * uint8_t *rbuf read data buf * @retval None */ void I2C_DevWrite(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *wbuf) { int i=0; I2C_Start(); I2C_SendByte(devaddr); I2C_WaitAck(); I2C_SendByte(addr); //address ++ I2C_WaitAck(); for(i=0; i<len; i++) { I2C_SendByte(wbuf[i]); I2C_WaitAck(); } I2C_Stop( ); } //========================================================== void IIC_WriteBytes(uint8_t WriteAddr,uint8_t* data,uint8_t dataLength) { uint8_t i; I2C_Start(); I2C_SendByte(WriteAddr); //发送写命令 I2C_WaitAck(); for(i=0;i<dataLength;i++) { I2C_SendByte(data[i]); I2C_WaitAck(); } I2C_Stop();//产生一个停止条件 I2C_delay(); } void IIC_ReadBytes(uint8_t deviceAddr, uint8_t writeAddr,uint8_t* data,uint8_t dataLength) { uint8_t i; I2C_Start(); I2C_SendByte(deviceAddr); //发送写命令 I2C_WaitAck(); I2C_SendByte(writeAddr); I2C_WaitAck(); I2C_SendByte(deviceAddr|0X01);//进入接收模式 I2C_WaitAck(); for(i=0;i<dataLength-1;i++) { data[i] = I2C_ReceiveByte(1); } data[dataLength-1] = I2C_ReceiveByte(0); I2C_Stop();//产生一个停止条件 I2C_delay(); } void IIC_Read_One_Byte(uint8_t daddr,uint8_t addr,uint8_t* data) { I2C_Start(); I2C_SendByte(daddr); //发送写命令 I2C_WaitAck(); I2C_SendByte(addr);//发送地址 I2C_WaitAck(); I2C_Start(); I2C_SendByte(daddr|0X01);//进入接收模式 I2C_WaitAck(); *data = I2C_ReceiveByte(0); I2C_Stop();//产生一个停止条件 } void IIC_Write_One_Byte(uint8_t daddr,uint8_t addr,uint8_t data) { I2C_Start(); I2C_SendByte(daddr); //发送写命令 I2C_WaitAck(); I2C_SendByte(addr);//发送地址 I2C_WaitAck(); I2C_SendByte(data); //发送字节 I2C_WaitAck(); I2C_Stop();//产生一个停止条件 I2C_delay(); }

main.c  

/* Includes ------------------------------------------------------------------*/ #include "main.h" #include "stm32l1xx_hal.h" #include "usart.h" #include "gpio.h" /* USER CODE BEGIN Includes */ #include "max30102.h" #include "algorithm.h" /* USER CODE END Includes */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* Private variables ---------------------------------------------------------*/ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* Private function prototypes -----------------------------------------------*/ /* USER CODE END PFP */ /* USER CODE BEGIN 0 */ uint32_t aun_ir_buffer[500]; //IR LED sensor data uint16_t n_ir_buffer_length; //data length uint32_t aun_red_buffer[500]; //Red LED sensor data int16_t n_sp02; //SPO2 value int8_t ch_spo2_valid; //indicator to show if the SP02 calculation is valid int16_t n_heart_rate; //heart rate value int8_t ch_hr_valid; //indicator to show if the heart rate calculation is valid #define MAX_BRIGHTNESS 255 /* USER CODE END 0 */ int main(void) { /* USER CODE BEGIN 1 */ uint32_t un_min, un_max; int i; uint8_t temp[6]; uint8_t dis_hr=0,dis_spo2=0; /* USER CODE END 1 */ /* MCU Configuration----------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ printf("System Running\r\n"); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ MAX_INT_H; max30102_init(); printf("\r\n MAX30102 init \r\n"); un_min=0x3FFFF; un_max=0; n_ir_buffer_length=500; //buffer length of 100 stores 5 seconds of samples running at 100sps //read the first 500 samples, and determine the signal range for(i=0;i<n_ir_buffer_length;i++) { while(MAX_INT_read==1); //wait until the interrupt pin asserts max30102_FIFO_ReadBytes(REG_FIFO_DATA,temp); aun_ir_buffer[i] = (long)((long)((long)temp[0]&0x03)<<16) | (long)temp[1]<<8 | (long)temp[2]; // Combine values to get the actual number aun_red_buffer[i] = (long)((long)((long)temp[3] & 0x03)<<16) |(long)temp[4]<aun_red_buffer[i]) un_min=aun_red_buffer[i]; //update signal min if(un_max<aun_red_buffer[i]) un_max=aun_red_buffer[i]; //update signal max printf("red="); printf("%i", aun_red_buffer[i]); printf(", ir="); printf("%i\n\r", aun_ir_buffer[i]); } // un_prev_data=aun_red_buffer[i]; //calculate heart rate and SpO2 after first 500 samples (first 5 seconds of samples) maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); while(1) { i=0; un_min=0x3FFFF; un_max=0; //dumping the first 100 sets of samples in the memory and shift the last 400 sets of samples to the top for(i=100;iaun_red_buffer[i]) un_min=aun_red_buffer[i]; if(un_max<aun_red_buffer[i]) un_max=aun_red_buffer[i]; } //take 100 sets of samples before calculating the heart rate. for(i=400;i<500;i++) { while(MAX_INT_read==1); max30102_FIFO_ReadBytes(REG_FIFO_DATA,temp); aun_ir_buffer[i] = (long)((long)((long)temp[0]&0x03)<<16) | (long)temp[1]<<8 | (long)temp[2]; // Combine values to get the actual number aun_red_buffer[i] = (long)((long)((long)temp[3] & 0x03)<<16) |(long)temp[4]<<8 | (long)temp[5]; // Combine values to get the actual number //send samples and calculation result to terminal program through UART if(ch_hr_valid == 1 && ch_spo2_valid ==1 && (n_heart_rate-100)<120 && n_sp02140)//**/ ch_hr_valid == 1 && ch_spo2_valid ==1 && (n_heart_rate-100)<120 && n_sp02<101 { dis_hr = n_heart_rate-100; //这个-100我加的 dis_spo2 = n_sp02; printf("HR=%i, ", dis_hr); printf("HRvalid=%i, ", ch_hr_valid); printf("SpO2=%i, ", dis_spo2); printf("SPO2Valid=%i\r\n", ch_spo2_valid); } else { dis_hr = 0; dis_spo2 = 0; } } printf("HR=%i, ", dis_hr); printf("HRvalid=%i, ", ch_hr_valid); printf("SpO2=%i, ", dis_spo2); printf("SPO2Valid=%i\r\n", ch_spo2_valid); maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); } /* USER CODE END WHILE */ }

//上边给出的算法代码是原始的,修改地方已标注

这个传感器整体来说不算挺难,不过我在这个上边耗费了不小的时间,主要有一个很大的坑就是它的算法程序定义了大概有5个 uint32_t   aun_ir_buffer[500] 这样类似的数组,我用的stm32l151c8t6的传感器,局部数组太大导致一直程序一烧进去直接卡死在hardfault_handler()这个中断里边  程序根本跑不动,因为mcu已经确定不能换了,只能通过其他方法改善这个问题,刚开始使用串口打出来aun_ir_buffer[500]这些数组里边的 数发现他们都大于65536,好像也不能将uint32_t改为uint16_t,于是改了其他感觉可以改的uint32_t的变量,程序此刻是可以跑起来了,但是程序总是运行到一半的时候就又卡死在hardfault_handler(),头疼了好几天,今天灵机一动,给它们两个主要占地的((aun_ir_buffer[500],aun_red_buffer[500]))同时减80000再给他赋值,等到用这个数据的时候再给他加80000,这样数据就可以用uint16_t定义了,也不会出现溢出的情况 ,根据此思路测试了一下数据出来了可是测出来的心率值比实际的大了100左右(用小米手环和max30102同时测手指数据),目前不知道是什么问题,就给了一个最直接的办法,把解算出来的心率只都减100,心率基本和小米手环测出来的只有微小差异。(感觉有点投机,以后有时间再好好研究一下)


作者:脱发小白龙



心率 max stm32 学习 传感器

需要 登录 后方可回复, 如果你还没有账号请 注册新账号