这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 智能手环DIY活动过程帖【MAX30102心率血氧模块使用方法】

共1条 1/1 1 跳转至

智能手环DIY活动过程帖【MAX30102心率血氧模块使用方法】

菜鸟
2025-10-07 17:44:26     打赏

一、硬件介绍

1、产品特点

MAX78000FTHR 开发板集成卷积神经网络加速器,将ARM Cortex-M4处理器与浮点单元 (FPU)、卷积神经网络 (CNN) 加速器和 RISC-V 内核组合在一起,包括MAX20303 PMIC,用于电池和电源管理,兼容Adafruit Feather Wing外设扩展板。评估板包括各种外设,例如CMOS VGA图像传感器、数字麦克风、低功耗立体声音频CODEC、1MB QSPI SRAM、micro SD存储卡连接器、RGB指示LED和按键。

max78000fthr.jpg

板载的MAX32625微控制器已预先编程有 DAPLink 固件,可通过 USB 对 MAX78000 Arm 内核进行调试和编程

标准10pin引脚JTAG接口,可调试和编程MAX78000的RISC-V内核


特性

  • 双核:Arm Cortex-M4 FPU处理器,100MHz;RISC-V协处理器,60MHz

  • 512KB闪存

  • 128KB SRAM

  • 16KB 缓存

  • 卷积神经网络加速器

  • 12位并行摄像头接口

  • MAX20303可穿戴PMIC,带电量计

  • Micro SD卡连接器

  • CMOS VGA图像传感器

  • 低功耗、立体声音频编解码器

  • 数字麦克风


硬件框图

FgnPVfh_d1kmN-nDzmYpSZ04mEE0


系统框图

image-20250924223301534.png

image-20250924223301534

2、功能引脚示意图 / 原理图

原理图

板载按钮

SW1:用户可编程功能按钮;

连接到 P0_2

SW2:用户可编程功能按钮;

连接到 P1_7

SW3:PMIC 电源按钮;

Power_Button:当电路板处于通电状态时,按住此按钮 12 秒将执行硬断电; 当电路板处于断电状态时,按下此按钮可重新打开电路板电源。

该按钮可以被MAX78000读取,当按下按钮时,PMIC_PFN2(P3_0)信号进入逻辑低电平状态。

SW4:连接到 RSTN 的输入;

用于复位 MCU

SW5:DAPLink 固件更新按钮;

用于 DAPLINK 固件的更新; 当按下按钮上电时,将进入固件更新模式;


板载LED

D1: 该LED可由用户控制,连接到对应的GPIO端口;

LED_R:P2_0 LED_G:P2_1 LED_B:P2_2

D2:连接到MAX20303的PMIC_LEDx输出;

这些LED可以通过I2C命令进行控制; 还可以通过 I2C 命令配置为充电状态指示灯;

PMIC_LED1(Red) PMIC_LED2(Green) PMIC_LED0(Blue)

D3:DAPLink(MAX32625)状态指示灯;

由 DAPLink 控制


引脚图

image-20250923220205380

image-20250923220205380.png


image-20250923220052647

image-20250923220052647.png


image-20250923220317481


脚序名称功能
1RST主复位引脚
23V33.3V 输出为外设提供 3.3V 电压
31V81.8V 输出为外设提供 1.8V 电压
4GND
5P2_3GPIO 或 模拟输入(AIN3 通道)
6P2_4GPIO 或 模拟输入(AIN4 通道)
7P1_1GPIO 或 UART2_Tx
8P1_0GPIO 或 UART2_Rx
9MPC1GPIO 由 PMIC 通过 I2C 接口控制开漏 或 推挽输出
10MPC2GPIO 由 PMIC 通过 I2C 接口控制开漏 或 推挽输出
11P0_7GPIO 或 QSPI0 时钟SD卡 和 板载 QSPI SRAM 共享
12P0_5GPIO 或 QSPI0 MOSISD卡 和 板载 QSPI SRAM 共享
13P0_6GPIO 或 QSPI0 MISOSD卡 和 板载 QSPI SRAM 共享
14P2_6GPIO 或 LPUART_Rx
15P2_7GPIO 或 LPUART_Tx
16GND
17SYS这是主系统电源,可在电池电压 和 USB电源 之间自动切换 (5V)
18PWR强制关机按钮对地短路 13 秒,则关闭 PMIC
19VBUSUSB_VBUS当连接到 USB 时,为外设提供 5V 电压在不使用 USB 连接时,也可以用作为电路的供电输入(最好不要,因为没有电路防止电流回流至USB)
20P1_6GPIO
21MPC3GPIO 由 PMIC 通过 I2C 接口控制开漏 或 推挽输出
22P0_9GPIO 或 QSPI0 SDIO3SD 卡和板载 QSPI SRAM 共享
23P0_8GPIO 或 QSPI0 SDIO2SD卡和板载 QSPI SRAM 共享
24P0_11GPIO 或 QSPI0_Slave
25P0_19GPIO
26P3_1GPIO 或 Wake-up该引脚为 3.3V
27P0_16GPIO 或 I2C1_SCL板载电平转换器允许通过 R15 或 R20 电阻器选择 1.8V 或 3.3V (详见原理图)
28P0_17GPIO 或 I2C1_SDA 板载电平转换器允许通过 R15 或 R20 电阻选择 1.8V 或 3.3V (详见原理图)



