Arduino 血氧心率模块传感器数据采集

Arduino 血氧心率模块传感器数据采集,第1张

Arduino-1.8.5-windows.exe、HXDZ-30102-ACC

集成了LIS2DH12(ST的三轴加速传感器,用于记录运动数据)和MAX30102(血氧和心率检测记录)。

电路板尺寸:38*16mm

电路板厚度:2.5mm

LED峰值波长:660nm/880nm

LED供电电压:3.3v~5v

检测信号类型:光反射信号(PPG)

输出信号接口:I 2 C接口(数字接口)

通信接口电压:3~5v

工作电路:1.5mA(3.3v 输入)

心率精确度:+/- 5bpm,+/- 10bpm(动态)

分辨率: 1bpm

采样率:100Hz(STM32程序)/ 25Hz(arduino程序)

VCC:LED电源输入端,也是I 2 C总线上拉电平,可以接3.3v或者5v

GND:地线

SCL:I 2 C总线的时钟引脚

SDA:I 2 C总线的数据引脚

I_L:LIS2DH12芯片的中断引脚

I_M:MAX30102芯片的中断引脚

传统的脉搏测量方法主要有三种:

1、从心电信号中提取

2、从测量血压时压力传感器测到的波动来计算脉率

3、光电容积法

前两种会限制病人的活动,长时间使用会加重病患的心理和生理负担,而光电容积法在实际中时普遍使用的一种有效方法,其特点:方法简单、佩戴方便、可靠性高。

光电容积法基本原理:

利用人体组织在血管搏动时造成透光率不同来进行脉搏和血氧饱和度测量的。其使用的传感器由光源和早旦光电变换器两部分组成,通过绑带或夹子固定在病患的手指、手腕或耳垂上。光源一般采用对动脉血中氧合血红蛋白(Hb0 2 )和血红蛋白(Hb)有选择性的特定波长的发光二极管(一般使用660nm附近的红光和900nm附近的红外光)。当光束透过人体外周血管,由于动脉搏动充血容积变化导致这束光的透光率发生改变,此时由光电变换器接收经人体组织反射的光线,正衡转变为电信号并将其放大和输出。

由于脉搏是随举睁做心脏的搏动而周期性变化的信号,动脉血管容积也周期性变化,因此光电变换器的电信号变化周期就是脉搏率。同时根据血氧饱和度的定义,其表示为:

注意:

1、SaO 2 :广义上的氧饱和度,常指血液样品中的氧含量对该样品血液最大氧含量的百分比(SpO2是经皮血氧饱和度, 而SaO2是动脉血氧饱和度,二者不同,但是相关性好,绝对值十分接近)。

2、HbO 2 :氧合血红蛋白

3、Hb:还原血红蛋白

MAX30102本身集成了完整的发光LED及其驱动部分,光感应和AD转换部分,环境光干扰消除及数字滤波部分,只将数字接口留给用户,极大地减轻了用户的设计负担。用户只需要使用单片机通过硬件I 2 C或者模拟I 2 C接口来读取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连接说明:

接口连接说明:

实际连接图:

原理图管脚说明:

初始化:

无采集状态:

数据采集状态:

ps:这里由于整体软件和硬件的标准没有像医疗设备的标准一样,所以只是验证性测试。

/sendtimebetweenbeatswitha/在LCD12864上显示BPM

}

delay(138)9Set/发送并处理原始脉搏传感器数据

if(QS==true){//fadeRateVariableto255tofadeLEDwithpulse

sendDataToProcessing(BQi++){

delay(1000)}

if(Pressure<100){

beep=0}}

