MAX30102心率血氧模块_代码详细注释

MAX30102模块

软件代码部分

main函数

#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include "max30102.h"
#include "myiic.h"
#include "algorithm.h"
#include "oled_iic.h"

#define MAX_BRIGHTNESS 255

u32 aun_ir_buffer[500];//IR LED sensor data
int32_t n_ir_buffer_length; //数据长度
u32 aun_red_buffer[500]; //Red LED sensor data

int32_t n_sp02; //SPO2 value
int8_t ch_spo2_valid;   //indicator to show if the SP02 calculation is valid
int32_t n_heart_rate;   //heart rate value
int8_t  ch_hr_valid;    //indicator to show if the heart rate calculation is valid

int main(void)
{

	u8 t = 0;  // 初始化一个计数变量t,用于计数,初始值为0
	u32 un_min, un_max, un_prev_data;  // 初始化三个32位无符号整数,用于存储信号的最小值、最大值和上一个数据
	int32_t n_brightness;  // 初始化一个32位有符号整数,用于表示亮度调整
	u16 i;  // 初始化一个16位无符号整数i,用于循环计数
	u8 temp[6];  // 初始化一个长度为6的无符号字符数组,用于存储传感器采样的数据
	float f_temp;  // 初始化一个单精度浮点数,用于存储临时的浮点型数据
	u8 dis_hr = 0, dis_spo2 = 0;  // 初始化两个8位无符号整数,用于表示心率和血氧饱和度的显示值

	delay_init();  // 调用延时函数的初始化,初始化延时功能
	HZ = GB16_NUM();  // 获取GB16字体的数量
	delay_ms(50);  // 进行短暂的延时,等待系统初始化
	OLED_Init();  // 初始化OLED显示屏
	delay_ms(50);  // 进行短暂的延时,等待OLED初始化完成
	OLED_Clear();  // 清除OLED屏幕上的内容
	delay_ms(50);  // 进行短暂的延时,等待清屏完成

	
	OLED_ShowCH(16,2,"心率:000");
	OLED_ShowCH(16,4,"血氧:000%");
	
	uart_init(115200);

	MX_GPIO_Init();
	max30102_init();
	
	un_min = 0x3FFFF;  // 初始化信号最小值为一个较大的初始值
	un_max = 0;        // 初始化信号最大值为0

	n_ir_buffer_length = 500;  // 缓冲区长度为500,存储5秒运行时的100sps采样数据

	// 读取前500个样本,确定信号范围
	for(i = 0; i < n_ir_buffer_length; i++)
	{
		while(MAX30102_INT == 1);  // 等待直到中断引脚触发中断

		//读取心率血氧传感器(max30102)的FIFO(First In, First Out)数据,然后解析这些数据并将其存储在相应的缓冲区中。
		
		//调用max30102_FIFO_ReadBytes函数从max30102的FIFO寄存器中读取6个字节的数据,存储在名为temp的数组中。
		max30102_FIFO_ReadBytes(REG_FIFO_DATA, temp);
		//将temp数组的前三个字节组合成一个32位的长整型,用于存储红光传感器的数据。这个数据的组合方式是:
		//将temp[0]的低两位(temp[0] & 0x03)左移16位,然后与temp[1]左移8位的结果以及temp[2]的结果进行按位或运算。
		aun_red_buffer[i] = (long)((long)((long)temp[0] & 0x03) << 16) | (long)temp[1] << 8 | (long)temp[2];
		//将temp数组的后三个字节组合成一个32位的长整型,用于存储红外光传感器的数据。这个数据的组合方式与红光传感器类似。
		aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03) << 16) | (long)temp[4] << 8 | (long)temp[5];


		if(un_min > aun_red_buffer[i])
			un_min = aun_red_buffer[i];  // 更新信号最小值
		if(un_max < aun_red_buffer[i])
			un_max = aun_red_buffer[i];  // 更新信号最大值
	}

	un_prev_data = aun_red_buffer[i];  // 记录最后一个样本值

	// 在前500个样本(5秒的样本)之后,计算心率和血氧饱和度
	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;

		// 将内存中的前100组样本丢弃,将最后400组样本移动到数组的前面
		for(i = 100; i < 500; i++)
		{
			aun_red_buffer[i - 100] = aun_red_buffer[i];
			aun_ir_buffer[i - 100] = aun_ir_buffer[i];

			// 更新信号的最小值和最大值
			if(un_min > aun_red_buffer[i])
				un_min = aun_red_buffer[i];
			if(un_max < aun_red_buffer[i])
				un_max = aun_red_buffer[i];
		}

		// 取得计算心率之前的100组样本
		for(i = 400; i < 500; i++)
		{
			un_prev_data = aun_red_buffer[i - 1];
			while(MAX30102_INT == 1);
			max30102_FIFO_ReadBytes(REG_FIFO_DATA, temp);
			aun_red_buffer[i] = (long)((long)((long)temp[0] & 0x03) << 16) | (long)temp[1] << 8 | (long)temp[2];
			aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03) << 16) | (long)temp[4] << 8 | (long)temp[5];

			if(aun_red_buffer[i] > un_prev_data)
			{
				f_temp = aun_red_buffer[i] - un_prev_data;
				f_temp /= (un_max - un_min);
				f_temp *= MAX_BRIGHTNESS;
				n_brightness -= (int)f_temp;
				if(n_brightness < 0)
					n_brightness = 0;
			}
			else
			{
				f_temp = un_prev_data - aun_red_buffer[i];
				f_temp /= (un_max - un_min);
				f_temp *= MAX_BRIGHTNESS;
				n_brightness += (int)f_temp;
				if(n_brightness > MAX_BRIGHTNESS)
					n_brightness = MAX_BRIGHTNESS;
			}

			// 将样本和计算结果通过UART发送到终端程序
			if((ch_hr_valid == 1))  // ch_hr_valid == 1 && ch_spo2_valid ==1 && n_heart_rate<120 && n_sp02<101
			{
				if(n_heart_rate < 120)
					dis_hr = n_heart_rate;
				else
					dis_hr = 0;
				dis_spo2 = n_sp02;
			}
			else
			{
				dis_hr = 0;
				dis_spo2 = 0;
			}

			t++;
			if(t > 10)
			{
				t = 0;
				printf("HR=%i,SpO2=%i\r\n ", dis_hr, dis_spo2);
			}
			OLED_ShowNum(56, 2, dis_hr, 3, 1);
			OLED_ShowNum(56, 4, dis_spo2, 3, 1);
		}

		// 调用函数计算心率和血氧饱和度,并通过OLED显示
		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);
		delay_ms(200);
	}

}