3、血氧心率模块 (MAX30102 )

MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了一个红光LED和一个红外光LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路,采用I2C协议进行通信;

芯片手册

image-20250928222026005

image-20250928222026005.png


基本原理

当光线照射到皮肤组织(通常是指尖、耳垂或手腕)时,一部分光会被组织、骨骼、静脉血等非脉动成分吸收,这部分吸收是恒定不变的。另一部分光则会穿透组织,被动脉血吸收。而动脉血会因为心脏的泵血活动而发生周期性的脉动(血液容积变化)。


主要参数:

I2C地址:0x57


名称参数
LED峰值波长器660nm / 880nm
LED供电电压3.3 ~ 5V
检测信号类型光反射信号(PPG)
输出信号接口I2C接口
通信接口电压1.8 ~ 3.3V ~ 5V(可选)


引脚说明

MAX30102内置了两个LED光源(红光RD和红外光IRD) 它们是IR和RED的驱动,不需要外部连接,内部已经连接好了


名称功能
VIN电源输入:1.8V - 5V (默认:3V3)
SDAI2C数据线
SCLI2C时钟线
GND
RD红色LED接地端,一般不接
IRD红外光IR_LED接地端,一般不接
INT中断引脚:低电平有效


系统框图

image-20250928222407136.png

image-20250928222407136

原理图

image-20250929095558965.png


二、硬件连接

开发板通过硬件I2C的方式连接MAX30102;

开发板                                模块

3V3                                      VIN    

GND                                    GND

P0_16(SCL)                    SCL

P0_17(SDA)                    SDA

P0_19                                INT


实物效果

image-20251002224412403.png



三、代码编写

MAX78000FTHR手册

MAX78000手册

SDK使用文档

读取心率和血氧饱和度(SPO2)数据

测量算法核心思路:上电后首先进行器件的初始化,然后通过IIC读取500组传感器数据,将其中的最大值和最小值筛选出来,调用函数计算500组数据的心率值有效性、血氧值有效性、心率值和血氧值,起到一个滤除前期杂波数据的作用,最后在while(1)中循环处理以下逻辑:舍弃前100组数据,将后400组数据移植前方,即将100-500缓存数据移位到0-400,并寻找其中的最大值和最小值,通过IIC读取新的100组数据放置在401-500缓存数组中,用新获取的数据与上次数据做比较,两者取绝对值,除以最大值和最小值的差值,再乘255计算出心率曲线,调用函数计算500组数据的心率值有效性、血氧值有效性、心率值和血氧值,如果测量值有效则读出心率值和血氧值,如此反复。

主要相关代码

max30102.h

#ifndef __MAX30102_H
#define __MAX30102_H

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "board.h"
#include "mxc_device.h"
#include "mxc_delay.h"
#include "nvic_table.h"
#include "i2c.h"

#define MAX_BRIGHTNESS 255
#define BUFFER_LENTH 500

/*  算法部分 */
#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))
/* end */

#define I2C_MASTER MXC_I2C1
#define MAX30102_INT_PORT MXC_GPIO0
#define MAX30102_INT_PIN MXC_GPIO_PIN_19
#define MAX30102_INT MXC_GPIO_InGet(MAX30102_INT_PORT, MAX30102_INT_PIN)
#define I2C_FREQ 400000 // 400kHZ

#define MAX30102_I2C_ADDR 0x57

// 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

int max30102_init(void);
void max30102_getFirst(void);
int max30102_get(uint8_t *HeartRate, uint8_t *spo2Data);


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


