开发环境:
Arduino-1.8.5-windows.exe、HXDZ-30102-ACC
硬件设备:
MAX30102
- MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。
- 它集成了一个红光LED和一个红外光LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。
- 采用一个1.8V电源和一个独立的5.0V用于内部LED的电源,应用于可穿戴设备进行心率和血氧采集检测,佩戴于手指、耳垂和手腕等处。
- 标准的I2C兼容的通信接口可以将采集到的数值传输给Arduino、KL25Z等单片机进行心率和血氧计算。
- 该芯片还可通过软件关断模块,待机电流接近为零,实现电源始终维持供电状态。
- 集成了玻璃盖可以有效排除外界和内部光干扰,拥有最优可靠的性能。
心率血氧传感器模块(HXDZ-30102-ACC)
集成了LIS2DH12(ST的三轴加速传感器,用于记录运动数据)和MAX30102(血氧和心率检测记录)。
传感器参数:
电路板尺寸:38*16mm
电路板厚度:2.5mm
LED峰值波长:660nm/880nm
LED供电电压:3.3v~5v
检测信号类型:光反射信号(PPG)
输出信号接口:I2C接口(数字接口)
通信接口电压:3~5v
工作电路:1.5mA(3.3v 输入)
心率精确度:+/- 5bpm,+/- 10bpm(动态)
分辨率: 1bpm
采样率:100Hz(STM32程序)/ 25Hz(arduino程序)
接口说明
VCC:LED电源输入端,也是I2C总线上拉电平,可以接3.3v或者5v
GND:地线
SCL:I2C总线的时钟引脚
SDA:I2C总线的数据引脚
I_L:LIS2DH12芯片的中断引脚
I_M:MAX30102芯片的中断引脚
工作原理
传统的脉搏测量方法主要有三种:
1、从心电信号中提取
2、从测量血压时压力传感器测到的波动来计算脉率
3、光电容积法
前两种会限制病人的活动,长时间使用会加重病患的心理和生理负担,而光电容积法在实际中时普遍使用的一种有效方法,其特点:方法简单、佩戴方便、可靠性高。
光电容积法基本原理:
利用人体组织在血管搏动时造成透光率不同来进行脉搏和血氧饱和度测量的。其使用的传感器由光源和光电变换器两部分组成,通过绑带或夹子固定在病患的手指、手腕或耳垂上。光源一般采用对动脉血中氧合血红蛋白(Hb02)和血红蛋白(Hb)有选择性的特定波长的发光二极管(一般使用660nm附近的红光和900nm附近的红外光)。当光束透过人体外周血管,由于动脉搏动充血容积变化导致这束光的透光率发生改变,此时由光电变换器接收经人体组织反射的光线,转变为电信号并将其放大和输出。
由于脉搏是随心脏的搏动而周期性变化的信号,动脉血管容积也周期性变化,因此光电变换器的电信号变化周期就是脉搏率。同时根据血氧饱和度的定义,其表示为:
注意:
1、SaO2 :广义上的氧饱和度,常指血液样品中的氧含量对该样品血液最大氧含量的百分比(SpO2是经皮血氧饱和度, 而SaO2是动脉血氧饱和度,二者不同,但是相关性好,绝对值十分接近)。
2、HbO2:氧合血红蛋白
3、Hb:还原血红蛋白
MAX30102本身集成了完整的发光LED及其驱动部分,光感应和AD转换部分,环境光干扰消除及数字滤波部分,只将数字接口留给用户,极大地减轻了用户的设计负担。用户只需要使用单片机通过硬件I2C或者模拟I2C接口来读取MAX30102本身的FIFO,就可以得到转换后的光强度数值,通过编写相应的算法就可以得到心率值和血氧饱和度。
实现算法
心率和血氧饱和度算法流程图:
工作流程
首先连接开发板串口,波特率需要进行必要设置,奇偶校验位无,上电后,复位MAX30102,并开始对MAX30102进行功能初始化,此时Red LED和 IR LED交替点亮来检测人体皮肤下血液的搏动和血氧含量(此时可以看到MAX30102有红光亮起,说明初始化成功)。开发板将一段时间内MAX30102采集的LED反射数据存储在内部RAM中,然后分别计算Red LED和 IR LED的直流成分(DC)和交流成分(AC),最后算出数值R并通过预先存储在两波峰之间的时间差T来确定,每分钟心跳数BPM=60/T。(具体算法原理可以参考AN6409芯片手册中29~31页说明)
red和ir是红色LED,红外LED的原始数据,HR表示心率值,HRvalid是心率是否有效标识,SPO2是血氧数值,SPO2valid是血氧首付有效标识
血氧模块与Arduino连接说明:
接口连接说明:
实际连接图:
原理图管脚说明:
实现代码
/*
引脚连接关系
Arduino UNO <---> HXDZ-30102/HXDZ-30102-ACC
SDA <---> SDA
SCL <---> SCL
PIN 10 <---> INT/I_M
5V <---> VCC
GND <---> GND
*/
#include <Arduino.h>
#include "algorithm.h"
#include "max30102.h"
//if Adafruit Flora development board is chosen, include NeoPixel library and define an NeoPixel object
#if defined(ARDUINO_AVR_FLORA8)
#include "adafruit_neopixel.h"
//to lower the max brightness of the neopixel LED
#define BRIGHTNESS_DIVISOR 8
Adafruit_NeoPixel LED = Adafruit_NeoPixel(1, 8, NEO_GRB + NEO_KHZ800);
#endif
#define MAX_BRIGHTNESS 255
#define blinkPin 13
#if defined(ARDUINO_AVR_UNO)
//Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format
//To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data.
uint16_t aun_ir_buffer[100]; //infrared LED sensor data
uint16_t aun_red_buffer[100]; //red LED sensor data
#else
uint32_t aun_ir_buffer[100]; //infrared LED sensor data
uint32_t aun_red_buffer[100]; //red LED sensor data
#endif
int32_t n_ir_buffer_length; //data length
int32_t n_spo2; //SPO2 value
int8_t ch_spo2_valid; //indicator to show if the SPO2 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
uint8_t uch_dummy;
// the setup routine runs once when you press reset:
void setup() {
#if defined(ARDUINO_AVR_LILYPAD_USB)
pinMode(13, OUTPUT); //LED output pin on Lilypad
#endif
#if defined(ARDUINO_AVR_FLORA8)
//Initialize the LED
LED.begin();
LED.show();
#endif
pinMode(blinkPin, OUTPUT); //LED output pin on UNO
digitalWrite(blinkPin,HIGH); // turn on pin 13 LED
delay(1000);
digitalWrite(blinkPin,LOW); // turn on pin 13 LED
delay(1000);
digitalWrite(blinkPin,HIGH); // turn on pin 13 LED
delay(1000);
digitalWrite(blinkPin,LOW); // turn on pin 13 LED
delay(1000);
digitalWrite(blinkPin,HIGH); // turn on pin 13 LED
delay(1000);
digitalWrite(blinkPin,LOW); // turn on pin 13 LED
delay(1000);
maxim_max30102_reset(); //resets the MAX30102
// initialize serial communication at 115200 bits per second:
Serial.begin(115200);
Serial.print(F("MAX30102 begins to start working"));
pinMode(10, INPUT); //pin D10 connects to the interrupt output pin of the MAX30102
delay(1000);
maxim_max30102_read_reg(REG_INTR_STATUS_1,&uch_dummy); //Reads/clears the interrupt status register
maxim_max30102_init(); //initialize the MAX30102
}
// the loop routine runs over and over again forever:
void loop() {
uint32_t un_min, un_max, un_prev_data, un_brightness; //variables to calculate the on-board LED brightness that reflects the heartbeats
int32_t i;
float f_temp;
un_brightness=0;
un_min=0x3FFFF;
un_max=0;
n_ir_buffer_length=100; //buffer length of 100 stores 4 seconds of samples running at 25sps
//read the first 100 samples, and determine the signal range
for(i=0;i<n_ir_buffer_length;i++)
{
while(digitalRead(10)==1); //wait until the interrupt pin asserts
maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i)); //read from MAX30102 FIFO
if(un_min>aun_red_buffer[i])
un_min=aun_red_buffer[i]; //update signal min
if(un_max<aun_red_buffer[i])
un_max=aun_red_buffer[i]; //update signal max
}
un_prev_data=aun_red_buffer[i];
//calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples)
maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_spo2, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
//Continuously taking samples from MAX30102. Heart rate and SpO2 are calculated every 1 second
while(1)
{
i=0;
un_min=0x3FFFF;
un_max=0;
//dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top
for(i=25;i<100;i++)
{
aun_red_buffer[i-25]=aun_red_buffer[i];
aun_ir_buffer[i-25]=aun_ir_buffer[i];
//update the signal min and max
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];
}
//take 25 sets of samples before calculating the heart rate.
for(i=75;i<100;i++)
{
un_prev_data=aun_red_buffer[i-1];
while(digitalRead(10)==1);
digitalWrite(9, !digitalRead(9));
maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i));
//calculate the brightness of the LED
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;
f_temp=un_brightness-f_temp;
if(f_temp<0)
un_brightness=0;
else
un_brightness=(int)f_temp;
}
else
{
f_temp=un_prev_data-aun_red_buffer[i];
f_temp/=(un_max-un_min);
f_temp*=MAX_BRIGHTNESS;
un_brightness+=(int)f_temp;
if(un_brightness>MAX_BRIGHTNESS)
un_brightness=MAX_BRIGHTNESS;
}
#if defined(ARDUINO_AVR_LILYPAD_USB)
analogWrite(13, un_brightness);
#endif
#if defined(ARDUINO_AVR_FLORA8)
LED.setPixelColor(0, un_brightness/BRIGHTNESS_DIVISOR, 0, 0);
LED.show();
#endif
//send samples and calculation result to terminal program through UART
Serial.print(F("red="));
Serial.print(aun_red_buffer[i], DEC);
Serial.print(F(", ir="));
Serial.print(aun_ir_buffer[i], DEC);
Serial.print(F(", HR="));
Serial.print(n_heart_rate, DEC);
Serial.print(F(", HRvalid="));
Serial.print(ch_hr_valid, DEC);
Serial.print(F(", SPO2="));
Serial.print(n_spo2, DEC);
Serial.print(F(", SPO2Valid="));
Serial.println(ch_spo2_valid, DEC);
}
maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_spo2, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
}
}
运行效果
初始化:
无采集状态:
数据采集状态:
ps:这里由于整体软件和硬件的标准没有像医疗设备的标准一样,所以只是验证性测试。