ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《正点原子BEE BLOCK模块资料》ATK-MB020模块使用说明

《正点原子BEE BLOCK模块资料》ATK-MB020模块使用说明

原创 嵌入式/内核开发 作者:正点原子 时间:2025-04-12 09:18:34 0 删除 编辑

ATK-MB020模块使用说明


        1,硬件连接

       这里以正点原子M48Z-M3最小系统板STM32F103版为例,给大家介绍一下模块和板卡的连接方法。其它板卡与模块的硬件连接方法,请大家在“ATK-MB020心率血氧模块\3,程序源码\相应板卡例程文件夹\readme.txt”路径下查看。

       心率血氧模块可通过杜邦线与正点原子M48Z-M3最小系统板STM32F103版进行连接,具体的连接关系,如下表所示:


表1.1.1 心率血氧模块与M48Z-M3最小系统板STM32F103版连接关系


        2,实验功能


       2.1 心率血氧模块测试实验


       2.1.1 功能说明

       在本实验中,将手指或皮肤贴近心率血氧传感器表面,串口会实时打印当前的心率值和血氧值。需要查看这部分实验信息的用户,可用杜邦线将最小系统板STM32F103的PA9引脚和GND连接至外部的USB转串口设备,这样就可以通过XCOM上位机查看串口打印的信息了。

       开发板的LED0闪烁,提示程序运行。


       2.1.2 源码解读

       打开本实验的工程文件夹,能够在./Drivers/BSP目录下看到ATK_MAX30102文件夹,和IIC文件夹,其中ATK_MAX30102文件夹中就包含了心率血氧模块的驱动文件和FIR滤波器驱动代码,IIC文件夹中就包含了软件模拟IIC的驱动文件如下图所示:


图2.1.2.1 心率血氧模块驱动代码


图2.1.2.2 IIC驱动代码


       2.1.2.1 心率血氧模块驱动

       下面将简要介绍atk_max30102.c和atk_max30102_math.c中几个重要的API 函数。


       1. 函数atk_max30102_init()

       该函数用于初始化心率血氧模块,具体的代码,如下所示:

 
/**
 * @brief   初始化max30102
 * @param   无   
 * @retval  无
 */
void atk_max30102_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    
    INT_GPIO_CLK_ENABLE();
    /* INT引脚初始化为外部中断线 */
    gpio_init_struct.Pin = INT_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;   /* 下升沿触发 */
    gpio_init_struct.Pull = GPIO_PULLUP;            /* 上拉 */
    HAL_GPIO_Init(INT_GPIO_PORT, &gpio_init_struct);/* INT配置为下降沿触发中断 */
    
    HAL_NVIC_SetPriority(INT_IRQn, 0, 2);           /* 抢占0,子优先级2 */
    HAL_NVIC_EnableIRQ(INT_IRQn);                    /* 使能中断线5 */
    
    iic_init();                                         /* 初始化I2C接口 */
    
    atk_max30102_reset();                            /* 复位设备 */
    /* 中断使能:FIFO满以及新FIFO数据就绪 */
    atk_max30102_write_byte(MAX30102_INTR_ENABLE_1, 0xC0);  
    atk_max30102_write_byte(MAX30102_INTR_ENABLE_2, 0x00);
    /* 清空指针FIFO_WR_PTR[4:0] */
atk_max30102_write_byte(MAX30102_FIFO_WR_PTR, 0x00);    
/* 清空指针OVF_COUNTER[4:0] */
atk_max30102_write_byte(MAX30102_OVF_COUNTER, 0x00);   
/* 清空指针FIFO_RD_PTR[4:0] */ 
    atk_max30102_write_byte(MAX30102_FIFO_RD_PTR, 0x00);    
    /* 样本平均(4),FIFO满滚(0),FIFO几乎满值(发出中断时为15个空数据样本) */
atk_max30102_write_byte(MAX30102_FIFO_CONFIG, 0x4F);    
/* 配置为SpO2(血氧饱和)模式,会测量RED 和 IR */
atk_max30102_write_byte(MAX30102_MODE_CONFIG, 0x03);    
/* SpO2配置: ADC范围: 4096nA,采样速率控制:200Hz,·LED脉冲宽度:215us· */
    atk_max30102_write_byte(MAX30102_SPO2_CONFIG, 0x2A);    
    /* IR LED 电流选择9.4mA */