max30102模块

max30102.h
#ifndef __MAX30102_H
#define __MAX30102_H

#include "sys.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 MX_GPIO_Init(void);
void max30102_init(void);  
void max30102_reset(void);
u8 max30102_Bus_Write(u8 Register_Address, u8 Word_Data);
u8 max30102_Bus_Read(u8 Register_Address);
void max30102_FIFO_ReadWords(u8 Register_Address,u16  Word_Data[][2],u8 count);
void max30102_FIFO_ReadBytes(u8 Register_Address,u8* 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"
#include "myiic.h"
#include "delay.h"

// 初始化GPIO配置
void MX_GPIO_Init()
{
    GPIO_InitTypeDef GPIO_InitStructure;
	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_ResetBits(GPIOB, GPIO_Pin_7|GPIO_Pin_8);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_Init(GPIOB, &GPIO_InitStructure);	
}

// 通过I2C总线写入数据
u8 max30102_Bus_Write(u8 Register_Address, u8 Word_Data)
{
    // 采用串行EEPROM随即读取指令序列,连续读取若干字节

    // 第1步:发起I2C总线启动信号
    iic_Start();

    // 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
    iic_Send_Byte(max30102_WR_address | I2C_WR);	// 此处是写指令

    // 第3步:发送ACK
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第4步:发送字节地址
    iic_Send_Byte(Register_Address);
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }
    
    // 第5步:开始写入数据
    iic_Send_Byte(Word_Data);

    // 第6步:发送ACK
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 发送I2C总线停止信号
    iic_Stop();
    return 1;	// 执行成功

cmd_fail: // 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备
    // 发送I2C总线停止信号
    iic_Stop();
    return 0;
}

// 通过I2C总线读取数据
u8 max30102_Bus_Read(u8 Register_Address)
{
    u8 data;

    // 第1步:发起I2C总线启动信号
    iic_Start();

    // 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
    iic_Send_Byte(max30102_WR_address | I2C_WR);	// 此处是写指令

    // 第3步:发送ACK
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第4步:发送字节地址
    iic_Send_Byte((uint8_t)Register_Address);
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第6步:重新启动I2C总线。下面开始读取数据
    iic_Start();

    // 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
    iic_Send_Byte(max30102_WR_address | I2C_RD);	// 此处是读指令

    // 第8步:发送ACK
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第9步:读取数据
    {
        data = iic_Read_Byte(0);	// 读1个字节
        iic_NAck();	// 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1)
    }
    // 发送I2C总线停止信号
    iic_Stop();
    return data;	// 执行成功 返回data值

cmd_fail: // 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备
    // 发送I2C总线停止信号
    iic_Stop();
    return 0;
}

