- MAX30102_STATUS Max30102_WriteReg(uint8_t uch_addr, uint8_t uch_data)
- {
- if (DAL_I2C_Mem_Write(&MAX30102_I2C_PORT, MAX30102_ADDRESS, uch_addr, 1, &uch_data, 1, I2C_TIMEOUT) == DAL_OK)
- return MAX30102_OK;
- return MAX30102_ERROR;
- }
- MAX30102_STATUS Max30102_ReadReg(uint8_t uch_addr, uint8_t* puch_data)
- {
- if (DAL_I2C_Mem_Read(&MAX30102_I2C_PORT, MAX30102_ADDRESS, uch_addr, 1, puch_data, 1, I2C_TIMEOUT) == DAL_OK)
- return MAX30102_OK;
- return MAX30102_ERROR;
- }
我这里直接使用了APM32F407的DAL库进行封装。
2. 读取其测量到的数据内容。
- MAX30102_STATUS Max30102_ReadFifo(volatile uint32_t* pun_red_led, volatile uint32_t* pun_ir_led)
- {
- uint32_t un_temp;
- *pun_red_led = 0;
- *pun_ir_led = 0;
- uint8_t ach_i2c_data[6];
- if (DAL_I2C_Mem_Read(&MAX30102_I2C_PORT, MAX30102_ADDRESS, REG_FIFO_DATA, 1, ach_i2c_data, 6, I2C_TIMEOUT) != DAL_OK)
- {
- return MAX30102_ERROR;
- }
- 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]
- return MAX30102_OK;
- }
1. 数据读取:
函数首先通过 I2C 接口从 MAX30102 的 FIFO 数据寄存器 `REG_FIFO_DATA` 中读取 6 字节的数据。
`ach_i2c_data[0]` 到 `ach_i2c_data[2]` 存储红光 LED 数据。
`ach_i2c_data[3]` 到 `ach_i2c_data[5]` 存储红外光 LED 数据。
2. 数据拼接:
1. 红光 LED 数据的拼接:
`ach_i2c_data[0]` 左移 16 位,加到 `*pun_red_led`。
`ach_i2c_data[1]` 左移 8 位,加到 `*pun_red_led`。
`ach_i2c_data[2]` 加到 `*pun_red_led`。
2. 红外光 LED 数据的拼接:
`ach_i2c_data[3]` 左移 16 位,加到 `*pun_ir_led`。
`ach_i2c_data[4]` 左移 8 位,加到 `*pun_ir_led`。
`ach_i2c_data[5]` 加到 `*pun_ir_led`。
2.2 MAX30102初始化与配置
在初始化过程中,我们需要重置传感器并配置寄存器以设置测量模式和参数。以下是初始化过程中的关键代码:
初始化代码解析
- MAX30102_STATUS Max30102_Init(I2C_HandleTypeDef* i2c)
- {
- uint8_t uch_dummy;
- // 重置传感器
- if (MAX30102_OK != Max30102_Reset())
- return MAX30102_ERROR;
- // 验证I2C连接是否正常
- if (MAX30102_OK != Max30102_ReadReg(0, &uch_dummy))
- return MAX30102_ERROR;
- // 配置FIFO指针和溢出计数器
- if (MAX30102_OK != Max30102_FifoWritePointer(0x00))
- return MAX30102_ERROR;
- if (MAX30102_OK != Max30102_FifoOverflowCounter(0x00))
- return MAX30102_ERROR;
- if (MAX30102_OK != Max30102_FifoReadPointer(0x00))
- return MAX30102_ERROR;
- // 设置FIFO采样平均
- if (MAX30102_OK != Max30102_FifoSampleAveraging(FIFO_SMP_AVE_1))
- return MAX30102_ERROR;
- // 禁用FIFO回绕
- if (MAX30102_OK != Max30102_FifoRolloverEnable(0))
- return MAX30102_ERROR;
- // 设置FIFO几乎满阈值
- if (MAX30102_OK != Max30102_FifoAlmostFullValue(MAX30102_FIFO_ALMOST_FULL_SAMPLES))
- return MAX30102_ERROR;
- // 配置SpO2模式
- if (MAX30102_OK != Max30102_SetMode(MODE_SPO2_MODE))
- return MAX30102_ERROR;
- // 设置SpO2 ADC范围和采样率
- if (MAX30102_OK != Max30102_SpO2AdcRange(SPO2_ADC_RGE_4096))
- return MAX30102_ERROR;
- if (MAX30102_OK != Max30102_SpO2SampleRate(SPO2_SAMPLE_RATE))
- return MAX30102_ERROR;
- // 配置LED脉冲宽度
- if (MAX30102_OK != Max30102_SpO2LedPulseWidth(SPO2_PULSE_WIDTH_411))
- return MAX30102_ERROR;
- // 配置LED电流
- if (MAX30102_OK != Max30102_Led1PulseAmplitude(MAX30102_RED_LED_CURRENT_LOW))
- return MAX30102_ERROR;
- if (MAX30102_OK != Max30102_Led2PulseAmplitude(MAX30102_IR_LED_CURRENT_LOW))
- return MAX30102_ERROR;
- // 启用中断
- if (MAX30102_OK != Max30102_SetIntAlmostFullEnabled(1))
- return MAX30102_ERROR;
- if (MAX30102_OK != Max30102_SetIntFifoDataReadyEnabled(1))
- return MAX30102_ERROR;
- StateMachine = MAX30102_STATE_BEGIN;
- return MAX30102_OK;
- }
* Max30102\_Reset:重置传感器,确保所有寄存器处于初始状态。
* Max30102\_SetMode: 设置传感器为SpO2测量模式。
* Max30102\_Led1PulseAmplitude:配置红光LED的电流强度。
程序调用关系图:
3 数据读取与处理
在主循环中,我们定期读取传感器的数据,并计算心率和SpO2值。
3.1 主循环
以下代码段展示了主循环中如何获取心率和SpO2值:
- /* Infinite loop */
- while (1)
- {
- Max30102_Task();
- printf("HeartRate:%d,SpO2:%d\r\n",Max30102_GetHeartRate(), Max30102_GetSpO2Value()/100);
- BOARD_LED_Toggle(LED2);
- DAL_Delay(1000U);
- }
* Max30102\_Task: 处理传感器任务,包括数据采集。
* Max30102\_GetHeartRate 和 Max30102\_GetSpO2Value: 获取当前计算的心率和血氧值。
MAX30102使用状态机管理测量流程,确保在不同状态下执行正确的操作。
- void Max30102_Task(void)
- {
- switch (StateMachine)
- {
- case MAX30102_STATE_BEGIN:
- HeartRate = 0;
- Sp02Value = 0;
- if (IsFingerOnScreen)
- {
- CollectedSamples = 0;
- BufferTail = BufferHead;
- Max30102_Led1PulseAmplitude(MAX30102_RED_LED_CURRENT_HIGH);
- Max30102_Led2PulseAmplitude(MAX30102_IR_LED_CURRENT_HIGH);
- StateMachine = MAX30102_STATE_CALIBRATE;
- }
- break;
- case MAX30102_STATE_CALIBRATE:
- if (IsFingerOnScreen)
- {
- if (CollectedSamples > (MAX30102_BUFFER_LENGTH - MAX30102_SAMPLES_PER_SECOND))
- {
- StateMachine = MAX30102_STATE_CALCULATE_HR;
- }
- }
- else
- {
- // 手指不在传感器上,降低LED电流,返回初始状态
- Max30102_Led1PulseAmplitude(MAX30102_RED_LED_CURRENT_LOW);
- Max30102_Led2PulseAmplitude(MAX30102_IR_LED_CURRENT_LOW);
- StateMachine = MAX30102_STATE_BEGIN;
- }
- break;
- case MAX30102_STATE_CALCULATE_HR:
- if (IsFingerOnScreen)
- {
- // 计算心率和SpO2值
- maxim_heart_rate_and_oxygen_saturation(IrBuffer, RedBuffer, MAX30102_BUFFER_LENGTH - MAX30102_SAMPLES_PER_SECOND, BufferTail, &Sp02Value, &Sp02IsValid, &HeartRate, &IsHrValid);
- BufferTail = (BufferTail + MAX30102_SAMPLES_PER_SECOND) % MAX30102_BUFFER_LENGTH;
- CollectedSamples = 0;
- StateMachine = MAX30102_STATE_COLLECT_NEXT_PORTION;
- }
- else
- {
- // 手指不在传感器上,降低LED电流,返回初始状态
- Max30102_Led1PulseAmplitude(MAX30102_RED_LED_CURRENT_LOW);
- Max30102_Led2PulseAmplitude(MAX30102_IR_LED_CURRENT_LOW);
- StateMachine = MAX30102_STATE_BEGIN;
- }
- break;
- case MAX30102_STATE_COLLECT_NEXT_PORTION:
- if (IsFingerOnScreen)
- {
- if (CollectedSamples > MAX30102_SAMPLES_PER_SECOND)
- {
- StateMachine = MAX30102_STATE_CALCULATE_HR;
- }
- }
- else
- {
- // 手指不在传感器上,降低LED电流,返回初始状态
- Max30102_Led1PulseAmplitude(MAX30102_RED_LED_CURRENT_LOW);
- Max30102_Led2PulseAmplitude(MAX30102_IR_LED_CURRENT_LOW);
- StateMachine = MAX30102_STATE_BEGIN;
- }
- break;
- }
- }
函数调用关系图:
3.2 计算心率和血氧饱和度
`maxim_heart_rate_and_oxygen_saturation`函数是MAX30102传感器用于计算心率和血氧饱和度(SpO2)的核心算法之一。下面是对该函数的详细说明:
函数原型
- void maxim_heart_rate_and_oxygen_saturation(
- volatile uint32_t* pun_ir_buffer,
- volatile uint32_t* pun_red_buffer,
- int32_t n_buffer_length,
- uint16_t un_offset,
- int32_t* pn_spo2,
- int8_t* pch_spo2_valid,
- int32_t* pn_heart_rate,
- int8_t* pch_hr_valid
- )
参数说明
1. pun\_ir\_buffer: 指向红外LED采集的数据缓冲区。
2. pun\_red\_buffer: 指向红光LED采集的数据缓冲区。
3. n\_buffer\_length: 缓冲区长度,即可用的数据点数量。
4. un\_offset: 缓冲区的起始偏移量,通常用于循环缓冲区中。
5. pn\_spo2: 指向存储计算结果的SpO2值的指针。
6. pch\_spo2\_valid: 指向一个标志位,表示计算的SpO2值是否有效。
7. pn\_heart\_rate: 指向存储计算结果的心率值的指针。
8. pch\_hr\_valid: 指向一个标志位,表示计算的心率值是否有效。
功能描述
1. 去除直流成分:
1. 首先从红外信号中移除直流成分,这是通过计算信号均值并从每个样本中减去该均值来实现的。
2. 信号平滑:
1. 使用移动平均滤波器(4点)对信号进行平滑处理,以减少高频噪声的影响。
3. 峰谷检测:
1. 反转波形以便使用峰值检测算法识别原始信号中的谷值。
2. 计算峰值之间的间隔,确定心率。
4. AC/DC成分计算:
1. 计算红光和红外光信号的交流(AC)和直流(DC)成分。
2. 使用AC/DC比率来计算血氧饱和度。
5. SpO2计算:
1. 根据计算得到的比率,使用预定义的查找表或公式来计算SpO2值。
2. 验证计算结果的有效性。
6. 输出结果:
1. 将计算得到的心率和SpO2值存储到相应的输出参数中。
细节说明
1. 峰谷检测算法: 通过检测信号中的最大/最小值来识别心跳周期,以计算心率。
2. AC/DC比率计算: 通过测量红光和红外光的AC和DC成分,利用其比率计算出SpO2值。
3. 查找表使用: 为了提高效率和简化计算,常常使用查找表而不是实时计算复杂的数学公式。
3.3 中断处理
当传感器检测到特定事件(如数据准备就绪)时,会触发中断。以下是中断回调的实现:
- void DAL_GPIO_EINT_Callback(uint16_t GPIO_Pin)
- {
- if (GPIO_Pin == INT_Pin)
- {
- Max30102_InterruptCallback();
- }
- }
* Max30102\_InterruptCallback:在中断触发时调用,处理数据读取和状态更新。
以下是对该函数中不同中断处理流程的详细解释:读取传感器的中断状态寄存器,以确定哪种中断事件已经发生。
- uint8_t Status;
- while (MAX30102_OK != Max30102_ReadInterruptStatus(&Status));
3.3.1 FIFO几乎满中断处理
- // Almost Full FIFO Interrupt handle
- if (Status & (1 << INT_A_FULL_BIT))
- {
- for (uint8_t i = 0; i < MAX30102_FIFO_ALMOST_FULL_SAMPLES; i++)
- {
- while (MAX30102_OK != Max30102_ReadFifo((RedBuffer + BufferHead), (IrBuffer + BufferHead)));
- if (IsFingerOnScreen)
- {
- if (IrBuffer[BufferHead] < MAX30102_IR_VALUE_FINGER_OUT_SENSOR) IsFingerOnScreen = 0;
- }
- else
- {
- if (IrBuffer[BufferHead] > MAX30102_IR_VALUE_FINGER_ON_SENSOR) IsFingerOnScreen = 1;
- }
- BufferHead = (BufferHead + 1) % MAX30102_BUFFER_LENGTH;
- CollectedSamples++;
- }
- }
1. 功能:当FIFO缓冲区接近满时触发此中断,确保及时读取数据以防止溢出。
2. 流程:
1. 读取FIFO中的数据,并存储在\`RedBuffer\`和\`IrBuffer\`中。
2. 根据红外光信号判断手指是否在传感器上,并更新\`IsFingerOnScreen\`标志。
3. 更新缓冲区头指针\`BufferHead\`和采集的样本数\`CollectedSamples\`。
3.3.2 新FIFO数据就绪中断处理
- // New FIFO Data Ready Interrupt handle
- if (Status & (1 << INT_PPG_RDY_BIT))
- {
- while (MAX30102_OK != Max30102_ReadFifo((RedBuffer + BufferHead), (IrBuffer + BufferHead)));
- if (IsFingerOnScreen)
- {
- if (IrBuffer[BufferHead] < MAX30102_IR_VALUE_FINGER_OUT_SENSOR) IsFingerOnScreen = 0;
- }
- else
- {
- if (IrBuffer[BufferHead] > MAX30102_IR_VALUE_FINGER_ON_SENSOR) IsFingerOnScreen = 1;
- }
- BufferHead = (BufferHead + 1) % MAX30102_BUFFER_LENGTH;
- CollectedSamples++;
- }
1. 功能: 当新的测量数据准备好时触发此中断,确保APM32F4能够及时获取最新数据。
2. 流程: 类似于FIFO几乎满的处理中,读取数据并更新状态。
4 总结
通过本文所述的配置和代码实现,你可以成功地使用APM32F4微控制器驱动MAX30102传感器,实现心率和血氧的实时测量。在实际应用中,可以根据需求调整传感器的采样率、LED电流等参数,以优化性能和功耗。
这里是串口打印效果:
这里是代码:
APM32F4xx_DAL_SDK_V1.1.1_MAX30102.zip
(5.77 MB, 下载次数: 9)
希望这篇博客能帮助你更好地理解和实现MAX30102的应用。如果你有任何问题或建议,欢迎留言讨论!