atk_max30102_write_byte(MAX30102_LED1_PA, 0x2F); 
/* RED LED 电流选择9.4mA */
    atk_max30102_write_byte(MAX30102_LED2_PA, 0x2F);        
    
    /* 建议初始化时,通过读取将中断状态寄存器,将中断标志位清空 */
    atk_max30102_read_byte(MAX30102_INTR_STATUS_1);
    atk_max30102_read_byte(MAX30102_INTR_STATUS_2);
}

       从上述代码可以看出,这段代码完成了 MAX30102 的初始化和配置工作,包括 GPIO 中断设置、FIFO 指针清空、传感器模式和 LED 参数的配置。它确保了传感器进入SpO2模式,并在新数据就绪或 FIFO快满时触发中断,主控可以通过 I2C 接口读取数据寄存器。


       2. 函数atk_max30102_fifo_read()

       这函数实现了读取MAX30102传感器的FIFO数据,将RED和IR两种光信号的数据解析并存储,具体代码,如下所示:

/**
 * @brief   读取FIFO数据
 * @param   red_data :    存储RED数据   
 * @param   ir_data  :    存储IR数据    
 * @retval  无
 */
void atk_max30102_fifo_read(float *red_data, float *ir_data)
{
    uint8_t receive_data[6];
    /* 通过读取将中断状态寄存器,将中断标志位清空 */
    atk_max30102_read_byte(MAX30102_INTR_STATUS_1);
    atk_max30102_read_byte(MAX30102_INTR_STATUS_2);
    
    atk_max30102_read_nbytes(MAX30102_FIFO_DATA, receive_data, 6);
*red_data = ((receive_data[0] << 16 | receive_data[1] << 8 | receive_data[2]) 
& 0x03ffff);  /* RED数据 */
*ir_data = ((receive_data[3] << 16 | receive_data[4] << 8 | receive_data[5]) 
& 0x03ffff);   /* IR数据 */
}

       从上面的代码可以看出,在读取RED和IR数据之前,需要先通过读取中断状态寄存器来清除中断标志,确保不会发生意外的中断触发。由于模块被配置为SpO2模式,因此每次需要连续读取6个字节的数据(前3个字节为RED通道数据,后3个字节为IR通道数据),才能获取一个完整的数据样本。接着,通过拼接这6个字节的数据,获取RED和IR数据。


       3. 函数atk_max30102_get_heart()

       该函数通过峰值检测算法来计算心率。具体的代码,如下所示:

/**
 * @brief   获取心率值
 * @param   input_data :经过FIR滤波后的红外光数据    
 * @param   cache_nums :采样数量    
 * @note    经过滤波的信号,心跳会呈现周期性的峰值(每个峰值代表一次心跳)
 *          设定一个阈值(一般为采集样本的平均值),当信号超过该阈值时,识别为峰值
 *          计算两个采样点的差值,为了准确性同样多次采集取平均 得到稳定的两次采样点差值(两次心跳的间隔时间 = 两个采样点差值/采样频率)
 *          计算心率公式(bpm 即每分钟跳动次数):Heart_Rate = 60 * 采样频率 / 两个采样点差值
 * @retval  返回心率值
 * @retval  返回0 代表识别失败,皮肤要紧贴并保持不动
 */
uint16_t atk_max30102_get_heart(float *input_data, uint16_t cache_nums)
{
    float input_data_sum_aver = 0;
    uint16_t i, temp;
    int num_peaks = 0;
    int current_peak_time = 0;
    int last_peak_time = 0;
    int total_intervals = 0;
    
    for(i = 0; i < cache_nums; i++)
    {
        input_data_sum_aver += *(input_data + i);
    }
    input_data_sum_aver = input_data_sum_aver / cache_nums; /* 获得阈值 */
    
    for(i = 0; i < cache_nums; i++)
    {
        /* 检测峰值 */
        if((*(input_data + i) > input_data_sum_aver) && (*(input_data + i + 1) < 
input_data_sum_aver))
        {
            current_peak_time = i;                     /* 记录当前采样点 */
            if(last_peak_time != 0)
            {
                temp = current_peak_time - last_peak_time; /* 计算两个采样点间的差 */
                total_intervals += temp;               /* 差累加 */
                num_peaks++;                           /* 次数累加,后续取平均数用 */
            }
/* 记录上次采样点,以便下次计算两个峰值采样点的差 */
            last_peak_time = current_peak_time;             
        }
    }
 
/* 单位:bpm(次/min) 心率 = 60 * sampling_rate / 两个采样点间的差值   其中
sampling_rate = 采样率/样本数: 200/4 = 50HZ */
    if(num_peaks > 0)
{
/* 得到两个峰值间的平均差 */
        int avg_interval = total_intervals / num_peaks;     
        return (3000 / avg_interval);            /* 计算心率 */
    }
    return 0;
}

       从上述代码可以看出,首先通过传入经过FIR滤波器处理后的IR采样数据数组及其样本数量,接着使用峰值检测算法来计算心率。具体方法是计算数据样本的平均值,并将其作为阈值。当数据超过此阈值时,即可判定为峰值。在检测到至少一个峰值对后,代码计算所有峰值之间的平均时间间隔,并利用心率计算公式得出最终的心率值。


       4. 函数atk_max30102_get_spo2()

       该函数主要实现了计算血氧饱和度(SpO2),具体的代码,如下所示:

/**
 * @brief   获取血氧值
 * @param   ir_input_data :经过FIR滤波后的红外光数据    
 * @param   red_input_data :经过FIR滤波后的红光数据    
 * @param   cache_nums :采样数量   
 * @note    首先通过对红光和红外光信号的最大最小值,计算其AC和DC分量
 *          然后通过公式计算出R = (ACred/DCred) / (ACir/DCir) ;
 *          最后根据R值使用公式SpO2 = aR^2 + bR + c  其中a b c是根据大量数据拟合得出
的系数。
 * @retval  血氧值
 */
float atk_max30102_get_spo2(float *ir_input_data, float *red_input_data, uint16_t cache_nums)
{
    float ir_max = *ir_input_data, ir_min = *ir_input_data;
    float red_max = *red_input_data, red_min = *red_input_data;
    float R;        /* 比率R */
    uint16_t i;
    
    /* 寻找红光和红外光的最大值和最小值 */
    for(i = 1; i < cache_nums; i++)
    {
        if(ir_max < *(ir_input_data + i))
        {
            ir_max = *(ir_input_data + i);
        }
        if(ir_min > *(ir_input_data + i))
        {
            ir_min = *(ir_input_data + i);
        }
        if(red_max < *(red_input_data + i))
        {
            red_max = *(red_input_data + i);
        }
        if(red_min > *(red_input_data + i))
        {
            red_min = *(red_input_data + i);
        }
    }
 
/* (ir_max - ir_min) 和 (red_max - red_min) 表示:红外和红光信号的AC分量(脉动成
分)。 */
/* (red_max + red_min) 和 (ir_max + ir_min):表示:红光和红外信号的DC分量(静态
成分) */
R = ((ir_max + ir_min) * (red_max - red_min)) / ((red_max + red_min) * (ir_max 
- ir_min));
return ((-45.060f) * R * R + 30.354f * R + 94.845f);    /* SpO2 = aR^2 + bR 
+ c */
}

       从上述代码可以看出,首先使用一个循环遍历输入的IR和RED数据,更ir_max、ir_min、red_max和red_min。通过比较当前元素与已有最大值和最小值,找出数组中红光和红外光的最大和最小值,然后通过AC分量和DC分量计算比率R,最后代入血氧饱和浓度公式,SpO2=−45.060 * R^2 + 30.354 * R + 94.845,获得血氧饱和度SpO2。


       5. 函数max30102_fir_init/ir_max30102_fir/red_max30102_fir()

/**
 * @brief   初始化FIR滤波器
 * @param   无   
 * @retval  无
 */
void max30102_fir_init(void)
{
    arm_fir_init_f32(&S_ir, NUM_TAPS, (float32_t *)&firCoeffs32LP[0], &firStateF32_ir[0], blockSize);
    arm_fir_init_f32(&S_red, NUM_TAPS, (float32_t *)&firCoeffs32LP[0], &firStateF32_red[0], blockSize);
}
 
/**
 * @brief   红外光FIR滤波
 * @param   input  :指向输入信号的数据数组   
 * @param   output :指向输出滤波结果的数据数组。
 * @retval  无
 */
void ir_max30102_fir(float *input, float *output)
{
    arm_fir_f32(&S_ir, input,output, blockSize);
}
 
/**
 * @brief   红光FIR滤波
 * @param   input  :指向输入信号的数据数组   
 * @param   output :指向输出滤波结果的数据数组。
 * @retval  无
 */