// 通过I2C总线读取多个字节的数据
void max30102_FIFO_ReadWords(u8 Register_Address, u16 Word_Data[][2], u8 count)
{
    u8 i = 0;
    u8 no = count;
    u8 data1, data2;

    // 第1步:发起I2C总线启动信号
    iic_Start();

    // 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
    iic_Send_Byte(max30102_WR_address | I2C_WR);	// 此处是写指令

    // 第3步:发送ACK
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第4步:发送字节地址
    iic_Send_Byte((uint8_t)Register_Address);
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第6步:重新启动I2C总线。下面开始读取数据
    iic_Start();

    // 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
    iic_Send_Byte(max30102_WR_address | I2C_RD);	// 此处是读指令

    // 第8步:发送ACK
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第9步:读取数据
    while (no)
    {
        data1 = iic_Read_Byte(0);	
        iic_Ack();
        data2 = iic_Read_Byte(0);
        iic_Ack();
        Word_Data[i][0] = (((u16)data1 << 8) | data2);  //

        data1 = iic_Read_Byte(0);	
        iic_Ack();
        data2 = iic_Read_Byte(0);
        if (1 == no)
            iic_NAck();	// 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1)
        else
            iic_Ack();
        Word_Data[i][1] = (((u16)data1 << 8) | data2); 

        no--;	
        i++;
    }
    // 发送I2C总线停止信号
    iic_Stop();

cmd_fail: // 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备
    // 发送I2C总线停止信号
    iic_Stop();
}

// 通过I2C总线读取字节数据
void max30102_FIFO_ReadBytes(u8 Register_Address, u8* Data)
{	
    max30102_Bus_Read(REG_INTR_STATUS_1);
    max30102_Bus_Read(REG_INTR_STATUS_2);
    
    // 第1步:发起I2C总线启动信号
    iic_Start();

    // 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
    iic_Send_Byte(max30102_WR_address | I2C_WR);	// 此处是写指令

    // 第3步:发送ACK
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第4步:发送字节地址
    iic_Send_Byte((uint8_t)Register_Address);
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第6步:重新启动I2C总线。下面开始读取数据
    iic_Start();

    // 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
    iic_Send_Byte(max30102_WR_address | I2C_RD);	// 此处是读指令

    // 第8步:发送ACK
    if (iic_Wait_Ack() != 0)
    {
        goto cmd_fail;	// EEPROM器件无应答
    }

    // 第9步:读取数据
    Data[0] = iic_Read_Byte(1);	
    Data[1] = iic_Read_Byte(1);	
    Data[2] = iic_Read_Byte(1);	
    Data[3] = iic_Read_Byte(1);
    Data[4] = iic_Read_Byte(1);	
    Data[5] = iic_Read_Byte(0);
    // 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1)
    // 发送I2C总线停止信号
    iic_Stop();

cmd_fail: // 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备
    // 发送I2C总线停止信号
    iic_Stop();
}

// MAX30102传感器初始化配置

void max30102_init(void)
{
    iic_Init();  // 初始化I2C总线

    max30102_reset();  // 复位MAX30102传感器

    // 下面是一些配置参数的设置

    // max30102_Bus_Write(REG_INTR_ENABLE_1, 0xc0);	// INTR setting
    max30102_Bus_Write(REG_INTR_ENABLE_1, 0xc0);	// 使能中断,开启PPG_RDY_EN

    max30102_Bus_Write(REG_INTR_ENABLE_2, 0x00);  // 关闭其他中断

    // MAX30102配置参数设置
    max30102_Bus_Write(REG_INTR_ENABLE_1, 0xc0);	// INTR setting
    max30102_Bus_Write(REG_INTR_ENABLE_2, 0x00);
    max30102_Bus_Write(REG_FIFO_WR_PTR, 0x00);  	// 设置FIFO写指针,位[4:0]
    max30102_Bus_Write(REG_OVF_COUNTER, 0x00);  	// 设置溢出计数器,位[4:0]
    max30102_Bus_Write(REG_FIFO_RD_PTR, 0x00);  	// 设置FIFO读指针,位[4:0]
    max30102_Bus_Write(REG_FIFO_CONFIG, 0x0f);  	// 配置FIFO,设置样本平均值为1,禁止FIFO循环,FIFO几乎满时触发中断
    max30102_Bus_Write(REG_MODE_CONFIG, 0x03);  	// 配置工作模式,0x02表示仅红光模式,0x03表示SpO2模式,0x07表示多模式LED
    max30102_Bus_Write(REG_SPO2_CONFIG, 0x27);  	// 配置SpO2,设置ADC范围为4096nA,SpO2采样率为100 Hz,LED脉冲宽度为400uS
    max30102_Bus_Write(REG_LED1_PA, 0x24);   	// 设置LED1功率,选择值约为7mA
    max30102_Bus_Write(REG_LED2_PA, 0x24);   	// 设置LED2功率,选择值约为7mA
    max30102_Bus_Write(REG_PILOT_PA, 0x7f);   	// 设置Pilot LED功率,选择值约为25mA


}

