【补档_STM32】脉搏波采集显示硬件设计

一、脉搏波简介

​ 脉搏一般情况下指的都是动脉脉搏。每分钟的脉搏次数称为脉率,正常情况下与心率是一致的。心脏的一次收缩和舒张成为一个心动周期。在每个心动周期内,心室的收缩和舒张会引起脉内压力的周期性波动,使动脉扩张和回缩,从而使得动脉血管发生有规律的搏动,称为脉搏。当脉搏在血管中向前传递的时候,是采用波浪式向前,所以称为脉搏波。脉搏波是一种波的形式,当心脏规律性的进行收缩和舒张运动的时候,血液注入到主动脉流经身体其他部位时产生的一种波。心室进行收缩运动的时候,主动脉瓣会呈现一种被张开的状态,血液在这种情况下会注入主动脉,但是在血管中存在着阻挠血液流动的力,这样血液会有一部分不能够立即注入到静脉中,造成血液会暂时存在主动脉的近端,扩张了主动脉,而导致主动脉血液容量变大,血压升高等变化。而当心室舒张时,主动脉瓣会呈现关闭状态,停止向动脉注血,主动脉在血管的弹性影响下而回到最初状态。脉搏波是心脏的收缩和舒张沿动脉向四周传递而形成的,其传播特点与心脏射血能力、血管粗细、血管壁弹性、血液粘稠度等有关。因此,在脉搏波中包含着大量的人体心脏和血管等人体系统的病理和生理信息。典型的脉搏波如图所示。

img

图中的 1 表示脉搏波的上升支,代表心室的快速射血周期,表现为上升快速且平滑,上升的速度与心室的射血能力以及动脉血管的阻力和血管壁的弹性有关,占整个脉搏波波动周期的时间比较短,图中 a 点所处的位置为心室射血结束,紧接着主动脉关闭,进入下降支。2 表示脉搏波的下降支,是心室射血到下一次射血开始前的一段时期,占整个脉搏波动周期的时间比较长,a 点之后,主动脉内血流向主动脉瓣方向反向流冲击主动脉瓣,因瓣膜关闭不能进入心室,因而又退回到主动脉,过程类似潮汐,因此称为潮波,又称为重波前波,如图 中 b 所示。c 点为重波波谷,又被称之为降中峡,它的形成是因为心室收缩完毕、舒张开始,此时主动脉内血流趋向回流,即血流向主动脉瓣方向反向流动,因此在急速降低的脉搏波的降支形成一个切迹。d 点为重搏波峰。在心室射血期缓慢下来后,心室慢慢舒张,室内的压力很快降低到明显低于主动脉的脉压,主动脉内的血流开始向心室方向反向流动,因主动脉瓣关闭,血流逆流冲击主动脉瓣,但因瓣膜关闭血流不能流入心室,只能向主动脉退去,因此造成动脉内的压强在突然降下后,很快的上升,动脉管壁亦随之稍有扩张,因此,在下降支的终端形成一个小波。

二、系统组成

该系统由Pluse Sensor光电反射式传感器模块、基于IIC的OLED显示模块和STM32F103ZET6最小系统三个模块组成。Pluse Sensor脉搏波形模块采集原始模拟信号,通过导线传输给STM32芯片经内部ADC转换为数字信号,再通过心率脉搏算法得到心率BPM,然后由画点函数将数据以波形和数据的方式在OLED屏幕上实时显示,其系统框图如下图所示。

img

根据图所示的系统框图,本设计以STM32F103为核心,简单的外围电路为辅助,主要实现实时采集人体的脉搏以及心电信号,采集到的脉搏波形在显示屏上实时显示,并计算出每分钟心跳次数的功能。

三、系统模块选择

3.1传感器选择

PulseSensor 是一款用于脉搏心率测量的光电反射式模拟传感器。将其佩戴于手指、耳垂等处,利用人体组织在血管搏动时造成透光率不同来进行脉搏测量。传感器对光电信号进行滤波、放大,最终输出模拟电压值。单片机通过将采集到的模拟信号值转换为数字信号,再通过计算就可以得到心率数值。

3.2主控芯片的选择