void red_max30102_fir(float *input, float *output)
{
    arm_fir_f32(&S_red,input, output, blockSize);
}

       这段代码利用ARM_MATH库实现了FIR滤波器的结构和基本操作。通过对红外光和红光信号应用低通FIR滤波器,可以有效去除高频噪声,从而提高信号质量。在初始化过程中,配置了滤波器的系数和状态,具体的信号处理则通过ir_max30102_fir和red_max30102_fir函数完成。


       2.1.2.2 IIC驱动

       在图2.1.2.2中,myiic.c 和 myiic.h 是开发板与心率血氧模块通讯而使用的模拟 IIC 驱动文件,关于模拟 IIC 的驱动介绍,请查看正点原子各个开发板对应的开发指南中模拟 IIC 对应的章节。


       2.1.2.3 实验测试代码

       实验的测试代码为文件 demo.c,在工程目录下的 User 子目录中。测试代码的入口函数为 demo_run(),具体的代码,如下所示:

#define CACHE_NUMS           150          /* 缓存数&采集次数 */
#define PPG_DATA_THRESHOLD  100000    /* 检测阈值 */
 
float g_red_data[1];                      /* 红光数据缓冲区,用于计算spo2(血氧含量) */
float g_ir_data[1];                       /* 红外光数据缓冲区,用于计算heart(心率) */
 
float fir_output[2];                     /* 经过FIR滤波后的数据缓冲区 */
float ppg_data_cache_red[CACHE_NUMS] = {0};  /* 缓存区 */
float ppg_data_cache_ir[CACHE_NUMS] = {0};   /* 缓存区 */
 
/**
 * @brief       例程演示入口函数
 * @param       无
 * @retval      无
 */
void demo_run(void)
{      
    uint16_t t = 0;
    uint16_t cnt = 0;                /* 缓存计数器 */
    uint16_t heart = 0;              /* 心率缓存区 */
    float spo2 = 0;                  /* 血氧缓存区 */
    
    atk_max30102_init();             /* 初始化max30102 */
    max30102_fir_init();             /* FIR滤波初始化 */
    printf("温度:%.2f℃\r\n", atk_max30102_get_temp());
    while(1)
    {
        if(g_max30102_int_flag)
        {
            g_max30102_int_flag = 0;
            atk_max30102_fifo_read(&g_red_data[0], &g_ir_data[0]);
            
            /* 使用DSP库实现FIR滤波 */
            red_max30102_fir(&g_red_data[0], &fir_output[0]);  
            ir_max30102_fir(&g_ir_data[0], &fir_output[1]);
            
            /* 检测是否超过阈值,超过代表有皮肤接触传感器 */
            if((g_red_data[0] > PPG_DATA_THRESHOLD) && (g_ir_data[0] > 
PPG_DATA_THRESHOLD))   //大于阈值,说明传感器有接触
            {   
                ppg_data_cache_red[cnt] = fir_output[0];
                ppg_data_cache_ir[cnt] = fir_output[1];
                cnt++;
            }
            else                         /* 小于阈值 */
            {
                cnt = 0;
            }
            if(cnt >= CACHE_NUMS)   /* 收集满了数据 */
            {
/* 获取心率值 */
                heart = atk_max30102_get_heart(ppg_data_cache_ir, CACHE_NUMS);                  
                spo2 = atk_max30102_get_spo2(ppg_data_cache_ir, 
ppg_data_cache_red, CACHE_NUMS);/* 获取血氧值 */
                if(heart == 0)
                {
                    printf("心率测量失败,请将皮肤紧贴传感器,并保持静止\r\n");
                }
                else
                {
                    printf("心率:%d 次/min\r\n", heart);
                }
                printf("血氧:%.2f%%\r\n", spo2);                
                printf("\r\n");
                cnt = 0;
            }
            
            t++;
            if(t == 20)
            {
                t = 0;
                LED0_TOGGLE();
            }
        }
    }
}

       从上面的代码中可以看出,整个测试代码的逻辑还是比较简单的,首先通过MAX30102传感器采集红光和红外光数据,并通过FIR滤波进行处理,最终计算出心率和血氧值。程序在while循环中持续运行,监测传感器数据并实时输出结果。需注意的是手指或皮肤需紧贴MAX30102传感器保持静止,否则可能导致数据读取有误。


       2.1.3 实验现象

       将心率血氧模块按照第一节“硬件连接”中介绍的连接方式与开发板连接,并将实验代码编译烧录至开发板中,本实验使用串口输出心率以及血氧浓度信息,因此需将开发板的 PA9连接至 DAP 虚拟串口(或 USB 转 TTL 模块)的 RX引脚。并通过串口调试助手查看实验信息输出,如下图所示:


图2.1.3.1 串口调试助手显示内容


       首先一开始我们需要根据提示信息,将手指头或者皮肤紧贴心率血氧模块表面,并保持静止,之后串口就会打印相应的心率和血氧浓度值,如果未保持静止,则会提示心率测试失败等信息!大家可自行测试看看。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3081088/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    765121