// 下面是对其他函数的注释,与max30102_init函数相似
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;

    // 读取和清除状态寄存器
    maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);
    maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);

    // 读取FIFO数据
    iic_ReadBytes(I2C_WRITE_ADDR, REG_FIFO_DATA, (u8 *)ach_i2c_data, 6);

    // 解析FIFO数据
    un_temp = (unsigned char)ach_i2c_data[0];  // 读取FIFO数据的第一个字节
    un_temp <<= 16;  // 左移16位,将数据放入对应的高位
    *pun_red_led += un_temp;  // 累加到红光LED的数据中

    un_temp = (unsigned char)ach_i2c_data[1];  // 读取FIFO数据的第二个字节
    un_temp <<= 8;   // 左移8位,将数据放入对应的高位
    *pun_red_led += un_temp;  // 累加到红光LED的数据中

    un_temp = (unsigned char)ach_i2c_data[2];  // 读取FIFO数据的第三个字节
    *pun_red_led += un_temp;  // 累加到红光LED的数据中

    un_temp = (unsigned char)ach_i2c_data[3];  // 读取FIFO数据的第四个字节
    un_temp <<= 16;  // 左移16位,将数据放入对应的高位
    *pun_ir_led += un_temp;  // 累加到红外LED的数据中

    un_temp = (unsigned char)ach_i2c_data[4];  // 读取FIFO数据的第五个字节
    un_temp <<= 8;   // 左移8位,将数据放入对应的高位
    *pun_ir_led += un_temp;  // 累加到红外LED的数据中

    un_temp = (unsigned char)ach_i2c_data[5];  // 读取FIFO数据的第六个字节
    *pun_ir_led += un_temp;  // 累加到红外LED的数据中

    *pun_red_led &= 0x03FFFF;  // 对红光LED的数据进行按位与操作,屏蔽掉高位 [23:18]
    *pun_ir_led &= 0x03FFFF;   // 对红外LED的数据进行按位与操作,屏蔽掉高位 [23:18]
}

algorithm函数

algorithm.h
//Copyright (C) 2015 Maxim Integrated Products, Inc., All Rights Reserved.
#ifndef ALGORITHM_H_
#define ALGORITHM_H_

#include "sys.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 ,  int32_t n_ir_buffer_length, uint32_t *pun_red_buffer ,   int32_t *pn_spo2, int8_t *pch_spo2_valid ,  int32_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.c
#include "algorithm.h"

//汉明窗口,函数的目的是在时域内对信号进行平滑处理,以减小信号两端的幅度,防止频谱泄漏。
//计算公式:w(n)=0.54-0.46cos(2 pi n/N-1),其中 N 是窗口的长度
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 ;
//ratioAverage 是一个用于计算血氧饱和度的比值。这个比值与红外光和红光的交流和直流分量的比值有关
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,  int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid, 
                              int32_t *pn_heart_rate, int8_t  *pch_hr_valid)