本设计的单片机采用意法半导体的STM32F103ZET6。此芯片是基于高性能的ARM®Cortex™-M3的32位RISC内核,工作频率最高可达72 MHz。Cortex-M3是一个32位处理器内核。内部的数据路径是32位的,寄存器是32位的,存储器接口也是32位的。CM3采用了哈佛结构,拥有独立的指令总线和数据总线,可以让取指与数据访问并行不悖。这样一来数据访问不再占用指令总线,从而提升了性能。为实现这个特性,CM3内部含有好几条总线接口,每条都为自己的应用场合优化过,并且它们可以并行工作。此外此款单片机还有丰富的外设比较低的功耗,因此可以用在医疗设备上。

3.3显示设备及数据传输

我们采用了四引脚的IIC模式OLED屏幕,处理好的数据将在SDA数据线和SCL时钟的共同控制下通过单片机的IIC接口实时发送到OLED显示屏中显示出波形。同时也会将计算好的心率BPM显示。

四、检测原理

4.1传感器

传感器只有三个引脚,分别为信号输出 S 脚 、电源正极 VCC 以及电源负极 GND,供电电压为 3.3V到5V,可通过杜邦线与开发板连接。

上电后,传感器会不断从 S 脚输出采集到的电压模拟值。需要注意的是,印有心形的一面才是与手指接触面,在测量时要避免接触布满元件的另一面,否则会影响信号准确性。模块图如图。

img

4.2心跳节拍采集与计算处理

总体上就是设定一些参数,判断心跳节拍是否产生(也就是有没有把手稳定的放到传感器上)。如果有,那么找到该节拍的峰值和波谷,计算公式如下:

IBI = sampleCounter - lastBeatTime

测量示意如图所示。

img

4.3心率计算

通过以下公式即可算得心率。

img

4.4 OLED显示

​ 脉搏波形可以在OLED屏幕上实时显示,但由于IIC接口是并行传输,屏幕的刷新速度可能会比较慢

img

五、电路设计

5.1 STM32F103控制模块

STM32F103单片机,具有512kB Flash,64kB SRAM,系统时钟72MHz,内核为ARM32位的Cortex-M3 CPU。 该控制器含有112个多功能双向I/O口,所有I/O口可以映像到16个外部中断;同时,还包括多达4个16位定时器,每个定时器有多达4个用于输入捕获、输出比较、PWM或脉冲计数的通道,串口通信接口可用于单片机与上位机或串口显示屏的通信。STM32片上资源丰富,功能强大,结合中断、定时器等其他外设实现便携式心电监测功能。其硬件电路如图所示,串口屏的RXD和TXD分别连接单片机上的PA9(TXD)和PA10(RXD),进行人机数据交换。

IMG_256

5.2 Pulse Sensor心电监测模块

Pulse Sensor是一款用于脉搏心率测量、脉搏波形测量和HRV分析的光电反射式模拟传感器。将其佩戴于手指、耳垂等处,通过导线连接可将采集到的的模拟信号转变为数字信号,再通过单片机的简单计算后就可以得到心率数值。本传感器采用了峰值波长为515mm的绿光LED,光接收器采用了APDS-9008,这是一款环境光感受器,感受峰值波长为565mm,两者峰值波长相近,灵敏度较高。此外,由于脉搏信号容易受到各种信号干扰,在传感器后面使用了低通滤波器和由运放MCP6001构成的放大器,将信号放大了330倍,同时采用分压电阻设置直流偏置电压为电源电压的1/2,使放大后的芯片可以很好的被单片机的AD采集到的。

img

5.3 OLED(I2C)显示模块

本设计采用主控为SSD1306的0.96寸I2C OLED屏幕来显示得到的数据并绘制波形。其数据传输原理是使用STM32F103ZET6自带的IIC接口(或使用GPIO模拟IIC也行),通过SDA数据线和SCL时钟线来与OLED屏幕通信,进而接收或发送处理好的数据。OLED屏幕显示图像和字符锐利清晰,并且体积小,功耗低,作为长时间显示的屏幕十分合适,其实物图如图所示。

img

六、软件设计

6.1程序总体设计说明

​ 采集到的模拟量经过STM32F103ZET6中自带的AD转换模块转化为数字量,并以3.3V作为参考电压将数字量归化为电压。