if(BPM<60|BPM>100){

for(i=0i<显示信息4

while(1)

{

sendDataToProcessing(}

}

}

//AD转换结果result

ADC_CONTR&=0xf8/依次执行写入 *** 作

{

putchar(ucStr3[i])

}

for(i=0

_nop_()/延时19.6ms

LCD_disp_list_char(4,DisBuff2)设置ADCCONTR控制寄存器后/S确定发现一个心跳

fadeRate=255,0/voidledFadeToBeat(){

//,BPM)///analogWrite(fadePin,fadeRate)/:GetADCResult

函数功能:获取AD转换结果函数

入口参数,Signal)

sys_init()

beep=1

LCD12864_DisplayOneLine(0x80,ucStr1)//,255)//prefix

QS=false/,IBI),4/resettheQuantifiedSelfflagfornexttime

LCD_disp_list_char(2:BYTEch(通道选择)

返回值:result(枣大A///依次执行写入 *** 作

{

putchar(ucStr2[i])

}

for(i=0、CHS1、CHS0(清除通道选择乎岩搜)

_nop_()//显示信息1

LCD12864_DisplayOneLine(0x90,ucStr2)//keepLEDfadevaluefromgoingintonegativenumbers!

/B}

for(i=0i<3i++)

{

putchar(DisBuff[i])/清除ADC控制寄存器ADCCONTR的CHS2,DisBuff)/16i++)/和心率

sendDataToProcessing(setLEDfadevalue

//,要加4个空 *** 作延时才可以正确读到ADCCONTR寄存器的值

_nop_()4i++)//,4i++){

delay(1000)}

if(BPM<60|BPM>100){

beep=0i<16/i++)//8

}

for(i=0Q/}

/******************************************************************************

函数名称岁历显示信息2

LCD12864_DisplayOneLine(0x88,ucStr3)//ledFadeToBeat()显示信息3

LCD12864_DisplayOneLine(0x98,ucStr4)//i<16}

for(i=0i<

unsignedintGetADCResult(BYTEch)

{unsignedintresult//依次执行写入 *** 作

{

putchar(ucStr1[i])/fadeLED

/i<16fadeRate=constrain(fadeRatei++)///发送一个fadeRate-=15//D转换结果)

备注:无

*******************************************************************************/

_nop_()

ADC_CONTR=ADC_POWER|ADC_SPEEDLL|ch|ADC_START//开ADC电源,选择AD转换速率,并选择AD通道,开始AD转换

_nop_()//设置ADCCONTR控制寄存器后,要加4个空 *** 作延时才可以正确读到ADCCONTR寄存器的值

_nop_()

_nop_()

_nop_()

while(!(ADC_CONTR&ADC_FLAG))//等待AD转换结束

ADC_CONTR&=~ADC_FLAG//关闭ADC

result=ADC_RES//将AD转换结果的高两位赋给result

result=result<<8//将result循环左移8位

result+=ADC_RESL//将AD转换结果的底8位加高两位共10位给result

returnresult//返回10位AD转换结果

}