/**
* \brief        Calculate the heart rate and SpO2 level
* \par          Details
*               通过检测PPG周期的峰值和相应的红/红外信号的AC/DC,计算出SPO2的比值。
*				因为这个算法的目标是Arm M0/M3。由于寄存器溢出,SPO2的公式没有达到精度。
*				因此,准确的SPO2是预先计算出来的,并且每个比率都节省了很长的uch_spo2_table[]。
*
* \param[in]	*pun_ir_buffer 				-红外传感器数据缓冲区
* \param[in]	n_ir_buffer_length 			-红外传感器数据缓冲区长度
* \param[in]	*pun_red_buffer 			-红光传感器数据缓冲区
* \param[out]	*pn_spo2 					-计算的SpO2值
* \param[out]	*pch_spo2_valid 			-如果计算的SpO2值有效,则为1
* \param[out]	*pn_heart_rate 				-计算心率值
* \param[out]	*pch_hr_valid 				- 1如果计算的心率值是有效的

*				存储计算结果的指针pn_spo2、pch_spo2_valid、pn_heart_rate和pch_hr_valid。
* \retval       None



* 实现步骤:
	主要实现过程包括:
	(1)去除IR信号的直流成分:
		 计算IR信号的平均值,然后从原始IR信号中减去平均值,得到去除直流成分的信号。
	
	(2)平滑信号:
		 对去除直流成分的IR信号进行4点移动平均。
	
	(3)计算信号差分:
		 计算平滑后的IR信号的差分。
	
	(4)使用汉明窗口翻转波形:
		 对差分信号使用汉明窗口,以便在后续步骤中检测波谷。
	
	(5)使用峰值检测找到波谷:
		 使用峰值检测算法找到波谷的位置,并保存在an_ir_valley_locs数组中。
		 
	(6)计算心率:
		 根据波谷的位置计算心率,单位为每分钟心跳数(BPM)。
		 
	(7)计算IR和红光信号的AC/DC比值:
		 在两个相邻波谷之间,找到IR和红光信号的AC(交流)和DC(直流)成分,并计算AC/DC比值。
		 在每个心跳周期内,计算红外和红光信号的AC分量和DC分量。
		 AC分量反映了血液的脉动变化,而DC分量反映了总体的血液吸收
		 
	(8)使用比值计算血氧饱和度:
		 使用IR和红光信号的AC/DC比值,查表计算血氧饱和度。结果存储在pn_spo2中。
		 红外和红光的AC/DC比值与血液的含氧量相关,因为含氧血液和脱氧血液对红外和红光的吸收比例不同
		 使用预先计算好的SPO2校准表,将红外和红光的AC/DC比值映射到实际的SPO2值。
*/
{
	uint32_t un_ir_mean, un_only_once;		// IR信号的均值和标志位,用于去除直流(DC)成分
	int32_t k, n_i_ratio_count;				// 循环变量k,AC/DC比值计数器
	int32_t i, s, m, n_exact_ir_valley_locs_count, n_middle_idx;
	// 循环变量i,临时变量s,计数器m,精确IR谷点数目,中间索引
	int32_t n_th1, n_npks, n_c_min;			// 阈值n_th1,峰值计数n_npks,最小值n_c_min
	int32_t an_ir_valley_locs[15];			// IR信号谷点的数组
	int32_t an_exact_ir_valley_locs[15];	// 精确IR信号谷点的数组
	int32_t an_dx_peak_locs[15];			// 微分信号峰点的数组
	int32_t n_peak_interval_sum;			// 峰值间隔总和

	int32_t n_y_ac, n_x_ac;					// 红光和红外光的AC(交流)分量
	int32_t n_spo2_calc;					// 计算得到的SPO2值
	int32_t n_y_dc_max, n_x_dc_max;			// 红光和红外光的DC(直流)最大值
	int32_t n_y_dc_max_idx, n_x_dc_max_idx; // 红光和红外光的DC最大值索引
	int32_t an_ratio[5], n_ratio_average;	// AC/DC比值数组和比值平均值
	int32_t n_nume, n_denom;				// 分子和分母,用于计算AC/DC比值


    // 去除IR信号的直流成分
    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点移动平均
    for (k = 0; k < BUFFER_SIZE - MA4_SIZE; k++)
    {
		// 计算4个点的和
        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;
    }

    // 计算信号差分,计算信号an_x的差分,即相邻元素之间的差值。
	//通过对原始信号进行差分运算,可以获取信号的变化率,有助于后续的信号处理和特征提取。
    for (k = 0; k < BUFFER_SIZE - MA4_SIZE - 1; k++)
        an_dx[k] = (an_x[k + 1] - an_x[k]);

    // 2点移动平均
    for (k = 0; k < BUFFER_SIZE - MA4_SIZE - 2; k++)
    {
        an_dx[k] = (an_dx[k] + an_dx[k + 1]) / 2;
    }

	// 汉明窗口
	//汉明窗口是一种窗口函数,通常用于在信号处理中对信号进行平滑处理,以减少频谱泄漏。
	//在这里,通过对差分信号应用汉明窗口,可以对信号进行加权,突出信号中的特定频率成分。
	//这个加权后的信号被用于后续的信号处理步骤,以提取相关的特征或信息。
	for (i = 0; i < BUFFER_SIZE - HAMMING_SIZE - MA4_SIZE - 2; i++)
	{
		s = 0;
		// 应用汉明窗口对信号进行加权
		for (k = i; k < i + HAMMING_SIZE; k++)
		{
			// 汉明窗口的权重数组为 auw_hamm
			// 将差分信号 an_dx 与汉明窗口加权,并求和
			s -= an_dx[k] * auw_hamm[k - i];
		}
		// 将加权和除以汉明窗口权重之和(1146),归一化
		an_dx[i] = s / (int32_t)1146;
	}
	

	// 阈值计算,计算差分信号数组 an_dx 的平均阈值 n_th1
	n_th1 = 0; // 初始化阈值
	// 遍历差分信号数组 an_dx,计算阈值
	for (k = 0; k < BUFFER_SIZE - HAMMING_SIZE; k++)
	{
		// 阈值为正差分信号之和,如果差分信号为负值,则取绝对值累加
		n_th1 += ((an_dx[k] > 0) ? an_dx[k] : ((int32_t)0 - an_dx[k]));
	}
	// 将阈值除以数组长度,得到平均阈值
	n_th1 = n_th1 / (BUFFER_SIZE - HAMMING_SIZE);

	
	
	// 使用峰值检测找到波谷,计算心率
	/*
	思路:
	maxim_find_peaks 函数找到差分信号数组 an_dx 中的峰值位置,并将这些位置存储在 an_dx_peak_locs 数组中
	然后,通过计算峰值之间的平均间隔,利用心率计算公式计算心率值。
	最后,根据检测到的峰值位置,计算波谷位置,并将波谷位置存储在 an_ir_valley_locs 数组中。
	*/
	
	maxim_find_peaks(an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE - HAMMING_SIZE, n_th1, 8, 5);

	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);
		
		// 设置心率有效标志为1
		*pch_hr_valid = 1;
	}
	else
	{
		// 如果未检测到足够的峰值,设置心率为无效值-999
		*pn_heart_rate = -999;
		
		// 设置心率有效标志为0
		*pch_hr_valid = 0;
	}

	// 将峰值位置加上 HAMMING_SIZE / 2 作为波谷位置,为后面寻找准确的波谷做准备
	for (k = 0; k < n_npks; k++)
		an_ir_valley_locs[k] = an_dx_peak_locs[k] + HAMMING_SIZE / 2;

	

	// 计算AC/DC比值,从原始的红外和红光信号中提取出用于计算的实际波谷位置
	/*
	首先,将原始的红外信号存储在 an_x 数组中,红光信号存储在 an_y 数组中。
	然后,遍历波谷位置数组,寻找每个波谷的实际位置。通过在波谷位置的前后寻找最小值,确定波谷的确切位置,
	并将这些位置存储在 an_exact_ir_valley_locs 数组中。
	*/
	for (k = 0; k < n_ir_buffer_length; k++)
	{
		an_x[k] = pun_ir_buffer[k];  // 将红外信号存储在 an_x 数组中
		an_y[k] = pun_red_buffer[k]; // 将红光信号存储在 an_y 数组中
	}

	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 < BUFFER_SIZE - HAMMING_SIZE && m - 5 > 0)
		//检查实际红外信号谷值位置 m 前后是否有足够的空间,以防数组越界。
		{
			for (i = m - 5; i < m + 5; i++)
			//循环遍历从当前谷值位置向前后各取5个点的范围。
			{
				if (an_x[i] < n_c_min)
				{
					if (un_only_once > 0)
					{
						//第一次找到最小值,将其设为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++;
		}
	}

	// 波谷是信号的极小值,用于确定每个心跳周期的起始点。如果确切波谷位置的数量小于2,说明信号比值超出范围,设置SPO2为无效值-999
	if (n_exact_ir_valley_locs_count < 2)
	{
		*pn_spo2 = -999;
		*pch_spo2_valid = 0;
		return;
	}


    // 4点移动平均,平滑信号并降低高频噪声的影响
    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;
    }

    // 使用an_exact_ir_valley_locs,找到IR-RED DC和IR-RED AC进行SPO2校准比率
	//使用先前找到的红外信号的谷位置(an_exact_ir_valley_locs),来计算IR-RED DC和IR-RED AC的比率。用于SPO2(血氧饱和度)的校准。
    n_ratio_average = 0;
    n_i_ratio_count = 0;

    for (k = 0; k < 5; k++)
        an_ratio[k] = 0;
    for (k = 0; k < n_exact_ir_valley_locs_count; k++)
    {
        if (an_exact_ir_valley_locs[k] > BUFFER_SIZE)
        {
            *pn_spo2 = -999;   // 因为谷位置超出范围,不是正确的SPO2
            *pch_spo2_valid = 0;
            return;
        }
    }

	/*
	计算红光和红外信号的交流分量的乘积,并将计算得到的AC/DC比值保存到数组中。
	其中,通过找到两个谷值之间的最大值来估算直流分量,然后计算交流分量,并最终得到AC/DC比值。
	整个过程旨在对信号进行预处理,为后续的SPO2(血氧饱和度)计算提供必要的信息。
	*/
	
	for (k = 0; k < n_exact_ir_valley_locs_count - 1; k++)
	{
		n_y_dc_max = -16777216;   // 初始化红光信号的最大直流分量为一个较小的负数
		n_x_dc_max = -16777216;   // 初始化红外信号的最大直流分量为一个较小的负数

		// 判断两个谷值之间的距离是否大于10个样本点,确保两谷之间有足够的数据进行计算
		if (an_exact_ir_valley_locs[k + 1] - an_exact_ir_valley_locs[k] > 10)
		{
			// 遍历两个谷值之间的数据
			for (i = an_exact_ir_valley_locs[k]; i < an_exact_ir_valley_locs[k + 1]; i++)
			{
				// 找到红外信号的最大值及对应索引
				if (an_x[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;
				}
			}

			// 计算红光的交流分量
			//计算红光信号在两个相邻谷值之间的交流成分,使用两个谷值之间的光强差值乘以相邻两个峰谷之间的距离。
			
			//an_y: 这是存储红光信号光强数据的数组
			//an_exact_ir_valley_locs: 这是存储实际红外信号波谷位置的数组
			//n_y_dc_max_idx,红光信号的最大峰值位置的索引
			
			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]);
			//将得到的红光的交流分量加到红光信号在前一个谷值位置的基线值上
			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;
			//写成abs(n_y_ac - an_y[n_y_dc_max_idx])是否可以?

			// 计算红外光的交流分量
			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]);
			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[an_exact_ir_valley_locs[k + 1]] - n_x_ac;

			// 将红光信号的AC分量与红外信号最大峰值位置的直流分量相乘,作为计算SPO2的分子
			n_nume = (n_y_ac * n_x_dc_max) >> 7;

			// 将红外信号的AC分量与红光信号最大峰值位置的直流分量相乘,作为计算SPO2的分母
			n_denom = (n_x_ac * n_y_dc_max) >> 7;

			// 判断分母是否大于0,避免除零错误,并判断是否满足条件,满足则进行下一步计算
			if (n_denom > 0 && n_i_ratio_count < 5 && n_nume != 0)
			{
				// 计算AC/DC比值并保存到数组中
				an_ratio[n_i_ratio_count] = (n_nume * 20) / n_denom;
				n_i_ratio_count++;
			}
		}
	}

	/*
	对排序后的AC/DC比值数组进行处理,计算得到中值平均(或直接取中值),并通过判断得到的AC/DC比值
	是否在有效范围内,确定最终的SPO2值。如果AC/DC比值有效,通过查表获取对应的SPO2值,否则将SPO2
	值设为无效。在实际应用中,通过对AC/DC比值的处理,结合预先计算的SPO2表格,可以更准确地估算血氧饱和度。
	*/

	// 将存储AC/DC比值的数组按升序排列
	maxim_sort_ascend(an_ratio, n_i_ratio_count);

	// 计算中间值的索引
	n_middle_idx = n_i_ratio_count / 2;

	// 如果中间索引大于1,使用中值平均计算AC/DC比值
	if (n_middle_idx > 1)
		n_ratio_average = (an_ratio[n_middle_idx - 1] + an_ratio[n_middle_idx]) / 2;
	else
		n_ratio_average = an_ratio[n_middle_idx];

	// 判断计算得到的AC/DC比值是否在有效范围内
	if (n_ratio_average > 2 && n_ratio_average < 184)
	{
		// 通过查表获取对应的SPO2值
		n_spo2_calc = uch_spo2_table[n_ratio_average];
		*pn_spo2 = n_spo2_calc;
		*pch_spo2_valid = 1; // 设置SPO2有效标志

		// 可选:用以下公式计算浮点数SPO2,与查表结果进行比较
		// float_SPO2 = -45.060 * n_ratio_average * n_ratio_average / 10000 + 30.354 * n_ratio_average / 100 + 94.845;
	}
	else
	{
		// 如果计算得到的AC/DC比值不在有效范围内,设置SPO2为无效值
		*pn_spo2 = -999;
		*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_size-1){
        if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i-1]){            // find left edge of potential peaks
            n_width = 1;
            while (i+n_width < n_size && pn_x[i] == pn_x[i+n_width])    // find flat peaks
                n_width++;
            if (pn_x[i] > 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_old_npks; j++ ){
            n_dist =  pn_locs[j] - ( i == -1 ? -1 : pn_locs[i] ); // lag-zero peak of autocorr is at index -1
            if ( n_dist > 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 < n_size; i++) {
        n_temp = pn_x[i];
        for (j = i; j > 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 < n_size; i++) {
        n_temp = pn_indx[i];
        for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j-1]]; j--)
            pn_indx[j] = pn_indx[j-1];
        pn_indx[j] = n_temp;
    }
}