max30102.c

#include "max30102.h"

//  I2C 写
static int i2c_write_reg(uint8_t reg, uint8_t val)
{
   uint8_t buf[2] = {reg, val};
   mxc_i2c_req_t req = {
       .i2c = I2C_MASTER,
       .addr = MAX30102_I2C_ADDR,
       .tx_buf = buf,
       .tx_len = 2,
       .rx_buf = NULL,
       .rx_len = 0,
       .restart = 0,
       .callback = NULL};
   return MXC_I2C_MasterTransaction(&req);
}

//  I2C 读取
static int i2c_read_reg(uint8_t reg, uint8_t *val)
{
   mxc_i2c_req_t req = {
       .i2c = I2C_MASTER,
       .addr = MAX30102_I2C_ADDR,
       .tx_buf = &reg,
       .tx_len = 1,
       .rx_buf = val,
       .rx_len = 1,
       .restart = 1,
       .callback = NULL};
   return MXC_I2C_MasterTransaction(&req);
}

static int max30102_reset(void)
{
   i2c_write_reg(REG_MODE_CONFIG, 0x40);

   //printf("max30102 reset sucess\n");
   return E_NO_ERROR;
}

//  max30102 初始化
int max30102_init(void)
{
   mxc_gpio_cfg_t gpio_out;

   /* INT中断引脚初始化 上拉 */
   gpio_out.port = MAX30102_INT_PORT;
   gpio_out.mask = MAX30102_INT_PIN;
   gpio_out.pad = MXC_GPIO_PAD_PULL_UP;
   gpio_out.func = MXC_GPIO_FUNC_IN;
   gpio_out.vssel = MXC_GPIO_VSSEL_VDDIO;
   gpio_out.drvstr = MXC_GPIO_DRVSTR_0;
   MXC_GPIO_Config(&gpio_out);

   max30102_reset();

   // 清空 FIFO 指针
   i2c_write_reg(REG_FIFO_WR_PTR, 0x00);
   i2c_write_reg(REG_OVF_COUNTER, 0x00);
   i2c_write_reg(REG_FIFO_RD_PTR, 0x00);

   i2c_write_reg(REG_FIFO_CONFIG, 0x0F);
   i2c_write_reg(REG_SPO2_CONFIG, 0x27); // SPO2_ADC

   i2c_write_reg(REG_LED1_PA, 0x24); // RED
   i2c_write_reg(REG_LED2_PA, 0x24); // IR
   i2c_write_reg(REG_PILOT_PA, 0x7F);
   // 模式: SpO2 模式 = 0x03
   i2c_write_reg(REG_MODE_CONFIG, 0x03);

   i2c_write_reg(REG_INTR_ENABLE_1, 0xc0); // INTR setting
   i2c_write_reg(REG_INTR_ENABLE_2, 0x00);

   return E_NO_ERROR;
}

//  读取6个字节 数据
static int max30102_read_fifo(uint32_t *red, uint32_t *ir)
{
   uint8_t reg = REG_FIFO_DATA;
   uint8_t data[6];
   mxc_i2c_req_t req = {
       .i2c = I2C_MASTER,
       .addr = MAX30102_I2C_ADDR,
       .tx_buf = &reg,
       .tx_len = 1,
       .rx_buf = data,
       .rx_len = 6,
       .restart = 1,
       .callback = NULL};
   int ret = MXC_I2C_MasterTransaction(&req);
   if (ret != E_NO_ERROR)
       return ret;

   *red = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | data[2];
   *red &= 0x3FFFF; // 18 bit

   *ir = ((uint32_t)data[3] << 16) | ((uint32_t)data[4] << 8) | data[5];
   *ir &= 0x3FFFF;

   // printf("red:%d , ir:%d \n", *red, *ir);
   // 过滤异常值(例:无手指 / 未贴合检测时)
   if (*ir <= 50000 || *red <= 50000)
   {
       *ir = 0;
       *red = 0;
   }

   return E_NO_ERROR;
}

uint32_t aun_ir_buffer[BUFFER_LENTH];
// IR LED sensor data   红外数据,用于计算血氧
uint32_t aun_red_buffer[BUFFER_LENTH];
// 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

uint32_t un_min = 0x3FFFF, un_max = 0, un_prev_data;
int32_t n_brightness = 0;