由心率采集与计算处理模块计算得出心率BPM和心跳节拍IBI;

由位于定时器中的波形处理模块绘制波形;

由按键控制模块实现通过KEY1切换显示界面的功能;

6.2 主程序main

本设计的系统软件主程序流程图如图9所示,基于STM32F103ZET6,先初始化各项外设,然后判断KEY1是否被按下,并且通过按下的次数切换不同的功能模块进行显示 。这种设计可以更充分地利用OLED屏幕显示更多的信息。

img

6.3按键处理函数KEY_deal

img

在该函数中,需要通过key1_state标志位处理KEY1按键。KEY1就是用于决定display_flag值的。key1_state在等于1时代表某按键被按下。当判断KEY1按下后,清零标志位key1_state,并通过OLED_Clear()进行初始清屏操作,然后开始对display_flag进行赋值:若当下display_flag等于0(也就是KEY1只按下了一次,display_flag此时还为初始化时赋的0值),则使display_flag等于1;若当下display_flag已经不为0,则判断其为1(KEY1按下1次)还是2(KEY1按下2次),如果是1则使display_flag等于2,如果是2则清零display_flag。

6.4 数值显示函数OLED_Value_display

img

该函数中有两个主要变量:temp(u8无符号字符型,用于保存采集到的心率的原始ADC值的整数部分)和xiao(浮点型,保存小数部分)。

取整数和取小数的逻辑:取整,用原始ADC值(adcx)与1做除法运算(“/”)即可去除小数部分,然后把值保存在temp;取小数,把原始ADC值与取整后的值相减并存到xiao即可(因为小数可能很小,所以放大1000倍便于显示)。然后调用OLED_ShowString()、OLED_ShowHz()以及OLED_ShowNum()等函数显示。

6.5 波形显示OLED_Waveform_display函数

img

waveform_flag为波形采样时间计数标志位,当采样128次之后才一次性显示出来,即waveform_flag变为1时(该标志位在波形处理函数中,由一个采样延时变量控制),说明已经采样了128次,此时OLED_Waveform_display()函数按水平方向为Y轴(长度为128),垂直方向为X轴(长度为64)的规定开始绘制波形。值得注意的是,由于OLED屏幕的显示方式,我们绘制时是按“页”绘制,一“页”为OLED上一块81的区域,一共要绘制8“页”。这也是为什么要在设计坐标系时将x、y轴对调的原因。当绘制完毕,我们将所有数据更新到GRAM即可显示一帧波形曲线,若刷新率足够快,那么我们就能得到连续的波形。*

6.6 自带库中的关键函数

1、OLED_DrawPoint()画点函数

该函数有 3 个输入参数,前两个是要画点的坐标,第三个 t 为要写入 1 还是 0。该函数实现了我们在 OLED模块上任意位置画点的功能。

自定义OLED 坐标系如下: 【构建OLED 直角坐标系, x,y轴反置 方便函数运算】

​ ↑x

​ |

​ 127--------

​ | |

​ | |

​ | |

​ | |

​ | |

​ | |

​ (0,0)----------→y

​ 63

img

void OLED_DrawPoint(u8 x,u8 y,u8 t)

{

u8 pos,bx,temp=0;

if(x>127||y>63)return;//超出范围了.

pos=7-y/8;/*输入的Y坐标要除以8,以定位到要改变的那个点对应的“页”(page),减7是为了转化为1到8下的位置(因为底层是用0到7表示的) */

​ bx=y%8;//该点在该页中的位置

​ temp=1<<(7-bx);//把该点写入temp

if(t)OLED_GRAM[x][pos]|=temp; //如果t为1就把该点写1

else OLED_GRAM[x][pos]&=~temp; //如果t为0就把该点写0

}

OLED画图显示字符时,水平方向为Y轴;垂直方向才是X轴;

如何点亮一个点(假设为x,y)?因为我们是纵向取模,所以我们只要清楚y坐标在哪就行了。首先我们要确定y在哪一“页”(page)--> pos=y/8;,因为每8行为一页,而纵向取模是按页来完成的。其次就要知道y在这一页里面的哪里,这就要用到求余运算-->bx=y%8。

