2.60 MAX30102 心率血氧传感器模块(来自 TaurusHard 的贡献)
2.60.1 模块来源
采购链接: https://item.taobao.com/item.htm?spm=a21n57.1.0.0.6556523cd8G8kv&id=586401173804&ns=1&abbucket=15#detailhttps://item.taobao.com/item.htm?spm=a21n57.1.0.0.1749523crfpfW1&id=607153097452&ns=1&abbucket=3#detail 资料下载: https://pan.baidu.com/s/1RFzZhvUyyASrzFopCKZ14A 提取码: msbg
2.60.2 规格参数
工作电压:3.3V~5V通信方式:I2CLED 峰值波长器:660nm/880nm工作温度:-40℃~85℃引脚数量:8(VIN、GND、SCL、SDA、INT、IRD、RD、GND)模块尺寸:21mm x 16mm
2.60.3 模块原理
2.60.3.1 模块简介
MAX30102 传感器是一种集成了红外光传感器和光电二极管的生物传感器。它基于光吸收和反射的理,用于非侵入式心率监测和血氧饱和度测量。在使用 MAX30102 传感器时,LED 发射器会发出红色和红外光。这些光通过皮肤或组织层,并被血液吸收和反射,接收器上的光电二极管接收经过组织反射的光。根据血液中血红蛋白的吸收特性,红光和红外光的衰减程度与血液中的氧气饱和度相关。MAX30102 传感器利用这种光吸收与血氧饱和度之间的关系来测量血氧饱和度和心率。通过对接收到的红色和红外光的强度进行采样和处理,传感器可以计算出血氧饱和度以及心率的值。总结起来,MAX30102 传感器利用光吸收和反射的原理,通过测量血液中的氧气饱和度来实现非侵入式的心率监测和血氧饱和度测量。 MAX30102 是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块(芯片)。它集成了一个 660nm 红光 LED、880nm 红外光 LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路,其原理框图如下。
从框图看,芯片可分为两部分,一部分为模拟信号采集电路,通过 RED 和 IR 灯发出特定波长的光,采集人体反射回来的光,经过 PD 管将光信号转化为电信号,最终通过 18bit ADC 转换器转化为数字信号;第二部分为数字处理电路,将 ADC 转换出来的原始数据进行滤波处理后放置于缓冲区内;单片机通过 IIC 接口读写芯片内部寄存器,读取出相应的数据。2.60.3.2 代码实现方式
分析官方例程中的main.c的实现方式,其核心思路为上电后首先进行器件的初始化,然后通过IIC读取500组传感器数据,将其中的最大值和最小值筛选出来,调用algorithm.c中的函数计算500组数据的心率值有效性、血氧值有效性、心率值和血氧值,起到一个滤除前期杂波数据的作用,最后在while(1)中循环处理以下逻辑:舍弃前100组数据,将后400组数据移植前方,即将100-500缓存数据移位到0-400,并寻找其中的最大值和最小值,通过IIC读取新的100组数据放置在401-500缓存数组中,用新获取的数据与上次数据做比较,两者取绝对值,除以最大值和最小值的差值,再乘255计算出心率曲线,调用algorithm.c中的函数计算500组数据的心率值有效性、血氧值有效性、心率值和血氧值,如果测量值有效则读出心率值和血氧值,如此反复。
2.60.4 移植工程
2.60.4.1 引脚选择
MAX30102 | 立创·梁山派 | 接线图 |
---|
2.60.4.2 移植步骤
首先查看官方例程实现方式。从百度云链接中下载资料,找到官方程序 MH-ET LIVE MAX30102 STM32,打开后查看其构成,可以发现其主要由以下 x 个文件构成:main.cpp、algorithm.cpp、algorithm.h、MAX30102.cpp 和 MAX30102.h。逐一将 cpp 文件转换为 c 文件且适配梁山派开发板。
在 Hardware 文件夹下新建一个 MAX30102 文件夹,在其中创建 6 个文件,分别为 algorithm.h、algorithm.c、max30102.h、max30102.c、myiic.h 和 myiic.c,下面逐一移植每个文件。
algorithm.h
该文件直接复制官方案例即可,代码如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: TaurusHard
* 修改日期: 2023年09月05日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
/** \file algorithm.h ******************************************************
*
* Project: MAXREFDES117#
* Filename: algorithm.h
* Description: This module is the heart rate/SpO2 calculation algorithm header file
*
* Revision History:
*\n 1-18-2016 Rev 01.00 SK Initial release.
*\n
*
* --------------------------------------------------------------------
*
* This code follows the following naming conventions:
*
*\n char ch_pmod_value
*\n char (array) s_pmod_s_string[16]
*\n float f_pmod_value
*\n int32_t n_pmod_value
*\n int32_t (array) an_pmod_value[16]
*\n int16_t w_pmod_value
*\n int16_t (array) aw_pmod_value[16]
*\n uint16_t uw_pmod_value
*\n uint16_t (array) auw_pmod_value[16]
*\n uint8_t uch_pmod_value
*\n uint8_t (array) auch_pmod_buffer[16]
*\n uint32_t un_pmod_value
*\n int32_t * pn_pmod_value
*
* ------------------------------------------------------------------------- */
/*******************************************************************************
* Copyright (C) 2015 Maxim Integrated Products, Inc., All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************
*/
#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))
// 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 ;
// 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);
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_H_ */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
algorithm.c
该文件直接复制官方案例即可,将.cpp 改为.c,代码如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: TaurusHard
* 修改日期: 2023年09月05日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
/** \file algorithm.c ******************************************************
*
* Project: MAXREFDES117#
* Filename: algorithm.cpp
* Description: This module calculates the heart rate/SpO2 level
*
*
* --------------------------------------------------------------------
*
* This code follows the following naming conventions:
*
* char ch_pmod_value
* char (array) s_pmod_s_string[16]
* float f_pmod_value
* int32_t n_pmod_value
* int32_t (array) an_pmod_value[16]
* int16_t w_pmod_value
* int16_t (array) aw_pmod_value[16]
* uint16_t uw_pmod_value
* uint16_t (array) auw_pmod_value[16]
* uint8_t uch_pmod_value
* uint8_t (array) auch_pmod_buffer[16]
* uint32_t un_pmod_value
* int32_t * pn_pmod_value
*
* ------------------------------------------------------------------------- */
/*******************************************************************************
* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************
*/
#include "algorithm.h"
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 ;
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
* By detecting peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the ratio for the SPO2 is computed.
* Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
* Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each ratio.
*
* \param[in] *pun_ir_buffer - IR sensor data buffer
* \param[in] n_ir_buffer_length - IR sensor data buffer length
* \param[in] *pun_red_buffer - Red sensor data buffer
* \param[out] *pn_spo2 - Calculated SpO2 value
* \param[out] *pch_spo2_valid - 1 if the calculated SpO2 value is valid
* \param[out] *pn_heart_rate - Calculated heart rate value
* \param[out] *pch_hr_valid - 1 if the calculated heart rate value is valid
*
* \retval None
*/
{
uint32_t un_ir_mean, un_only_once;
int32_t k, n_i_ratio_count;
int32_t i, s, m, n_exact_ir_valley_locs_count, n_middle_idx;
int32_t n_th1, n_npks, n_c_min;
int32_t an_ir_valley_locs[15];
int32_t an_exact_ir_valley_locs[15];
int32_t an_dx_peak_locs[15];
int32_t n_peak_interval_sum;
int32_t n_y_ac, n_x_ac;
int32_t n_spo2_calc;
int32_t n_y_dc_max, n_x_dc_max;
int32_t n_y_dc_max_idx, n_x_dc_max_idx;
int32_t an_ratio[5], n_ratio_average;
int32_t n_nume, n_denom;
// remove DC of ir signal
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 pt Moving Average
for(k = 0; k < BUFFER_SIZE - MA4_SIZE; k++)
{
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;
}
// get difference of smoothed IR signal
for(k = 0; k < BUFFER_SIZE - MA4_SIZE - 1; k++)
an_dx[k] = (an_x[k + 1] - an_x[k]);
// 2-pt Moving Average to an_dx
for(k = 0; k < BUFFER_SIZE - MA4_SIZE - 2; k++)
{
an_dx[k] = (an_dx[k] + an_dx[k + 1]) / 2;
}
// hamming window
// flip wave form so that we can detect valley with peak detector
for(i = 0; i < BUFFER_SIZE - HAMMING_SIZE - MA4_SIZE - 2; i++)
{
s = 0;
for(k = i; k < i + HAMMING_SIZE; k++)
{
s -= an_dx[k] * auw_hamm[k - i];
}
an_dx[i] = s / (int32_t)1146; // divide by sum of auw_hamm
}
n_th1 = 0; // threshold calculation
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);
// peak location is acutally index for sharpest location of raw signal since we flipped the signal
maxim_find_peaks(an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE - HAMMING_SIZE, n_th1, 8, 5); // peak_height, peak_distance, max_num_peaks
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); // beats per minutes
*pch_hr_valid = 1;
}
else
{
*pn_heart_rate = -999;
*pch_hr_valid = 0;
}
for(k = 0; k < n_npks; k++)
an_ir_valley_locs[k] = an_dx_peak_locs[k] + HAMMING_SIZE / 2;
// raw value : RED(=y) and IR(=X)
// we need to assess DC and AC value of ir and red PPG.
for(k = 0; k < n_ir_buffer_length; k++)
{
an_x[k] = pun_ir_buffer[k];
an_y[k] = pun_red_buffer[k];
}
// find precise min near an_ir_valley_locs
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)
{
for(i = m - 5; i < m + 5; i++)
if(an_x[i] < n_c_min)
{
if(un_only_once > 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++;
}
}
if(n_exact_ir_valley_locs_count < 2)
{
*pn_spo2 = -999; // do not use SPO2 since signal ratio is out of range
*pch_spo2_valid = 0;
return;
}
// 4 pt MA
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;
}
// using an_exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio
// finding AC/DC maximum of raw ir * red between two valley locations
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; // do not use SPO2 since valley loc is out of range
*pch_spo2_valid = 0;
return;
}
}
// find max between two valley locations
// and use ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2
for(k = 0; k < n_exact_ir_valley_locs_count - 1; k++)
{
n_y_dc_max = -16777216;
n_x_dc_max = -16777216;
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;
}
}
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]); // red
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; // subracting linear DC compoenents from raw
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]); // ir
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[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw
n_nume = (n_y_ac * n_x_dc_max) >> 7; // prepare X100 to preserve floating value
n_denom = (n_x_ac * n_y_dc_max) >> 7;
if(n_denom > 0 && n_i_ratio_count < 5 && n_nume != 0)
{
an_ratio[n_i_ratio_count] = (n_nume * 20) / n_denom; // formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ; ///*************************n_nume鍘熸潵鏄?*100************************//
n_i_ratio_count++;
}
}
}
maxim_sort_ascend(an_ratio, n_i_ratio_count);
n_middle_idx = n_i_ratio_count / 2;
if(n_middle_idx > 1)
n_ratio_average = (an_ratio[n_middle_idx - 1] + an_ratio[n_middle_idx]) / 2; // use median
else
n_ratio_average = an_ratio[n_middle_idx];
if(n_ratio_average > 2 && n_ratio_average < 184)
{
n_spo2_calc = uch_spo2_table[n_ratio_average];
*pn_spo2 = n_spo2_calc;
*pch_spo2_valid = 1; // float_SPO2 = -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ; // for comparison with table
}
else
{
*pn_spo2 = -999; // do not use SPO2 since signal ratio is out of range
*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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
** max30102.h**
该部分代码主要包括引脚 IO 初始化和 MAX30102 的读写函数声明,具体代码如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: TaurusHard
* 修改日期: 2023年09月05日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#ifndef __MAX30102_H
#define __MAX30102_H
#include "sys.h"
#define RCU_MAX30102_INT RCU_GPIOB
#define PORT_MAX30102_INT GPIOB
#define PIN_MAX30102_INT GPIO_PIN_13
#define MAX30102_INT PBin(13)
#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 max30102_init(void);
void max30102_reset(void);
uint8_t max30102_Bus_Write(uint8_t Register_Address, uint8_t Word_Data);
uint8_t max30102_Bus_Read(uint8_t Register_Address);
void max30102_FIFO_ReadWords(uint8_t Register_Address, uint16_t Word_Data[][2], uint8_t count);
void max30102_FIFO_ReadBytes(uint8_t Register_Address, uint8_t *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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
max30102.c
该部分代码主要包括引脚 IO 初始化和 MAX30102 的读写函数具体实现,具体代码如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: TaurusHard
* 修改日期: 2023年09月05日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "max30102.h"
#include "myiic.h"
#include "systick.h"
uint8_t max30102_Bus_Write(uint8_t Register_Address, uint8_t 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;
}
uint8_t max30102_Bus_Read(uint8_t Register_Address)
{
uint8_t 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;
}
void max30102_FIFO_ReadWords(uint8_t Register_Address, uint16_t Word_Data[][2], uint8_t count)
{
uint8_t i = 0;
uint8_t no = count;
uint8_t 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] = (((uint16_t)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] = (((uint16_t)data1 << 8) | data2);
no--;
i++;
}
/* 发送I2C总线停止信号 */
IIC_Stop();
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
IIC_Stop();
}
void max30102_FIFO_ReadBytes(uint8_t Register_Address, uint8_t *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();
// uint8_t i;
// uint8_t fifo_wr_ptr;
// uint8_t firo_rd_ptr;
// uint8_t number_tp_read;
// //Get the FIFO_WR_PTR
// fifo_wr_ptr = max30102_Bus_Read(REG_FIFO_WR_PTR);
// //Get the FIFO_RD_PTR
// firo_rd_ptr = max30102_Bus_Read(REG_FIFO_RD_PTR);
//
// number_tp_read = fifo_wr_ptr - firo_rd_ptr;
//
// //for(i=0;i<number_tp_read;i++){
// if(number_tp_read>0){
// IIC_ReadBytes(max30102_WR_address,REG_FIFO_DATA,Data,6);
// }
// max30102_Bus_Write(REG_FIFO_RD_PTR,fifo_wr_ptr);
}
void max30102_init(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(RCU_MAX30102_INT);
/* 配置为上拉输入模式 */
gpio_mode_set(PORT_MAX30102_INT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, PIN_MAX30102_INT);
IIC_Init();
max30102_reset();
// max30102_Bus_Write(REG_MODE_CONFIG, 0x0b); //mode configuration : temp_en[3] MODE[2:0]=010 HR only enabled 011 SP02 enabled
// max30102_Bus_Write(REG_INTR_STATUS_2, 0xF0); //open all of interrupt
// max30102_Bus_Write(REG_INTR_STATUS_1, 0x00); //all interrupt clear
// max30102_Bus_Write(REG_INTR_ENABLE_2, 0x02); //DIE_TEMP_RDY_EN
// max30102_Bus_Write(REG_TEMP_CONFIG, 0x01); //SET TEMP_EN
// max30102_Bus_Write(REG_SPO2_CONFIG, 0x47); //SPO2_SR[4:2]=001 100 per second LED_PW[1:0]=11 16BITS
// max30102_Bus_Write(REG_LED1_PA, 0x47);
// max30102_Bus_Write(REG_LED2_PA, 0x47);
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_WR_PTR[4:0]
max30102_Bus_Write(REG_OVF_COUNTER, 0x00); // OVF_COUNTER[4:0]
max30102_Bus_Write(REG_FIFO_RD_PTR, 0x00); // FIFO_RD_PTR[4:0]
max30102_Bus_Write(REG_FIFO_CONFIG, 0x0f); // sample avg = 1, fifo rollover=false, fifo almost full = 17
max30102_Bus_Write(REG_MODE_CONFIG, 0x03); // 0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
max30102_Bus_Write(REG_SPO2_CONFIG, 0x27); // SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS)
max30102_Bus_Write(REG_LED1_PA, 0x24); // Choose value for ~ 7mA for LED1
max30102_Bus_Write(REG_LED2_PA, 0x24); // Choose value for ~ 7mA for LED2
max30102_Bus_Write(REG_PILOT_PA, 0x7f); // Choose value for ~ 25mA for Pilot LED
// // Interrupt Enable 1 Register. Set PPG_RDY_EN (data available in FIFO)
// max30102_Bus_Write(0x2, 1<<6);
// // FIFO configuration register
// // SMP_AVE: 16 samples averaged per FIFO sample
// // FIFO_ROLLOVER_EN=1
// //max30102_Bus_Write(0x8, 1<<4);
// max30102_Bus_Write(0x8, (0<<5) | 1<<4);
// // Mode Configuration Register
// // SPO2 mode
// max30102_Bus_Write(0x9, 3);
// // SPO2 Configuration Register
// max30102_Bus_Write(0xa,
// (3<<5) // SPO2_ADC_RGE 2 = full scale 8192 nA (LSB size 31.25pA); 3 = 16384nA
// | (1<<2) // sample rate: 0 = 50sps; 1 = 100sps; 2 = 200sps
// | (3<<0) // LED_PW 3 = 411μs, ADC resolution 18 bits
// );
// // LED1 (red) power (0 = 0mA; 255 = 50mA)
// max30102_Bus_Write(0xc, 0xb0);
// // LED (IR) power
// max30102_Bus_Write(0xd, 0xa0);
}
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)
{
// char ach_i2c_data[2];
// ach_i2c_data[0]=uch_addr;
// ach_i2c_data[1]=uch_data;
//
// IIC_WriteBytes(I2C_WRITE_ADDR, ach_i2c_data, 2);
IIC_Write_One_Byte(I2C_WRITE_ADDR, uch_addr, uch_data);
}
void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data)
{
// char ch_i2c_data;
// ch_i2c_data=uch_addr;
// IIC_WriteBytes(I2C_WRITE_ADDR, &ch_i2c_data, 1);
//
// i2c.read(I2C_READ_ADDR, &ch_i2c_data, 1);
//
// *puch_data=(uint8_t) ch_i2c_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;
// read and clear status register
maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);
maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);
IIC_ReadBytes(I2C_WRITE_ADDR, REG_FIFO_DATA, (uint8_t *)ach_i2c_data, 6);
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]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
** myiic.h**
该部分代码主要包含 IIC 底层驱动读写函数声明,具体代码如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: TaurusHard
* 修改日期: 2023年09月01日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#ifndef __MYIIC_H
#define __MYIIC_H
#include "gd32f4xx.h"
#include "sys.h"
#define RCU_SDA RCU_GPIOB
#define PORT_SDA GPIOB
#define PIN_SDA GPIO_PIN_14
#define RCU_SCL RCU_GPIOB
#define PORT_SCL GPIOB
#define PIN_SCL GPIO_PIN_15
// IO方向设置
#define SDA_IN() \
{ \
GPIO_CTL(GPIOB) &= ~GPIO_MODE_MASK(14); \
GPIO_CTL(GPIOB) |= GPIO_MODE_SET(14, GPIO_MODE_INPUT); \
}
#define SDA_OUT() \
{ \
GPIO_CTL(GPIOB) &= ~GPIO_MODE_MASK(14); \
GPIO_CTL(GPIOB) |= GPIO_MODE_SET(14, GPIO_MODE_OUTPUT); \
}
// IO操作函数
#define IIC_SCL PBout(15) // SCL
#define IIC_SDA PBout(14) // SDA
#define READ_SDA PBin(14) // 输入SDA
// IIC操作函数
void IIC_Init(void);
void IIC_Start(void);
void IIC_Stop(void);
void IIC_Send_Byte(uint8_t txd);
uint8_t IIC_Read_Byte(unsigned char ack);
uint8_t IIC_Wait_Ack(void);
void IIC_Ack(void);
void IIC_NAck(void);
void IIC_Write_One_Byte(uint8_t daddr, uint8_t addr, uint8_t data);
void IIC_Read_One_Byte(uint8_t daddr, uint8_t addr, uint8_t *data);
void IIC_WriteBytes(uint8_t WriteAddr, uint8_t *data, uint8_t dataLength);
void IIC_ReadBytes(uint8_t deviceAddr, uint8_t writeAddr, uint8_t *data, uint8_t dataLength);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
myiic.c
该部分代码主要包含 IIC 底层驱动读写函数具体实现,具体代码如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: TaurusHard
* 修改日期: 2023年09月05日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "myiic.h"
#include "systick.h"
// 初始化IIC
void IIC_Init(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(RCU_SDA);
rcu_periph_clock_enable(RCU_SCL);
/* 配置为输出模式 浮空模式 */
gpio_mode_set(PORT_SDA, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, PIN_SDA);
gpio_mode_set(PORT_SCL, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, PIN_SCL);
/* 配置为推挽输出 50MHZ */
gpio_output_options_set(PORT_SDA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, PIN_SDA);
gpio_output_options_set(PORT_SCL, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, PIN_SCL);
/* SDA SCL输出高 */
IIC_SDA = 1;
IIC_SCL = 1;
}
// 产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); // sda线输出
IIC_SDA = 1;
IIC_SCL = 1;
delay_us(4);
IIC_SDA = 0; // START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL = 0; // 钳住I2C总线,准备发送或接收数据
}
// 产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT(); // sda线输出
IIC_SCL = 0;
IIC_SDA = 0; // STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL = 1;
IIC_SDA = 1; // 发送I2C总线结束信号
delay_us(4);
}
// 等待应答信号到来
// 返回值:1,接收应答失败
// 0,接收应答成功
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime = 0;
SDA_IN(); // SDA设置为输入
IIC_SDA = 1;
delay_us(1);
IIC_SCL = 1;
delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime > 250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL = 0; // 时钟输出0
return 0;
}
// 产生ACK应答
void IIC_Ack(void)
{
IIC_SCL = 0;
SDA_OUT();
IIC_SDA = 0;
delay_us(2);
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
}
// 不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL = 0;
SDA_OUT();
IIC_SDA = 1;
delay_us(2);
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
}
// IIC发送一个字节
// 返回从机有无应答
// 1,有应答
// 0,无应答
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_SCL = 0; // 拉低时钟开始数据传输
for(t = 0; t < 8; t++)
{
IIC_SDA = (txd & 0x80) >> 7;
txd <<= 1;
delay_us(2);
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
delay_us(2);
}
}
// 诿1个字节,ack=1时,发送ACK,ack=0,发送nACK
uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i, receive = 0;
SDA_IN(); // SDA设置为输入
for(i = 0; i < 8; i++)
{
IIC_SCL = 0;
delay_us(2);
IIC_SCL = 1;
receive <<= 1;
if(READ_SDA)
receive++;
delay_us(1);
}
if(!ack)
IIC_NAck(); // 发送nACK
else
IIC_Ack(); // 发送ACK
return receive;
}
// 写多个字节数据
void IIC_WriteBytes(uint8_t WriteAddr, uint8_t *data, uint8_t dataLength)
{
uint8_t i;
IIC_Start();
IIC_Send_Byte(WriteAddr); // 发送写命令
IIC_Wait_Ack();
for(i = 0; i < dataLength; i++)
{
IIC_Send_Byte(data[i]);
IIC_Wait_Ack();
}
IIC_Stop(); // 产生一个停止信号
delay_ms(10);
}
// 读多个字节数据
void IIC_ReadBytes(uint8_t deviceAddr, uint8_t writeAddr, uint8_t *data, uint8_t dataLength)
{
uint8_t i;
IIC_Start();
IIC_Send_Byte(deviceAddr); // 发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(writeAddr);
IIC_Wait_Ack();
IIC_Send_Byte(deviceAddr | 0X01); // 进入接收模式
IIC_Wait_Ack();
for(i = 0; i < dataLength - 1; i++)
{
data[i] = IIC_Read_Byte(1);
}
data[dataLength - 1] = IIC_Read_Byte(0);
IIC_Stop(); // 产生一个停止信号
delay_ms(10);
}
// 读一个字节数据
void IIC_Read_One_Byte(uint8_t daddr, uint8_t addr, uint8_t *data)
{
IIC_Start();
IIC_Send_Byte(daddr); // 发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(addr); // 发送地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(daddr | 0X01); // 进入接收模式
IIC_Wait_Ack();
*data = IIC_Read_Byte(0);
IIC_Stop(); // 产生一个停止信号
}
// 写一个字节数据
void IIC_Write_One_Byte(uint8_t daddr, uint8_t addr, uint8_t data)
{
IIC_Start();
IIC_Send_Byte(daddr); // 发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(addr); // 发送地址
IIC_Wait_Ack();
IIC_Send_Byte(data); // 发送字节
IIC_Wait_Ack();
IIC_Stop(); // 产生一个停止信号
delay_ms(10);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
2.60.5 移植验证
按照官方例程中的 main.c 的实现方式,在 main.c 中增加以下代码,测试心率值和血氧值测量结果。
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: TaurusHard
* 修改日期: 2023年09月05日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "stdio.h"
#include "systick.h"
#include "uart.h"
#include "algorithm.h"
#include "max30102.h"
#define MAX_BRIGHTNESS 255
#define BUFFER_LENTH 500
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
int main(void)
{
uint32_t un_min, un_max, un_prev_data;
int i;
int32_t n_brightness;
float f_temp;
uint8_t temp[6];
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化 1us
usart_gpio_config(115200U); // 串口初始化
max30102_init(); // max30102初始化
n_brightness = 0;
un_min = 0x3FFFF;
un_max = 0;
// 读取前500个样本,并确定信号范围
for(i = 0; i < BUFFER_LENTH; i++)
{
while(MAX30102_INT == 1) // 等待中断引脚
;
max30102_FIFO_ReadBytes(REG_FIFO_DATA, temp); // 读取传感器数据,赋值到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(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个样本后的心率和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);
while(1)
{
// 读取和计算max30102数据,总体用缓存的500组数据分析,实际每读取100组新数据分析一次
un_min = 0x3FFFF;
un_max = 0;
// 将前100组样本转储到内存中(实际没有),并将后400组样本移到顶部,将100-500缓存数据移位到0-400
for(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(i = 400; i < 500; i++)
{
un_prev_data = aun_red_buffer[i - 1]; // 临时记录上一次读取数据
while(MAX30102_INT == 1) // 等待中断引脚
;
max30102_FIFO_ReadBytes(REG_FIFO_DATA, temp); // 读取传感器数据,赋值到temp中
aun_red_buffer[i] = (long)((long)((long)temp[0] & 0x03) << 16) | (long)temp[1] << 8 | (long)temp[2]; // 将值合并得到实际数字,数组400-500为新读取数据
aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03) << 16) | (long)temp[4] << 8 | (long)temp[5]; // 将值合并得到实际数字,数组400-500为新读取数据
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))
{
printf("HeartRate=%i, BloodOxyg=%i\r\n", n_heart_rate, n_sp02);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
将 DAP-Link 连接至电脑 USB 接口,打开串口调试助手,编译代码下载到梁山派开发板后,运行代码,将食指贴在传感器的光电二极管处,静置片刻,稍后观察串口是否有打印输出,有下图打印输出则证明移植成功。
移植成功代码见下文件。