voidsendDataToProcessing(charsymbol,intdat){

putchar(symbol)//symbolprefixtellsProcessingwhattypeofdataiscoming

printf(\"%drn

这个是主程序和部分代码由于字数限制所以你还是留个邮箱吧

void main(void)

{

unsigned char i

sys_init()

beep = 1

LCD12864_DisplayOneLine(0x80,ucStr1)//显示信息1

LCD12864_DisplayOneLine(0x90,ucStr2)//显示信息2

LCD12864_DisplayOneLine(0x88,ucStr3)//显示信息3

LCD12864_DisplayOneLine(0x98,ucStr4)//显示信息4

while(1)

{

sendDataToProcessing('S', Signal)// 发送并处大判理原始脉搏传感器数据

if (QS == true){ // 确定发现一个心跳

fadeRate = 255 // Set 'fadeRate' Variable to 255 to fade LED with pulse

sendDataToProcessing('B',BPM) // 发送一个'B'和心率

sendDataToProcessing('Q',IBI) // send time between beats with a 'Q' prefix

QS = false // reset the Quantified Self flag for next time

LCD_disp_list_char(2,4,DisBuff)//在LCD12864上显示BPM

}

delay(138)// 延时 19.6ms

LCD_disp_list_char(4,4,DisBuff2)

//ledFadeToBeat()

if(Pressure<100){

for(i=0i<8i++){

delay(1000)}

if (Pressure<100){

beep = 0}}

if(BPM<60|BPM>100){

for(i=0i<9i++){

delay(1000)}

if(BPM<60|BPM>100){

beep = 0}

for(i = 0i<16i++) //依次执行写入 *** 作

{

putchar(ucStr1[i])

}

for(i = 0i<16i++) //依次执行写入 *** 作

{

putchar(ucStr2[i])

}

for(i=0i<3i++)

{

putchar(DisBuff[i])}

for(i = 0i<16i++) //依次执行写入 *** 作

{

putchar(ucStr3[i])

}

for(i = 0i<16i++) //依次执行写入族仿凳 *** 兆旅作

{

putchar(ucStr4[i])

}

for(i=0i<4i++)

{

putchar(DisBuff2[i])}

}

}

//void ledFadeToBeat(){

//fadeRate -= 15// set LED fade value

//fadeRate = constrain(fadeRate,0,255) // keep LED fade value from going into negative numbers!

//analogWrite(fadePin,fadeRate) // fade LED

// }

/******************************************************************************

函数名称:GetADCResult

函数功能:获取AD转换结果函数

入口参数:BYTE ch(通道选择)

返回值:result(A/D转换结果)

备注:无

*******************************************************************************/

unsigned int GetADCResult(BYTE ch)

{ unsigned int result //AD转换结果result

ADC_CONTR&=0xf8 //清除ADC控制寄存器ADC CONTR的CHS2、CHS1、CHS0(清除通道选择)

_nop_() //设置ADC CONTR控制寄存器后,要加4个空 *** 作延时才可以正确读到ADC CONTR寄存器的值

_nop_()

_nop_()

_nop_()

ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ch | ADC_START//开ADC电源,选择AD转换速率,并选择AD通道,开始AD转换

_nop_() //设置ADC CONTR控制寄存器后,要加4个空 *** 作延时才可以正确读到ADC CONTR寄存器的值

_nop_()

_nop_()

_nop_()

while (!(ADC_CONTR &ADC_FLAG))//等待AD转换结束

ADC_CONTR &= ~ADC_FLAG//关闭ADC

result=ADC_RES//将AD转换结果的高两位赋给result

result=result<<8 //将result循环左移8位

result+=ADC_RESL //将AD转换结果的底8位加高两位共10位给result

return result //返回10位AD转换结果

}

void sendDataToProcessing(char symbol, int dat ){

putchar(symbol) // symbol prefix tells Processing what type of data is coming

printf("%d\r\n",dat) // the data to send culminating in a carriage return

}

void UART_init(void)

{

TMOD = 0x20 //定时器工作在定时器1的方式2

PCON = 0x00 //不倍频

SCON = 0x50//串口工作在方式1,并且启动串行接收

TH1 = 0xFd//设置波特率 9600

TL1 = 0xFd

TR1 = 1 //启动定时器1

}

char putchar(unsigned char dat)

{

TI=0

SBUF=dat

while(!TI)

TI=0

return SBUF

}

void _nop_ (void)

{}

void T0_init(void){

// Initializes Timer0 to throw an interrupt every 2mS.

TMOD |= 0x01//16bit TIMER

TL0=T0MS

TH0=T0MS>>8

TR0=1 //start Timer 0

ET0=1 //enable Timer Interrupt

EA=1// MAKE SURE GLOBAL INTERRUPTS ARE ENABLED

}

void T1_init(void){

// Initializes Timer0 to throw an interrupt every 2mS.

TMOD |= 0x01//16bit TIMER

TL1=T0MS2

TH1=T0MS2>>8

TR1=1 //start Timer 0

ET1=1 //enable Timer Interrupt

EA=1// MAKE SURE GLOBAL INTERRUPTS ARE ENABLED

}

void ADC_init(unsigned char channel)

{

P1ASF=ADC_MASK<<channel//选择P1. channel作为A/D输入来用

ADC_RES=0//清除ADC结果寄存器RES

ADC_RESL=0//清除ADC结果寄存器RESL

AUXR1 |= 0x04//调整ADC格式的结果

}

void Timer1_rountine(void) interrupt 1

{}

unsigned int analogRead(unsigned char channel)

{

unsigned int result

while (!(ADC_CONTR &ADC_FLAG))//Wait complete flag

ADC_CONTR &=!ADC_FLAG//clear ADC FLAG

result=ADC_RES

result=result<<8

result+=ADC_RESL

// ADC_CONTR|=channel|ADC_POWER|ADC_SPEEDLL|ADC_START

return result

}

// Timer 0中断子程序,每2MS中断一次,读取AD值,计算心率值

void Timer0_rountine(void) interrupt 1

{

int N

unsigned char i

// keep a running total of the last 10 IBI values

unsigned int runningTotal = 0 // clear the runningTotal variable

EA=0 // 关定时器中断

TL0=T0MS

TH0=T0MS>>8 //重装16位定时器初值

Pressure = (GetADCResult(PressurePin)) //****************

DisBuff2[3] = Pressure%10+48//取个位数

DisBuff2[2] = Pressure%100/10+48//取十位数

DisBuff2[1] = Pressure%1000/100+48 //百位数 ***************

DisBuff2[0] = Pressure/1000+48//取千位数

Signal = GetADCResult(PulsePin) // 读脉搏传感器

sampleCounter += 2// 使用这个值跟踪记录脉搏时间间隔在ms级

N = sampleCounter - lastBeatTime // 减上个节拍的时间来避免噪声

// 找到脉搏波的波峰和波谷

if(Signal <thresh &&N >(IBI/5)*3){ // 如果脉搏传感器输出小于电源电压一半 并且 消除噪声时间小于 3/5个脉搏时间间隔

if (Signal <Trough){// 如果脉搏传感器输出小于波谷

Trough = Signal// 跟踪脉搏波的最低点

}

}

if(Signal >thresh &&Signal >Peak){ // 如果输出大于电源电压一半并且大于波峰

Peak = Signal// 将新值设为波峰

}// 跟踪脉搏波的波峰

if (N >250){ // 避免高频噪声

if ( (Signal >thresh) &&(Pulse == false) &&(N >(IBI/5)*3) ){

Pulse = true // 当检测到一个脉搏时将脉搏标志设为真

blinkPin=0 // 点亮脉搏灯

IBI = sampleCounter - lastBeatTime// 测量两个脉搏的时间in mS

lastBeatTime = sampleCounter // 跟踪脉搏时间

if(secondBeat){// 如果这是第二个脉搏

secondBeat = false // 清除标识

for(i=0i<=9i++){ // 全部的数据作为真实脉搏BMP

rate[i] = IBI

}

}

if(firstBeat){ // 如果是第一个脉搏

firstBeat = false // 清除标志

secondBeat = true // 设置第二脉搏标志

EA=1 //开中断

return // IBI 值是不可靠的所以抛弃

}

for(i=0i<=8i++){// 移动数据在rate数组中

rate[i] = rate[i+1] // 顶替旧值

runningTotal += rate[i] // 加上第九个新值

}

rate[9] = IBI // 加最后的IBI到rate数组中

runningTotal += rate[9] // 加上一个IBI到runningTotal

runningTotal /= 10// 取平均值

BPM = 60000/runningTotal // 一分钟可以检测到多少个心跳及 BPM!

if(BPM>200)BPM=200 //限制BPM最高显示值

if(BPM<30)BPM=30 //限制BPM最低显示值

DisBuff[2] = BPM%10+48//取个位数

DisBuff[1] = BPM%100/10+48//取十位数

DisBuff[0] = BPM/100+48 //百位数

if(DisBuff[0]==48)

DisBuff[0]=32

QS = true // 设置QS标志

// QS FLAG IS NOT CLEARED INSIDE THIS ISR

}

}

if (Signal <thresh &&Pulse == true){ // 当电压归零节拍结束

blinkPin=1 // 熄灭脉搏灯

Pulse = false// 重置脉搏标识我们可以重新测

amp = Peak - Trough // 得到脉搏波的峰峰值

thresh = amp/2 + Trough // 设置thresh位脉搏峰峰值的一半

Peak = thresh // 为下一次测试重置波峰

Trough = thresh

}

if (N >2500){ //如果超过2.5秒没有检测到一个脉搏

thresh = 512 // 重新设置波谷

Peak = 512 // 重新设置波峰

Trough = 512 // 重新设置间隔

lastBeatTime = sampleCounter // 把最后的节拍时间更新

firstBeat = true // 重新设置标志避免噪声

secondBeat = false // 当我们得到心跳的时候

}

EA=1 // 开中断

}// end isr


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/12565083.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-26
下一篇 2023-05-26

发表评论

登录后才能评论

评论列表(0条)

保存