在这个越来越智能化的时代,出现了我们常见的智能手环、智能手表等一些列的智能穿戴设备。因此很多传感器被集成到了这些智能设备内。而心率、血氧等人体生命体征的重要参数也越来越被人们所重视。
恰好近日,朋友赵先生要做一个关于血氧心率检测仪的课题,让我帮忙一起设计一下。
首先了解一下概念,心率传感器是什么?
光学心率传感器是是智能穿戴设备中最为普及的用于心率检测的传感器之一。它采用电光溶剂脉搏波描记法(PPG)来测量心率及其他生物计量指标。
测量原理:通过电容灯光射向皮肤,透过皮肤组织反射回的光被光敏传感器接受并转换成电信号,再经过电信号转换成数字信号,再根据血液的吸光率算出心率。简化测量过程就是:发射光——转换成电信号——转换成数字信号。

光学心率传感器使用四个主要技术元件来测量心率:
光发射器 - 通常至少由两个光发射二极管(LED)构成,它们会将光波照进皮肤内部。
光电二极管和模拟前端(AFE) - 这些元件捕获穿戴者折射的光,并将这些模拟信号转换成数字信号用于计算可实际应用的心率数据。
加速计 - 加速计可测量运动,与光信号结合运用,作为PPG算法的输入。
算法 - 算法能够处理来自AFE和加速计的信号,然后将处理后的信号叠加到PPG波形上,由此可生成持续的、运动容错心率数据和其他生物计量数据。
光学心率传感器可测量哪些生物计量指标?
采用 PPG 的光学传感器在获得高品质 PPG 信号的情况下可获取大量的基础生物计量值,光学心率传感器不止获取心率数据那么简单,还可以获取以下生物计量指标:
呼吸率 - 休息时的呼吸率越低,通常这表明身体状况越好。
最大摄氧量(VO2max)– VO2测量人体可以摄入的最大氧气量,是人们广泛使用的有氧耐力指标。
血氧水平(SpO2) - 是指血液中的氧气浓度。
R-R间期(心率变异率)- R-R间期是血脉冲的间隔时间;一般而言,心跳间隔时间越长越好。R-R间期分析,可用作压力水平和不同心脏问题的指标。
血压 - 通过PPG传感器信号,无需使用血压计即可测量血压。
血液灌注 - 灌注是指人体推动血液流经循环系统的能力,特别是在濒于死亡时流经全身毛细血管床的能力。因为PPG传感器可跟踪血液流动,所以可以测量血流相对灌注率及血液灌注水平的变化。
心效率 - 这是心脑血管健康和身体状况的另一个指标,一般来说,它测量的是心脏每搏的做功效率。
了解了相关的生物计量指标,那我们接下来开始进行设计。
首先选择传感器型号,我们选择MAX30102光学心率传感器,根据MAX30102的数据手册,使用IIC的通信方式。使用STM32f103c8t6单片机作为驱动芯片,使用C语言进行裸机程序的设计,整个心率血氧检测仪的设计还是很简单。
网上相关的设计也比较多,但是又比较杂乱,这里而大鑫实际课题项目的源码和电路比较复杂,这里仅仅展示一下max30102的相关驱动代码吧。
#include "max30102.h"#include "myiic.h"#define max30102_WR_address 0xAEbool maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data)/*** \brief Write a value to a MAX30102 register* \par Details* This function writes a value to a MAX30102 register** \param[in] uch_addr - register address* \param[in] uch_data - register data** \retval true on success*/{/* µÚ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(uch_addr);if (i2c_WaitAck() != 0){goto cmd_fail; /* EEPROMÆ÷¼þÎÞÓ¦´ð */}/* µÚ5²½£º¿ªÊ¼Ð´ÈëÊý¾Ý */i2c_SendByte(uch_data);/* µÚ6²½£º·¢ËÍACK */if (i2c_WaitAck() != 0){goto cmd_fail; /* EEPROMÆ÷¼þÎÞÓ¦´ð */}/* ·¢ËÍI2C×ÜÏßÍ£Ö¹ÐźŠ*/i2c_Stop();return true; /* Ö´Ðгɹ¦ */cmd_fail: /* ÃüÁîÖ´ÐÐʧ°Üºó£¬ÇмǷ¢ËÍÍ£Ö¹Ðźţ¬±ÜÃâÓ°ÏìI2C×ÜÏßÉÏÆäËûÉ豸 *//* ·¢ËÍI2C×ÜÏßÍ£Ö¹ÐźŠ*/i2c_Stop();return false;}bool maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data)/*** \brief Read a MAX30102 register* \par Details* This function reads a MAX30102 register** \param[in] uch_addr - register address* \param[out] puch_data - pointer that stores the register data** \retval true on success*/{/* µÚ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)uch_addr);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²½£º¶ÁÈ¡Êý¾Ý */{*puch_data = i2c_ReadByte(); /* ¶Á1¸ö×Ö½Ú */i2c_NAck(); /* ×îºó1¸ö×Ö½Ú¶ÁÍêºó£¬CPU²úÉúNACKÐźÅ(Çý¶¯SDA = 1) */}/* ·¢ËÍI2C×ÜÏßÍ£Ö¹ÐźŠ*/i2c_Stop();return true; /* Ö´Ðгɹ¦ ·µ»ØdataÖµ */cmd_fail: /* ÃüÁîÖ´ÐÐʧ°Üºó£¬ÇмǷ¢ËÍÍ£Ö¹Ðźţ¬±ÜÃâÓ°ÏìI2C×ÜÏßÉÏÆäËûÉ豸 *//* ·¢ËÍI2C×ÜÏßÍ£Ö¹ÐźŠ*/i2c_Stop();return false;}bool maxim_max30102_init(void)/*** \brief Initialize the MAX30102* \par Details* This function initializes the MAX30102** \param None** \retval true on success*/{if(!maxim_max30102_write_reg(REG_INTR_ENABLE_1, 0xc0)) // INTR settingreturn false;if(!maxim_max30102_write_reg(REG_INTR_ENABLE_2, 0x00))return false;if(!maxim_max30102_write_reg(REG_FIFO_WR_PTR, 0x00)) //FIFO_WR_PTR[4:0]return false;if(!maxim_max30102_write_reg(REG_OVF_COUNTER, 0x00)) //OVF_COUNTER[4:0]return false;if(!maxim_max30102_write_reg(REG_FIFO_RD_PTR, 0x00)) //FIFO_RD_PTR[4:0]return false;if(!maxim_max30102_write_reg(REG_FIFO_CONFIG, 0x6f)) //sample avg = 8, fifo rollover=false, fifo almost full = 17return false;if(!maxim_max30102_write_reg(REG_MODE_CONFIG, 0x03)) //0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LEDreturn false;if(!maxim_max30102_write_reg(REG_SPO2_CONFIG, 0x2F)) // SPO2_ADC range = 4096nA, SPO2 sample rate (400 Hz), LED pulseWidth (411uS)return false;if(!maxim_max30102_write_reg(REG_LED1_PA, 0x17)) //Choose value for ~ 4.5mA for LED1return false;if(!maxim_max30102_write_reg(REG_LED2_PA, 0x17)) // Choose value for ~ 4.5mA for LED2return false;if(!maxim_max30102_write_reg(REG_PILOT_PA, 0x7f)) // Choose value for ~ 25mA for Pilot LEDreturn false;return true;}bool maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)/*** \brief Read a set of samples from the MAX30102 FIFO register* \par Details* This function reads a set of samples from the MAX30102 FIFO register** \param[out] *pun_red_led - pointer that stores the red LED reading data* \param[out] *pun_ir_led - pointer that stores the IR LED reading data** \retval true on success*/{uint32_t un_temp;uint8_t uch_temp;*pun_ir_led = 0;*pun_red_led = 0;maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);/* µÚ1²½£º·¢ÆðI2C×ÜÏ߯ô¶¯ÐźŠ*/i2c_Start();/* µÚ2²½£º·¢Æð¿ØÖÆ×Ö½Ú£¬¸ß7bitÊǵØÖ·£¬bit0ÊǶÁд¿ØÖÆÎ»£¬0±íʾд£¬1±íʾ¶Á */i2c_SendByte(max30102_WR_address | I2C_WR); /* ´Ë´¦ÊÇдָÁî *//* µÚ3²½£º·¢ËÍACK */if (i2c_WaitAck() != 0){printf("read fifo failed");goto cmd_fail; /* EEPROMÆ÷¼þÎÞÓ¦´ð */}/* µÚ4²½£º·¢ËÍ×Ö½ÚµØÖ·£¬ */i2c_SendByte((uint8_t)REG_FIFO_DATA);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Æ÷¼þÎÞÓ¦´ð */}un_temp = i2c_ReadByte();i2c_Ack();un_temp <<= 16;*pun_red_led += un_temp;un_temp = i2c_ReadByte();i2c_Ack();un_temp <<= 8;*pun_red_led += un_temp;un_temp = i2c_ReadByte();i2c_Ack();*pun_red_led += un_temp;un_temp = i2c_ReadByte();i2c_Ack();un_temp <<= 16;*pun_ir_led += un_temp;un_temp = i2c_ReadByte();i2c_Ack();un_temp <<= 8;*pun_ir_led += un_temp;un_temp = i2c_ReadByte();i2c_Ack();*pun_ir_led += un_temp;*pun_red_led &= 0x03FFFF; //Mask MSB [23:18]*pun_ir_led &= 0x03FFFF; //Mask MSB [23:18]/* ·¢ËÍI2C×ÜÏßÍ£Ö¹ÐźŠ*/i2c_Stop();return true;cmd_fail: /* ÃüÁîÖ´ÐÐʧ°Üºó£¬ÇмǷ¢ËÍÍ£Ö¹Ðźţ¬±ÜÃâÓ°ÏìI2C×ÜÏßÉÏÆäËûÉ豸 *//* ·¢ËÍI2C×ÜÏßÍ£Ö¹ÐźŠ*/i2c_Stop();return false;}bool maxim_max30102_reset()/*** \brief Reset the MAX30102* \par Details* This function resets the MAX30102** \param None** \retval true on success*/{if(!maxim_max30102_write_reg(REG_MODE_CONFIG, 0x40))return false;elsereturn true;}
#include "max30102.h"#include "myiic.h"#include "algorithm.h"#include "hr.h"extern __IO uint16_t HR_tick;uint32_t aun_ir_buffer[150]; //IR LED sensor dataint32_t n_ir_buffer_length=150; //data lengthuint32_t aun_red_buffer[150]; //Red LED sensor dataint32_t n_spo2; //SPO2 valueint8_t ch_spo2_valid; //indicator to show if the SP02 calculation is validint32_t n_heart_rate; //heart rate valueint8_t ch_hr_valid; //indicator to show if the heart rate calculation is validuint8_t uch_dummy;int32_t hrTimeout=0;int32_t hrValidCnt=0;int32_t hrThrowOutSamp=0;int32_t hrBuffFilled;int32_t hr_buf[16];int32_t hrSum;int32_t hrAvg;#define MAX30102_INT HAL_GPIO_ReadPin(INT_GPIO_Port,INT_Pin)//±äÁ¿À´¼ÆËã·´Ó³ÐÄÌøµÄLEDÁÁ¶Èuint32_t un_min=0x3FFFF, un_max=0, un_prev_data,un_brightness;int32_t max_i;float f_temp;uint8_t temp[6];uint8_t str[100];uint8_t dis_hr=0,dis_spo2=0;#define MAX_BRIGHTNESS 255void dis_DrawCurve(uint32_t* data,uint8_t x);void MAX30102Init(void){bsp_InitI2C();maxim_max30102_reset(); //resets the MAX30102maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_dummy); //Reads/clears the interrupt status registermaxim_max30102_init(); //initialize the MAX30102}void HRSPO1(void){for(max_i=0;max_i<n_ir_buffer_length;max_i++){while(MAX30102_INT==1); //µÈ´ý£¬Ö±µ½ÖжϹܽÅÖжÏmaxim_max30102_read_fifo((aun_red_buffer + max_i), (aun_ir_buffer + max_i));if(un_min>aun_red_buffer[max_i])un_min=aun_red_buffer[max_i]; //¸üÐÂÐźÅ×îСֵif(un_max<aun_red_buffer[max_i])un_max=aun_red_buffer[max_i]; //¸üÐÂÐźÅ×î´óÖµ}un_prev_data=aun_red_buffer[max_i];maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_spo2, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);}void HRSPO2(void){max_i=0;un_min=0x3FFFF;un_max=0;for(max_i=100;max_i<150;max_i++){aun_red_buffer[max_i-100]=aun_red_buffer[max_i];aun_ir_buffer[max_i-100]=aun_ir_buffer[max_i];if(un_min>aun_red_buffer[max_i])un_min=aun_red_buffer[max_i];if(un_max<aun_red_buffer[max_i])un_max=aun_red_buffer[max_i];}for(max_i=100;max_i<150;max_i++){un_prev_data=aun_red_buffer[max_i-1];if(MAX30102_INT==1)return;maxim_max30102_read_fifo((aun_red_buffer + max_i), (aun_ir_buffer + max_i));if(aun_red_buffer[max_i]>un_prev_data){f_temp=aun_red_buffer[max_i]-un_prev_data;f_temp/=(un_max-un_min);f_temp*=MAX_BRIGHTNESS;f_temp = un_brightness - f_temp;if(f_temp < 0)un_brightness = 0;elseun_brightness = (int)f_temp;}else{f_temp=un_prev_data-aun_red_buffer[max_i];f_temp/=(un_max-un_min);f_temp*=MAX_BRIGHTNESS;un_brightness+=(int)f_temp;if(un_brightness>MAX_BRIGHTNESS)un_brightness=MAX_BRIGHTNESS;}}maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_spo2, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid)//这里输出n_spo2和n_heart_rate即得到的血氧和心率}
该血氧心率仪还有很多的融合文件,大鑫整理了简易版的心率血氧仪的电路和源码,需要的同学也可以留言联系大鑫。
最近大鑫工作比较忙,有时候更新不及时,也会出现文章疏漏,请读者就原谅哦。
喜欢更多好玩的项目的你
喜欢更多有趣的故事的你
分享,点赞,在看
三连哦!!!
-----------------END-----------------
关注公众号,即可获得更多精彩内容