void max30102_getFirst(void)
{
   int i;
   // 读取前500个样本,并确定信号范围
   for (i = 0; i < BUFFER_LENTH; i++)
   {
       while (MAX30102_INT == 1) // 等待中断引脚
           ;
       max30102_read_fifo(&aun_red_buffer[i], &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]; // 更新信号最大值
   }

   // 计算前500个样本后的心率和SpO2(样本的前5秒)
   maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, BUFFER_LENTH, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
}

int max30102_get(uint8_t *HeartRate, uint8_t *spo2Data)
{
   float f_temp;
   uint8_t temp[6];
   // 读取和计算max30102数据,总体用缓存的500组数据分析,实际每读取100组新数据分析一次
   un_min = 0x3FFFF;
   un_max = 0;
   // 将前100组样本转储到内存中(实际没有),并将后400组样本移到顶部,将100-500缓存数据移位到0-400
   for (int i = 100; i < 500; i++)
   {
       aun_red_buffer[i - 100] = aun_red_buffer[i]; // 将100-500缓存数据移位到0-400
       aun_ir_buffer[i - 100] = aun_ir_buffer[i];   // 将100-500缓存数据移位到0-400
       // 更新信号的最小值和最大值
       if (un_min > aun_red_buffer[i]) // 寻找移位后0-400中的最小值
           un_min = aun_red_buffer[i];
       if (un_max < aun_red_buffer[i]) // 寻找移位后0-400中的最大值
           un_max = aun_red_buffer[i];
   }
   // 在计算心率前取100组样本,取的数据放在400-500缓存数组中
   for (int i = 400; i < 500; i++)
   {
       un_prev_data = aun_red_buffer[i - 1]; // 临时记录上一次读取数据
       while (MAX30102_INT == 1)             // 等待中断引脚
           ;

       max30102_read_fifo(&aun_red_buffer[i], &aun_ir_buffer[i]);

       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; // 公式(心率曲线)=(新数值-旧数值)/(最大值-最小值)*255
           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; // 公式(心率曲线)=(旧数值-新数值)/(最大值-最小值)*255
           n_brightness += (int)f_temp;
           if (n_brightness > MAX_BRIGHTNESS)
               n_brightness = MAX_BRIGHTNESS;
       }
   }
   maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, BUFFER_LENTH, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); // 传入500个心率和血氧数据计算传感器检测结论,反馈心率和血氧测试结果

   if ((1 == ch_hr_valid) && (1 == ch_spo2_valid) && (n_heart_rate < 120) && (n_sp02 < 101))
   {
       *HeartRate = n_heart_rate;
       *spo2Data = n_sp02;
       return 1;  //  有效数据
   }
   return 0;
}


main.c


/***** Includes *****/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "board.h"
#include "mxc_device.h"
#include "nvic_table.h"

#include "oled.h"
#include "max30102.h"

#define COUNT 30
uint8_t spo2Data;  // 血氧数据
uint8_t heartData; // 心率数据

int i2c_init(void)
{
   int error = MXC_I2C_Init(I2C_MASTER, 1, 0);
   if (error != E_NO_ERROR)
   {
       printf("I2C init fail:%d\n", error);
   }
   else
   {
       MXC_I2C_SetFrequency(I2C_MASTER, I2C_FREQ);
       printf("I2C init success\n");
   }
}

int main(void)
{
   i2c_init();
   if (max30102_init() != E_NO_ERROR)
   {
       printf("MAX30102 init failed\n");
   }
   else
       printf("MAX30102 init OK\n");

   max30102_getFirst();

   while (1)
   {
       uint32_t hr_sum = 0, spo2_sum = 0;
       int valid_cnt = 0;

       // 有效数值  平均
       while (valid_cnt < COUNT)
       {
           if (max30102_get(&heartData, &spo2Data))
           {
               hr_sum += heartData;
               spo2_sum += spo2Data;
               valid_cnt++;
           }
       }
       printf("Heart Rate:%d bpm, SpO2:%d%% \n", hr_sum / valid_cnt, spo2_sum / valid_cnt);
   }
}


编译代码

使用CTRL + SHIFT + B 选择 Build 编译项目;

或使用终端 make -j16 PROJECT=GPIO,编译成 .elf 程序二进制文件;


四、程序烧录

1、用数据线连接开发板至电脑上;

2、程序烧录

使用CTRL + SHIFT + B 选择 flash & run 烧录并运行程序;



五、演示效果


串口打印 心率 / 血氧的相关信息;

MAX30102模块.gif











关键词: MAX78000FTHR    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]