其他部分函数如myiic等使用的是正点原子的官方例程,基于篇幅原因,不再敖述。

接线图与实物连接

实物连接

max30102stm32最小板
INTPB9
VIN3V3
GNDGND
SDAPB8
SCLPB7
OLED显示屏stm32最小板
SCLPB13
SDAPB12
GNDGND
VCCVCC
蓝牙模块stm32最小板
TXDPA10
RXDPA9
GNDGND
VCCVCC

测试效果图

在这里插入图片描述

  • 26
    点赞
  • 186
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
### 回答1: max30102心率血氧传感器是一种集成了红外LED、红光LED、光电探测器和数字信号处理器的传感器,能够测量心率血氧饱和度等生理参数。max30102具有高灵敏度、低功耗的优点,适用于可穿戴设备、健康监测设备、医疗设备等领域。max30102通过红外光、红光和光电探测器对主要脉搏部位进行测量,从而获得心率血氧饱和度等生理参数信息。该传感器可广泛应用于医疗领域,例如心电监测、运动恢复、疾病监测和自我诊断等方面。max30102具有高可扩展性,可以轻松集成到不同的平台上,例如ARM、PIC、AVR等微控制器中,使得开发者可以更加灵活地迅速构建心率血氧监测设备。与传统的心率血氧监测设备相比,max30102不仅体积小、功耗低,同时具有更高的精准度和实时性,为用户提供更便捷、准确的医疗体验。 ### 回答2: MAX30102是一种小型心率血氧传感器,由美国芯片制造商Maxim Integrated生产。它采用红外光和红光发射二极管和接收二极管,通过液对不同波长光的吸收来测量血氧水平和心率MAX30102由两个传感器组成:一个红外线LED和一个红色LED。当光照射到皮肤上时,光线会被皮肤内的液吸收。因此,MAX30102可以通过测量在两个波长下通过皮肤的光的吸收来估计血氧水平和心率MAX30102的还支持四种工作模式(空闲模式、连续测量模式、间隔测量模式和多通道模式),用户可以根据具体需求选择不同的模式。此外,MAX30102还具有内置的ADC、数字信号处理器和电源管理单元,可以直接输出数字信号。 MAX30102在医学、健康监测和运动监测等领域有着广泛的应用。例如,它可以用于佩戴在手腕上的智能手表、健康手环和健康监测仪器中,可以实时监测使用者的心率血氧;在医院中,它可以与医疗设备连接,用于监测病患的心率血氧水平,以及在手术中用于监测麻醉效果。 总之,MAX30102是一种高度精准、小型化和多功能的心率血氧传感器,具有广泛的应用前景和市场前景。 ### 回答3: MAX30102 心率血氧传感器是一款由美国集成电路厂商 Maxim Integrated 生产的传感器。该传感器采用了反射式光电传感技术,可以同时测量心率血氧饱和度数据。它的使用场景非常广泛,如家庭健康监测、儿童测量、体育训练等。相对于传统的心率血氧计或手环式健康监测器,MAX30102 具有更高的准确性和可靠性。 MAX30102 心率血氧传感器采用了先进的可见光和红外线 LED 技术,可以在不同皮肤类型的人群中快速准确地检测出心率血氧饱和度。同时,它还配备了高精度的 ADC 转换器和低功耗模式,以提高电池寿命和节省能源。 MAX30102 心率血氧传感器还拥有相当灵活的配置选项,可以通过 I2C 接口进行可编程。传感器工作时,数据通过传感器内部的 DSP 数字信号处理器进行处理,以提高数据的准确度和可靠性。传感器的测量范围是 30-250 bps 的心率和 0%-100% 的血氧饱和度。 总的来说,MAX30102 心率血氧传感器具有高精度、可靠性高、能耗低、体积小等优点,是健康监测和体育训练领域不可或缺的重要代表。随着智能穿戴设备和可穿戴医疗设备的快速发展,这种传感器将有更广泛的应用前景。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值