例如: Y = 25

pos = 25 /8 = 3,即该点处于第4页(底层驱动是从0开始计数的,代码中再用7减3可得到1到8下对应页数);

bx = 25 % 8= 1, 即该点处于第4页第2行(底层驱动是从0开始计数的);

2、OLED_Refresh_Gram()gram更新函数

由于我们使用的是正点原子系列开发板,其在 STM32 内部定义了一个块 GRAM:u8 OLED_GRAM[128][8];此部分 GRAM 对应 OLED 模块上的 GRAM。在操作的时候,我们只要修改 STM32 内部的 GRAM 就可以了,然后通过 OLED_Refresh_Gram 函数把 GRAM 一次刷新到 OLED 的 GRAM 上。OLED_Refresh_Gram 函数先设置页地址,然后写入列地址(也就是纵坐标),然后从 0 开始写入 128 个字节,写满该页,最后循环把 8 页的内容都写入,就实现了整个从 STM32 显存到 OLED 显存的拷贝。

6.7 ADC处理函数

img

此处定义一个temp(u16,无符号字符型),调用Get_Adc()获得ADC原始值,此时该原始值只是ADC输出的一个数字量,不带单位,由于ADC是12位的(最大的数字量是4096),所以这个原始值最大只能为4096。因为它不带单位,我们想要用单位表示它就必须有一个参考,于是就用电压作为这个参考。参考电压是3.3v,所以用原始值*3.3/4096就可以把ADC采集到的数字量转化为电压值,保存到adcx即可。Signal = temp>>2用于处理传感器的值,向右位移2位。

6.8 心率采集与计算处理函数(代码详见工程)

设定一些参数,判断心跳节拍是否产生(也就是有没有把手稳定的放到传感器上)。如果有,那么找到该节拍的峰值和波谷,用sampleCounter与lastBeatTime做差来得到节拍持续的时间,差值为IBI。其中,sampleCounter是位于通用定时器中的一个自增量,自增值为2ms,用于记录CPU运行时间;lastBeatTime始终跟随上一次sampleCounter的值,目的是为了记录下一次节拍的时间,它的初值为0。

例如从CPU开始运行开始记录时间到第100ms的时候有了一个脉冲,此时IBI = sampleCounter – lastBeatTime,舍弃此时的IBI,显然波形没有持续100ms,此时使sampleCounter = lastBeatTime,当下一次波形到来时(相隔时间很短,因为人的心跳是连续的,可视为脉冲是连续的一个一个到来),此时再用IBI = sampleCounter – lastBeatTime便可以得到单独一个脉冲的持续时间。将IBI存入一个数组中,用保留的最后十个数据,取一个平均值保存为runningTotal,再把runningTotal平均到一分钟里面就得到心率BPM,计算公式如下:

IBI = sampleCounter - lastBeatTime

6.9 波形处理函数Waveform_deal(代码详见工程)

该函数中包含两个主要变量,waveSample_times和xiao,前者为采样延时变量,用于保证每采样128次记录一次waveform_flag标志位,然后使波形显示函数绘制波形;xiao则是用来控制转换并放大后的小数的长度以便于oled显示的,一般我们设置为45到80。最终,xiao经过处理后的值在0到60间,用来控制波形的宽度。(因为我们规定x轴的长度为64,超过的话显示会不完整)

6.10 通用定时器TIM3

img

TIM3中断,执行时间由TIMER控制,每2ms执行一次。该定时器内部的中断函数为标志量sampleCounter的自增语句、Waveform_deal()波形数据处理函数和HeartRate_deal()心率采集与计算处理函数,作用就是每2ms执行对应函数或语句一次以达到之前介绍的一些功能。

6.11主界面显示函数OLED_Main_display

该函数会调用一个库函数OLED_ShowHz()去显示作者和标题信息。

七、实物图

放几张效果图(由于时间限制,就直接用现成的开发板了)

image-20210601154555100

image-20210601154748226

image-20210601154756762

工程地址:
https://gitee.com/daycen/STM32-BPM
通过Keil uVision5打开即可使用

posted @ 2021-06-01 15:54  dayceng  阅读(2390)  评论(7编辑  收藏  举报