找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 23767|回复: 22
打印 上一主题 下一主题
收起左侧

STM32三导联蓝牙心电监护仪设计,C#上位机显示波形 附源码与电路

  [复制链接]
跳转到指定楼层
楼主
ID:402025 发表于 2018-9-25 10:05 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
题  目:  基于stm32的无线蓝牙心电监护仪

视频:


摘要
   近些年,随着物质生活水平的提高,人们开始逐渐关注自身健康,随之而来的就是市场上出现了各种各样的健康相关的电子消费品。心电图信号(ECG)是人体健康相关的一项很重要的参数,通过对ECG信号的分析可以找出人体出现的各类心脏疾病,同时通过ECG信号还可以对健康人群起到监护预防作用,近些年市场上出现比较多的此类电子消费品。但是市场上的心电监测设备存在价格比较昂贵,同时在质量上也不能得到保证。本设计旨在设计出一种价格相对低廉,但同具有较高的测量精度的心电信号检测系统,该系统同时具有测量信号分析信号存储信号的功能,同时在使用上便捷简单。本设计使用AD8232作为模拟前端来采集放大处理人体的心电信号,使用STM32F103VET6作为主控芯片来对模拟前端的信号进行进一步滤波处理,最终使用上位机将处理后的信号进行分析保存,在硬件端可以通过简单的按键来控制检测系统的使用,并且通过OLED显示屏来显示相应的数据。本系统实现了硬件与上位机配合使用的方式,当然也可以仅仅使用硬件端做简单的信息采集,正文部分从内容上细分为硬件配置和软件设计两部分。硬件部分详细论证了各模块的实现原理、器件选型和参数配置,软件部分仔细分析了系统功能的实现逻辑、控制思路及算法流程。系统使用STM32F103VET6作为最主要的处理单元,可以对信号进行快速的滤波处理,保证信号的实时上传,使用AD采样配合DMA传输,可以快速的将信号采集出来,同时模拟前端自身具有的信号放大以及滤波的能力可以更好的保证信号的精度。经过多次的实际测量以及对多人进行心电数据采样后,分析得出该系统在信号的质量以及精度上能得到很好的保证,同时也做到了使用便捷,具有较高的实际价值。

1.        引言
伴随着人口老龄化问题的日趋严重,心脏类疾病的发病率越来越高,人们对于心电监护类消费品的需求逐渐加大,心电监护仪是一种新一代的医用电子仪器用户在家中使用它就可以完成心电图的测量。它与监护诊断仪器不同,心电监护仪必须长时间实时监护病人的心电图,才能检测出心率变化趋势,保存失常的心电图信号,为医务人员提供治疗的参考依据,起到缓解并移除病情的作用。
市面上普遍存在的心率测量方式为通过光线照射毛细管来检测心率,虽然这种方式可以有效的测量出人的心率,但无法提供心电图形的测量。另一方面现阶段市场上很多能买到心率仪器普遍价格高昂,不是很能被大众接受,但同时那些廉价的心率仪存在质量差,信号测不准,数据不准确等多种问题,因此本设计可以提供一种价格便宜数据准确且同时具有较强的数据分析功能的心电信号采集系统。
在本设计中,心电信号的采集系统主要分为模拟前端对数据的采集,微处理器对信号进行采样滤波上传,上位机部分对心率、心电波形的计算以及存储。在本设计中模拟前端采用ADI公司的一款用于心电以及其它生物电测量的集成信号调理芯片AD8232,该芯片集成了仪表放大器、增益放大器、右腿驱动电路还具有导联脱落检测功能可实现高共模抑制能力。本设计采用AD8232芯片作为信号采集模块的模拟前端,用于对人体心电信号的采集。
在考虑到本设计需要较强的信号处理能力因此微处理器采用的是STM32F103VET6,使用该芯片的原因是应为该处理器兼具了较强性能以及便宜的价格,该芯片主要用于对数据采样,并且进行IIR滤波将处理好的数据通过串口上传到上位机,同时还可以将波形数据通过显示模块显示出来。本设计最后一部分为上位机,上位机使用C#语言编写,主要实现串口读取、数据显示、数据处理保存等功能。基于以上的分析本设计采用AD8232与STM32F103VET6的搭配设是符合设计要求的。
2.        系统方案
本设计详细分析了各种MCU包括STC89C52、STM32F103VET6、STM32F407、MSP430等多种MCU之间的优缺点,同时分析了市场上使用的ADS1298,AD8232,ADAS1000三种ECG前端方案进行了分析,包括性能,价格等多个点进行综合分析。最终选择了STM32F103和AD8232配合的方案作为本设计的主要方案,该组合同时保证了性能与较经济的价格。同时在蓝牙模块的选择上使用了常用的HC-05蓝牙芯片来用于与上位机的通信。在数据的显示上最终选择了SSD1306 OLED来实现本设计硬件端波形显示以及相关数据显示功能。为了保证本设计的独立供电使用了XL6009 DC-DC升压模块来将3.7V锂电池电压提升到5V来为系统供电。在本设计中模拟前端AD8232作为最主要的信号采集前端,在本文后将做详细的介绍,同样作为MCU使用的STM32F103VET6也要做详细的讨论。作为显示的OLED显示屏由于是最直接的显示系统的显示效果他的选择也将会影响到用户体验,因此本文也将讨论多种现有的方案并在其中选择最佳的方案。
3.        系统硬件设计
3.1 系统原理框图设计
    本次设计的基于STM32的心电信号采集系统由四个模块组成:STM32F103VET6主控模块、OLED显示屏模块,蓝牙模块,AD8232模拟前端模块。在本设计中首先通过三路导联获取人体的心电信号,三路导联线分别与人体的左胸、右胸、右腿连接。然后通过将信号输入到AD8232模拟前端中进行信号放大,将信号放大1100倍后通过模拟输出将信号传输到STM32的AD端口,STM32通过12位AD将模拟信号转换为数字信号,然后通过程序实现的高通滤波以及50HZ陷波器对信号进行滤波处理,处理完成后将信号通过与STM32串口连接的蓝牙模块传输到上位机中,同时信号波形显示在与STM32连接的OLED显示模块上并计算出此时人体的心率数据,当上位机接收到信号后对信号进行SG平滑滤波,最终处理完的信号可以通过上位机显示出波形,计算相关的心率参数并且能够保存在文件中。本设计的基本框图如图3-1所示。

系统总体原理框图

3.2 系统主要元器件介绍
3.2.1 STM32F103VET6主控芯片
MCU芯片选用的是意法半导体公司的STM32F103VET6。该芯片的具有ARM 32位 Cortex-M3 CPU,片上128k Flash,20kRAM,12位精度的片内ADC转换器,以及串口等外设。Cortex-M3 采用 ARM V7 构架,不仅支持 Thumb-2 指令集,而且拥有很多新特性。较之ARM7 TDMI,Cortex-M3 拥有更强劲的性能。STM32拥有超多的外设并且具有极高的集成度。同时STM32有杰出的功耗控制。STM32各个外设都配置了独立时钟开关,可以通过关闭相应外设的时钟来降低功耗。对于学习与设计来说STM32有极低的开发成本,STM32程序仿真过程十分简单可以通过串口直接下载程序,同时配合JTAG仿真器可以方便的实时仿真调试代码。
本设计使用的STM32F103VET6拥有72MHZ工作频率、256k闪存(FLASH)空间、高达64K的SRAM,同时支持定时器、ADC、SPI、USB、IIC和UART等多种外设,可以在简单的电路上完成信号采集与数据滤波等运算,图3-2为STM32F103VET6内部资源图。模拟信号的采样使用了ADC1的第10通道,两次采样率为250HZ,转换精度为12bit,ADC的触发时钟配置为TIM2输出,通过DMA方式进行外设到内存数据传输,采用DMA乒乓结构进行数据存储和处理。同时使用STM32F103VET6实现了0.3HZ高通滤波器以及50HZ数字陷波器。在数据上传方面使用STM32的串口1将数据发送到上位机。
图3-2 STM32F103内部资源图

     在本设计中,使用STM32F103VET6作为主控制器,使用STM32F103VET6的AD端口来读取模拟前端的信号,使用UART1和蓝牙模块连接后实现数据的上传,同时OLED模块使用5个IO口与STM32F103VET6连接后通过STM32F103VET6的控制实现波形以及其他数据的显示。通过其他的IO口还可以实现按键与LED灯显示。
图3-3 STM32F103VET6引脚配置图

本设计总共使用了STM32F103VET6 100根引脚中的15根,其引脚配置图如图3-3所示,其中各引脚功能如表3-1所示:

表3-1 STM32F103VET6引脚配置功能
接口名称
接口功能
NRST
系统复位
PB3
OLED模块CS口
PB4
OLED模块DC口
PB5
OLED模块RST口
PB6
OLED模块DIN口
PB7
OLED模块CLK口
PCO
ADC1的通道10模拟前端信号输入
PA10
UART1_RX
PA9
UART1_TX
PB0
LED1
PC3
LED2
PC4
LED3
PA0
按键1
PC13
按键2
PA1
按键3

3.2.2 AD8232模拟前端芯片
              AD8232是一款用于心电信号测量及其他生物电测量的集成信号调理模块。该芯片可以在有运动或远程电极放置产生的噪声的情况下提取、放大及过滤微弱的生物电信号。该芯片使得模数转换器(ADC)或嵌入式微控制器(MCU)能够便捷的采集输出信号,AD8232模拟前端模块由AD8232芯片和辅助电路构成,由于心电信号的频率范围为0.5~100 HZ,幅度范围为0~4 mV,属于低频微弱小信号。同时心电信号中混杂着诸多干扰,如肌电噪声、工频干扰、基线漂移以及运动伪迹等,所以心电信号采集模块需在有效提取出微弱的心电信号的同时将对各种噪声起到最大的抑制。心电信号的前端放大模块由AD8232以及外围电路构成,实现了心电信号的输出,图3-4为AD8232内部滤波器结构图。

图3-4 AD8232内部结构图
3.2.3 SSD1306 OLED显示模块
OLED显示屏模块128*64 OLED显示屏组成,该OLED使用SSD1306作为主控芯片,与MCU采用IIC接口通信,OLED无需背光、显示单元能自发光具有很好的显示效果。OLED显示模块一共有7个管脚,其中两个为电源VCC与GND,一个为复位口,还有四个管脚为OLED模块的控制管脚,使用四线串行模式来进行图像的显示,图3-5为SSD1306内部结构图。
图3-5 SSD1306内部结构图
3.2.4 HC-05蓝牙模块

    考虑到心电信号采集系统的便携性,因此本设计使用无线的方式来进行数据的传输,经过多种蓝牙模块的比较最终选择HC-05蓝牙模块,HC-05是一款高性能的主从一体蓝牙串口模块,可以和各种自带蓝牙功能的外部设备连接包括手机、电脑等职能终端设备,HC-05蓝牙模块支持的通信频率为4800~1382400,并且可以同时兼容5V或3.3V单片机系统,在本设计中能够方便的通过TX、RX接口与STM32F103VET6连接。本模块使用非常灵活、方便。本设计中电脑通过与蓝牙模块配对连接后可以在电脑上实现COM口从而实现串口数据传输。蓝牙模块设置波特率为115200,同时通过AT指令将工作模式配置为从机模式以便于PC连接。

图3-6  HC-05蓝牙模块引脚图
3.2.5 XL6009 DC-DC电源模块
XL6009 是一款使用开关电流的升压模块。该模块使用XL6009E1为核心芯片,性能远超LM2577。XL6009升压模块成本低,性能更卓越,XL6009拥有超宽输入电压3V~32V,最佳工作电压范围是5~32V,同时内置4A高效MOSFET开关管,电源的转换效率高达94%,XL6009还具有400KHz超高开关频率,使用小容量的滤波电容即能达到非常好的效果,纹波更小,因此能够为硬件系统提供稳定可靠的供电,图3-7为内部结构图。
图3-7  XL6009芯片内部结构图

3.3 系统模块电路设计
3.3.1 AD8232模拟前端电路设计
心电信号的频率范围为0.5~100 Hz,幅度范围为0~4 mV,属于低频微弱小信号。同时心电信号中混杂着诸多干扰,如肌电噪声、工频干扰、基线漂移以及运动伪迹等,所以心电信号采集模块需在有效提取出微弱的心电信号的同时将对各种噪声起到最大的抑制。因为STM32的AD检测电压为0-3.3V所以需要对原始信号进行1100倍的放大,同时由于原始信号中有较多低频干扰所以需要对信号进行0.5Hz高通滤波。心电信号的前端放大模块由AD8232以及外围电路构成,实现了模块模拟输出心电信号和导联脱落检测功能。在AD8232与右腿连接的导联接口图3-8中U22处在输入端具有1nF栅极电容和499kΩ电阻。这在每个输入端上构成一个简单的RC滤波器来实现低通滤波器,无需增加外部元件便可降低高频时的整流,在左右胸的导联端图3-8中U44、U42中也使用同样的方式来获得更好的输入信号。
为了保证输出信号的质量还可向隔直电路中添加额外的节点,以进一步抑制低频信号,即图3-8中HPSENSE、HPDRIVE、IAOUT、SW。该电路拓扑结构的另一个好处是,它允许利用较小的R和C值来提供较小的截止频率,并且可使用图3-8中电阻R77 来控制滤波器的Q,从而实现窄带带通滤波(针对心率监测)或最大带通平坦度(针对心脏监护)。在这种拓扑结构中,滤波器衰减在极低频率下会变回单极点滚降。由于初始滚降为40 dB/十倍频程,因此变回20 dB/十倍频程对滤波器的带外低频信号抑制性能影响不大。根据
R75=R76=10MΩ ,C24=C2=0.22UF ,R77=0.14×R75计算得到截止频率为:
                                               (3-1)
图3-4所示的仪表放大器由两个匹配良好的跨导放大器(GM1和GM2)、隔直放大器(HPA)和一个由C1和一个运算放大器构成的积分器组成。跨导放大器GM1产生一个与其输入电压成比例的电流。达到反馈要求时,跨导放大器GM2的输入端上即会出现大小相等的电压,从而匹配GM1产生的电流。这种差异会产生误差电流,该电流由电容C1进行积分。所得到的电压出现在仪表放大器的输出端。该放大器的反馈由GM2通过两个独立的路径施加:两个电阻对输出信号进行分频,以设置100的总增益,而隔直放大器则对与基准电平的任何偏差进行积分。因此,GM1输入端上高达±300 mV的直流失调会以相同幅度但相位相反的方式出现在GM2的输入端,始终不会导致目标信号出现饱和。为了获得失真最小的ECG波形,AD8232配置为使用一个0.5 Hz双极点高通滤波器,后接一个双极点、40 Hz、低通滤波器。除40 Hz滤波功能以外,运算放大器级的增益还配置为11,因此系统总增益为11×100=1100倍。
图3-8  AD8232模拟前端模块电路图
3.2.2 按键控制以及LED电路设计

    本设计需要通过三个按键来实现硬件端的模式选择,以及相关功能的切换,使用实体按键可以做到操作方便可靠,可以通过简单的显示引导即可让用户知道该如何操作硬件,将STM32F103VET6的PA0、PA1、PC13配置成GPIO_INPUT模式来实现按键检测,按键通过一个上拉电阻与STM32F103VET6的IO口连接。同样为了使用户很清晰的知道硬件系统工作在什么模式,通过三个LED灯的亮灭来实时的呈现硬件系统的两种工作模式以及是否在检测ECG数据,将STM32F103VET6的PB0、PC3、PC4配置成GPIO_OUTPUT模式,同时将IO与一个510R的限流电阻串联连接到STM32F103的IO口上。当IO口输出低电平时LED亮,当IO口输出高电平时LED熄灭,图3-9与图3-10为按键与LED连接引脚图。

图3-9 按键电路

图3-10 LED灯电路

3.3.3 OLED显示模块电路设计

OLED显示模块通过四线串行模式与STM32F103VET6连接,一共需要5根数据线,其中OLED显示模块的CS、DC、RES、DIN、CLK分别与STM32F103VET6的PB3、PB4、PB5、PB6、PB7连接,在这5根线中,RES为OLED的复位信号线,CS为片选信号,DC为数据/指令控制管脚,DIN为串行数据传输脚,CLK为数据时钟,同时OLED模块使用3.3V电压供电。OLED模块电路连接图如图3-11所示。

3.3.4 蓝牙模块与MCU连接电路设计

蓝牙模块一共有四个管脚VCC、GND、TX、RX,其中VCC连接3.3V电源,GND与STM32F103VET6的GND连接,蓝牙模块的TX与STM32F103VET6的UART1_RX连接也就是与PA10管脚连接,蓝牙模块的RX管脚与UART1_TX即管脚PA9。通过上述连接可以实现将ECG数据使用MCU的串口发送到上位机实现上位机部分需要完成的功能。蓝牙模块电路连接图如图3-12所示。


图3-11 OLED模块连接图

图3-12 蓝牙模块连接图


3.3.5 电源模块
本设计使用的电源是3.7V锂电池,但是stm32供电所需要的电压为5V与3.3V,因此使用XL6009 DC-DC升压模块,XL6009是一款开关电源高性能升压模块,该模块使用了XL6009E1作为核心芯片可以实现3V-32V的电压输入以及5V-32V的电压输出,通过可调电阻R1可以调节模块的输出电压,该模块可以用于将电压提升到5V,XL6009电路连接图如图3-13所示。
由于STM32F103VET6的供电电压为3.3V因此需要将XL6009升压模块升压后的5V电压的电压稳压到3.3V为系统提供供电,ASM1117的工作原理和普通的78系列线性稳压器或LM317线性稳压器相同,通过对输出电压采样,然后反馈到调节电路去调节输出级调整管的阻抗。使用型号为AMS117-3.3V的稳压芯片配合输入与输出端两组滤波电容即可构成本设计所需要的3.3V稳压电路,电路输入为5V输出为3.3V,AMS1117电路连接图如图3-14所示。
图3-13 XL6009 DC-DC升压模块电路图

图3-14 AMS1117-3.3V稳压电路图
3.3.6 整体电路连接
在完成各个模块的设计后,需要将各个模块通过相关的接口与STM32连接起来,首先是AD8232模拟前端模块的信号输出口与STM32的AD1通道10连接,即与PC0引脚连接,蓝牙模块与STM32的UART1连接,电源模块连接STM32的VCC与GND,同时OLED与STM32的PB3-PB7连接,同时LED灯以及按键分别与STM32的相关IO口连接。在完成以上连接有即完成了整个硬件平台的搭建,在确保硬件系统可以正常运行的时候还需要使用单独编写的程序来分别测试每一个硬件部分的功能是否正常。当所有硬件模块都能正常工作后,硬件系统的设计全部完成,图3-15为系统整体电路连接图。
图3-15系统整体电路连接图
4.        系统软件设计
心电信号的采集的主要依靠STM32来处理,因此在STM32上需要完成大部份的数据采集与处理,其中包括AD采样,数字滤波器以及数据上传。在系统上电后首先需要通过一个按键来选择系统的工作模式,包括上位机模式以及本地模式,选择本地模式后,系统系统可以脱离上位机单独工作,实现心率波形的测量显示以及心率计算,在本地模式中可以通过两个按键分别控制测量的开始与结束以及切换测量后的显示数据;当选择上位机模式后,系统将所有采集到的数据通过蓝牙传输到上位机,由上位机负责对数据的处理。
数据采样的过程主要通过AD进行,由于要保证采样与数据处理的同时快速进行,需要使用两路DMA轮流传输(DMA-存储器直接访问,这是指一种高速的数据传输操作,允许在外部设备和存储器之间直接读写数据,既不通过CPU,也不需要CPU干预),数据通过AD采样后通过两路DMA轮流送往两个缓存区,同时当一个缓存区数据存满后开始对该缓存区的数据进行数字滤波,这个过程包括50HZ陷波以及0.5HZ高通滤波,当完成这两个步骤后将数据通过蓝牙上传并且清空该缓存区,然后开始等待另一个缓存区存满,如此循环进行数据的采样处理以及发送,图4-1为该过程的程序框图。
4.2 子程序算法流程分析
4.2.1 模块初始化子程序设计
程序的初始化过程主要是对几个主要模块的接口以及STM32F103VET6的工作模式进行选择,首先对STM32F103VET6的UART1进行初始化,波特率设置为115200、无校验位、数据位为8位,停止位为1位;其次是对与OLED连接的接口进行初始化,包括使能PD端口时钟,端口设置为推挽输出以及对OLED显示的亮度,对比度等进行初始化;之
后进行ADC的初始化包括ADC端口初始化,模式初始化以及定时器与DMA中断的初始化,配置ADC的采样率为250HZ;完成以上初始化后需要对滤波使用到的TTR滤波器的基本参数进行初始化;最后完成的是按键与LED灯的GPIO初始化,与按键连接的IO口设置为上拉输入模式引脚速率为10MHz,与LED连接的引脚设置为推挽输出模式引脚速率为50MHZ,图4-2为系统初始化过程。


图4-1 系统整体程序框图

图4-2 系统初始化过程
图4-3 ADC采样过程
4.2.2 ADC配置以及DMA传输配置程序
模拟信号的采样使用了ADC1的第10通道,两次采样时间间隔为14个时钟周期,转换精度为12bit,ADC的触发时钟配置为TIM2输出,采样率为250HZ,通过DMA方式进行外设到内存数据传输,采用DMA乒乓结构进行数据存储和处理,也就是两路DMA轮流接收AD采样获得的数据当其中一路的数据缓冲区存满后切换到另一路继续存储,而期间进行对前一路缓冲区数据测滤波处理工作,这样交替进行采样滤波可以很大的提高系统的工作速率。具体的采样过程如图4-3所示。
定时器配置PWM输出关键代码:
  1. TIM_TimeBaseStructure.TIM_Period = 400;      
  2. TIM_TimeBaseStructure.TIM_Prescaler = 720 - 1;
  3. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  4. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
  5. TIM_OCInitStructure.TIM_Pulse = 200;
  6. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
  7. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  8. TIM_OCInitStructure.TIM_Pulse = 200;
  9. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
  10. TIM_CtrlPWMOutputs(TIM2, ENABLE);
  11. (2)配置DMA外设到内存传输
  12. DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
  13. DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue;
  14. (3)用定时器配置ADC采样率
  15. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2 ;
  16. //Timer触发            
  17. ADC_ExternalTrigConvCmd(ADC1, ENABLE);   //使用外部中断触发
复制代码


4.2.3 数字滤波器设计程序
1. ECG噪声来源
工频干扰:工频干扰是由电力系统引起的一种干扰,由50hz及其谐波构成的一种干扰,幅值约为ECG(心电信号)的50%。
基线漂移:呼吸作用引起的,源于被测对象在测试过程中呼吸时电机与人体皮肤间的阻抗仪器放大的热噪声等干扰引起的,频率约为0.15-0.3hz。呼吸作用时ECG幅值有15%的变化。
肌电干扰:来源于人体的肌肉颤抖,肌电干扰产生毫伏级电势,可视为瞬间发生的高斯零均值带限噪声。
2. 50HZ陷波器设计
工频干扰可以用陷波器进行处理,过滤50hz信号,而基线漂移则通过高通滤波器消除0.5hz以下的频率。常用固定频率的设计有IIR滤波器和FIR滤波器两种,其中FIR滤波器具有良好的线性相位。但是在同等性能条件下阶数比IIR滤波器高,运算量大。故本设计的工频陷波和高通滤波均采用IIR滤波器来设计。
IIR滤波器的设计方法有脉冲响应不变法和双线性变换法,但过程比较复杂,借助MATLAB的FDATOOL工具包可以直接生成相关参数。再通过直接II型翻译成C语言形式应用于STM32平台,图4-4为FDATOOL工具配置界面。

图4-4 FDATOOLl工具界面
导出滤波系数得到转移函数:
                            (4-1)
图4-5滤波器效果
图4-6滤波器直接II型实现框图

根据滤波器直接II型实现框图(图4-6)可以得到C语言代码:
const float IIR_50Notch_A[3] = {
                1,
              -0.5577124773,
                0.8047954884
};
x0=ADC_ConvertedValueLocal;  //输入ADC采集到的信号
w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
w0[2]=w0[1];
w0[1]=w0[0];

3. 0.5HZ高通滤波器设计
数字高通滤波器能有效抑制呼吸作用导致的基线漂移,同样采用Matlab的Fdatool工具箱设计0.5hz二阶IIR高通滤波器,导出滤波系数,图4-7为FDATOOL工具配置界面。

图4-7 fdatool工具设计界面
得到响应函数:
                                     (4-2)
C语言代码:
  1. const float IIR_50Notch_B[3] = {
  2.     0.9023977442,
  3.               -0.5577124773,
  4.                 0.9023977442
  5. };
  6. w1[0]=0.991153*(IIR_High_A[0]*x1-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2]);
  7. y1=IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2];
  8. w1[2]=w1[1];
  9. w1[1]=w1[0];
复制代码

4. 数字滤波器Matlab仿真
为了使STM32上设计的滤波器有好的效果,需要在Matlab上提前实现50Hz陷波器,0.5Hz高通滤波器以及SG平滑滤波,通过Matlab仿真可以提前有效的了解滤波器的实际效果,图4-8为MATLAB仿真的最终结果。
Matlab代码:
  1. clc
  2. clear all
  3. fs=250;           %数字滤波器的采样频率fs=250hz
  4. f=50;             %50hz的正弦信号
  5. t=0:1/fs:4;      
  6. %==========================================
  7. s=importdata('test.txt') ; %测试数据
  8. s=s(1:1000);
  9. figure
  10. subplot(411)
  11. plot(s);
  12. title('原始数据')
  13. IIR_B=[0.90239774423695518,-0.55771247730967288,0.90239774423695518];
  14. IIR_A=[1,-0.55771247730967288,0.80479548847391036];
  15. w01=0;
  16. w02=0;
  17. w03=0;
  18. y0=zeros(1,1000);
  19. for i=1:1000
  20.     w01=s(i)-IIR_A(2)*w02-IIR_A(3)*w03;
  21.     y0(i)=IIR_B(1)*w01+IIR_B(2)*w02+IIR_B(3)*w03;
  22.     w03=w02;
  23.     w02=w01;
  24. end
  25. subplot(412)
  26. plot(y0);
  27. title('50HZ陷波器+0.5HZ高通滤波器滤波之后');
  28. y0=y0(:);
  29. A=y0;
  30. %长度为5,阶数为5的S-G平滑滤波器
  31. N=11;
  32. d=3;
  33. M=(N-1)/2;
  34. for m=-M:M
  35.     for i=0:d
  36.         S(m+M+1,i+1)=m^i;
  37.     end
  38. end
复制代码



图4-8 matlab代码运行效果

4.2.4 模式切换及OLED显示程序

为了做到很好的交互体验,通过三个按键K1、K2、K3负责对系统的工作状态进行控制,同时使用三个LED灯LED1、LED2、LED3来显示系统的工作状态。K1负责切换系统的工作模式,当按下K1时通过改变标志位MODE_FLAG的值可以实现系统在上位机模式与本地模式间切换,同时通过LED1与LED2的亮灭来显示状态,当系统工作在上位机模式是只需要进行数据的采样以及滤波,完成后将数据发送即可;当系统工作在本地模式时通过KEY2来实现心电信号检测的开始,按下KEY1检测开始,系统进行10000个参数的采样同时LED3亮,完成后LED3熄灭并将这些参数滤波并且以波形的方式显示在OLED显示模块上,K3是用于切换波形显示与心率,心率的计算方式与上位机使用的方式相同,在下文会详细介绍。OLED显示模块主要用来显示波形以及其他的参数,在OLED显示函数中主要用到个函数OLED_Clear()用于清除屏幕显示,OLED_ShowString()用于显示字符,OLED_ShowNum()用于显示数字,LCD_DrawLine()用于显示波形时进行两个点之间的画线操作,下图4-9详细的表述了系统切换模式时的工作原理。

图4-9 系统模式切换框图

4.3 上位机设计
4.3.1 串口数据接收程序
上位机为获取心电数据必须要使用某种数据传送方式,考虑到数据传送的速度以及其后期设备的扩展,使用UART串口是最好的选择,考虑到数据较多因此串口通信的波特率为115200,同时为了便于数据的解析定义了数据发送的数据包格式(#+0000&)其中首位和末位为校验位第二位为数据的正负,第三到第六位为数据,由于实际数据较小这里将数据放大1000倍传输,在STM32端将数据转换成数据包格式并且发送,上位机将数据接收并解析成有效的心电数据并显示。
上位机接收数据使用了WINFORM的SERIALPORT控件,该控件可以提供对串口的配置、打开关闭、以及数据的接收和发送。在串口接收数据时存在进程锁死的问题因此需要使用多线程来防止锁死,图4-10与图4-11分别描述了串口的配置界面与接收的数据。
串口接收函数:
  1. private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
  2. {
  3.             string serialReadString, text;
  4.             string DataSymb;
  5.             string DataMain;
  6.             serialReadString = serialPort1.ReadExisting();
  7.             this.textBoxData.Invoke
  8.                 (
  9.                 new MethodInvoker
  10.                     (
  11.                       delegate
  12.                       {
  13.                           try
  14.                           {
  15.                               this.textBoxData.AppendText(serialReadString);
  16.                               text = textBoxData.Text;
  17.                               if (text.Length > 1)
  18.                               {
  19.                                   if (DataEnable == true)
  20.                                   {
  21.                                       TextDataTotal += serialReadString;
  22.                                   }
  23.                                   else if(DataEnable == false)
  24.                                   {
  25.                                       //TextDataTotal = "";
  26.                                   }
  27.                               }
  28.                           }
  29.                           catch { }
  30.                       }
  31.                     )
  32.                 );
  33. }
复制代码


图4-10串口配置界面
图4-11串口接收数据

4.3.2 波形显示控件设计
心电数据最直观的体现方式便通过波形图显示出来,波形图可以很直观的将心电状态每一个区域的特点体现出来,在上位机中使用chart控件来显示波形,该控件的原理是读取一个链表中的数据并显示,通过对串口上传获得的数据进行解析把解析后的数据存入链表这样便可以做到波形显示,同时该控件还可以实现波形的放大、拖动、实时显示等多种功能。由于需要显示心电数据在SG平滑滤波前后的波形特征因此需要两条波形同时显示。从图中4-12可以看出波形显示达到了很好地效果。
波形显示的过程程序:
  1. public List<float> x1 = new List<float>();              //新建四个链表用于存两条
  2. public List<float> y1 = new List<float>();              //波形的数据
  3. public List<float> x2 = new List<float>();
  4. public List<float> y2 = new List<float>();
  5. x1.Clear();                                             //链表初始化
  6. y1.Clear();
  7. x2.Clear();
  8. y2.Clear();
  9. zGraph1.f_ClearAllPix();
  10. zGraph1.f_reXY();
  11. zGraph1.f_InitMode(Pengpai.UI.ZGraph.GraphStyle.DefaultMoveMode);
  12. zGraph1.f_LoadOnePix(x1, y1, Color.Red, 2);
  13. zGraph1.f_AddPix(x2, y2, Color.Blue, 2);
  14. for (int i = 0; i < DataCalcTime; i++)                        //循环显示
  15. {
  16.     DrawLineSG(i, EcgDataNew[ i]);
  17. }
复制代码


图4-12波形显示效果

4.3.3 数据处理及保存
1. SG平滑滤波
SG平滑滤波:Savitzky-Golay滤波器(通常简称为S-G滤波器)最初由Savitzky和Golay于1964年提出,发表于Analytical Chemistry 杂志。之后被广泛地运用于数据流平滑除噪,是一种在时域内基于局域多项式最小二乘法拟合的滤波方法。这种滤波器最大的特点在于在滤除噪声的同时可以确保信号的形状、宽度不变。对IIR滤波后的数据进行SG平滑处理。在本设计中硬件端发送的数据虽然经过了0.5HZ高通滤波和50HZ陷波但是信号还是存在一定的毛刺,为了得到平滑的信号需要对信号进行平滑滤波。在本设计中使用长度为11阶数为5的SG平滑滤波,滤波器需要先在Matlab平台上面进行仿真,通过对样本数据的处理,可以验证SG平滑算法的效果,再将其在上位机上实现。
C#代码:
  1. _Matrix_Calc m_c = new _Matrix_Calc();
  2. _Matrix s = new _Matrix(N, D + 1);
  3. _Matrix s_tran = new _Matrix(D + 1, N);
  4. _Matrix F = new _Matrix(4, 4);
  5. _Matrix F_inv = new _Matrix(4, 4);
  6. _Matrix b1 = new _Matrix(N, 4);
  7. _Matrix b2 = new _Matrix(N, N);
  8. s.init_matrix();
  9. s_tran.init_matrix();
  10. F.init_matrix();
  11. F_inv.init_matrix();
  12. b1.init_matrix();
  13. b2.init_matrix();
  14. for (m = -M; m <= M; m++)           //得到矩阵s
  15. {
  16. for (i = 0; i <= D; i++)
  17. {
  18.          s.write(m + M, i, (float)Math.Pow(m, i));
  19. }
  20. }
  21. m_c.transpos(ref s,ref s_tran);
  22. m_c.multiply(ref s_tran, ref s, ref F);
  23. m_c.inverse(ref F, ref F_inv);
  24. m_c.multiply(ref s, ref F_inv , ref b1);
  25. m_c.multiply(ref b1, ref s_tran , ref b2);
  26. for (int i = 0; i <= M; i++)        //第0到M,一共M+1个点
  27. {
  28.        for (int j = 0; j < N; j++)
  29.        EcgDataNew[ i] = EcgDataNew[ i] + B.read(j, i) * EcgDataOld[j];
  30. }
  31. for (int i = M + 1; i <= L - M - 2; i++)  //第M+1到L-M-2,一共L-2M-2个点
  32. {
  33.         for (int j = 0; j < N; j++)
  34.         EcgDataNew[ i] = EcgDataNew[ i] + B.read(j, M) * EcgDataOld[i - M + j - 1];
  35.   }
  36. for (int i = 0; i <= M; i++)       //第L-M-1到L-1个点,一共M+1个点
  37. {
  38.         for (int j = 0; j < N; j++)
  39.         EcgDataNew[L - M + i - 1] = EcgDataNew[L - M + i - 1] + B.read(j, M + i) * EcgDataOld[L - N + j];
  40. }
复制代码


下图为SG平滑滤波效果,通过图4-13可以清晰的发现滤波前测试信号上拥有很多毛刺但是通过滤波毛刺基本消失波形达到了理想的效果。

图4-13红色为原始信号蓝色为滤波后的信号

2. 心率计算
心率是通过人体心电信号可以得出的一项重要的数据,在MCU以及上位机对数据进行综合处理之后得到了可以用于分析的心电波形数据,在心率的计算上使用了动态取阈值的方法,通过对一段采样数据做分段取极大极小值后再对该数据取平均后得到的数据为心率的平均极值,然后取平均极值的3/5作为该采样数据的阈值,分别为最小阈和最大阈,再分别对该段数据取最大最小值,用阈值与最大最小数据对比做对比,当最大值大于最大阈后继续判断如果此时最小值小于最小阈计数+1,同时最大最小值清零,判断继续直到将整段数据遍历一遍。这样就能得到该段数据的心跳次数。由图4-14可以看出在12S内程序计算出的心率次数与实际的心率次数相同。
心率计算函数:
  1. private void GetBMP()
  2. {
  3.             float bmpnummax = 0;
  4.             float bmpnummin = 0;
  5.             float bmpavemax = 0;
  6.             float bmpavemin = 0;
  7.             float bmpfamax = 0;
  8.             float bmpfamin = 0;
  9.             bool tipmax = true;
  10.             bool tipmin = true;
  11.             int BMP = 0;
  12.             for (int i = 0; i < txtline; i++)
  13.             {
  14.                 if (EcgTxtDataNew[ i] >= bmpnummax)
  15.                     bmpnummax = EcgTxtDataNew[ i];
  16.                 if (EcgTxtDataNew[ i] <= bmpnummin)
  17.                     bmpnummin = EcgTxtDataNew[ i];
  18.             }
  19.                         bmpmax.Text = bmpnummax.ToString();
  20.             bmpmin.Text = bmpnummin.ToString();
  21.             bmpfamax = bmpnummax * 3 / 5;
  22.             bmpfamin = bmpnummin * 3 / 5;
  23.             bmpnummax = 0;
  24.             bmpnummin = 0;         
  25.             for (int i = 0; i < txtline; i++)
  26.             {
  27.                 if (EcgTxtDataNew[ i] >= bmpnummax)// && tipmax == true)
  28.                 {
  29.                     bmpnummax = EcgTxtDataNew[ i];
  30.                 }
  31.                 if (EcgTxtDataNew[ i] <= bmpnummin)//&& tipmin == true)
  32.                 {
  33.                     bmpnummin = EcgTxtDataNew[ i];
  34.                 }
  35.                 if (bmpnummax > bmpfamax)
  36.                 {
  37.                     if(EcgTxtDataNew[ i]<bmpfamin)
  38.                     {
  39.                         BMP++;
  40.                         bmpnummax = 0;
  41.                         bmpnummin = 0;
  42.                     }
  43.                 }
  44.             }
  45.             BMP = (int)(60 * (float)BMP / ((float)txtline * 0.004));
  46.             HeartBit.Text = BMP.ToString();
  47. }
复制代码


图4-14 12s测试数据得到的心率

3. 数据保存与读取
在上位机上有一个按键专门给用于对数据进行保存,当点击该数据后可以将该数据存储在以保存时间命名的txt文件中,同时,在对应的文件读取控件中添加该文件以便于今后的读取。下图中点击保存按键可以将当前的波形保存为txt文件同样点击菜单栏中的读取文件按钮可以将波形再次读取出来这样有利于后期对数据的分析。如图4-15中可以通过数据菜单中的保存与读取按钮来进行数据存储操作。
图4-15数据保存与读取功能
评测与结论
5.1 硬件设计制作与系统调试
在完成对心电信号采集系统的综合设计论证之后便开始了硬件的设计过程,首先需要设计的是AD8232芯片和他的外围电路,通过参考官方的文档在官方的推荐电路的基础上完成了本设计模拟前端模块的设计。
在完成模拟前端的设计以及制作后开始了对模拟前端效果的测试,但是在初次的几次测试中效果很不理想,有大量的干扰导致心电信号并不能很好的体现出来,在综合分析了多种可能情况后的出的结论为:1.测试电路搭建太简陋使用了较多杜邦线2.没有使用数字滤波。在检查了原因后我对测试电路做出了改进。在通过硬件改进以及数字滤波器的使用后,心电信号的到了改善,从图5-1与图5-2中可以看出软件滤波的效果。

图5-1 AD8232模块原始信号

图5-2软件滤波后的信号

在硬件的设计过程中还有很重要的一部分就是对MCU的选择,由于我在长期的学习过程中对STM32比较熟悉以及STM32在硬件性能上有着较大的优势。因此我选用我比较熟悉的STM32F103VET6来设计心电信号采集系统,STM32的硬件直接选用了最小系统版,由于要使用AD和串口因此主要使用了串口1的PA9、PA10和AD的PC0,PA9,PA10与蓝牙模块的TX、RX连接PX0与模拟前端的OUT口连接。
在电源上由于STM32最小系统板上有AMS1117用于5V-3.3V所以电源的话采用5V即可,考虑到便携性的要求我使用3.7V锂电池通过升压模块将电压提升到5V供硬件系统使用。在早期没有使用蓝牙模块进行无线信号传输那时使用的是usb线连接来传输数据,当时接收到的数据中总是有一个干扰信号始终无法去除,在多方资料的查找过程成中我发现对于医疗设备来说USB电源是一个很大的干扰源,必须进行隔离,但是由于后期使用锂电池供电并不会产生USB干扰这个问题也被锂电池的使用解决了,对比图5-3与图5-4的波形来看使用USB电源不仅给信号带来了一个干扰波形还使得信号产生了较大的基线漂移。
图5-3 使用USB电源的心电波形
图5-4 隔离USB电源的心电波形
5.2 软件设计与调试
软件的设计主要分为两个部分,主要是STM32端的程序编写以及上位机的编写,STM32程序编写主要有AD采样,数字滤波,串口数据发送这几个方面,AD采样由于之前做过类似项目直接使用当时的程序即可,但需在采样率上要做相关调整,STM32负责的重点为数字滤波器的设计,这一部分在第三部分程序设计中已经做了详细的讲解。在数字滤波器过程中还是出现各种问题,主要有前期由于没有考虑到使用MATLAB仿真导致在滤波器的参数上设定的并不是很到位。之后通过多次调整才完成了滤波器的设计。其次STM32还需要完成与上位机的通信在数据包的设定上也要做好校验以防止数据丢失。
上位机在实现过程中由于之前有过相关的设计经验并没有遇到太大的困难,上位机的完成是在逐步添加功能的过程中进行的最初仅仅实现了串口数据的接收功能,然后在此基础上实现了多种功能,最终上位机实现了一键自动心率检测,数据保存,以及各种逐步测试功能,自动检测功能为最后添加的功能,设计初期由于需要测试并没有考虑到心电信号测量的简便,在设计接近完成后发现这个功能尤其重要。图5-5为上位机最终完成后的界面。
图5-5上位机最终完成界面

5.3 系统综合调试
综合测试实在软硬件完成并且做过相关测试的前提下进行的,测试的第一步为保证数据能否接收,在上位机模式中使用串口调试助手直接接收数据并且使用MATLAB读取数据确认数据的正确性,从下图的结果来看硬件部分可以将正常的将数据通过串口发送并且从波形中不难看出这是正常的心电信号,图5-6为MATLAB显示接收到的数据。

图5-6 MATLAB显示接收到的数据
在完成了最基础的数据接收及其准确性验证后,开始对上位机的各项功能进行验证,首先要保证上位机能准确的将数据包解析出来并且通过波形显示控件准确的将波形显示出来,下图为最初版的上位机对数据接收以及显示的验证。上位机上的测量时间为测试时间但是在实际的心率计算过程中并未使用该数据作为标准,考虑到数据发送的延迟以及该时间的准确性,通过采样率作为标准来计算心率最为准确。由图5-7可知系统可以正常的读取硬件发送的心率波形数据。
图5-7上位机读取心电数据
其次是对SG平滑滤波效果的验证,通过串口模拟器在电脑上虚拟出两个模拟串口将上位机与串口调试助手相连,通过串口调试助手产生一组随机数据发送给上位机,以此来验证SG平滑滤波的效果,通过验证平滑滤波的效果是符合要求的。由图5-8可以看出通过平滑滤波处理后的波形比原波形流畅许多。
图5-8 SG平滑滤波效果
完成以上两部分的测试,便可以正式对上位机的核心功信号采集以及数据分析保存功能进行测试,波形的测试标准为上位机处理过后的波形可以清晰的显示人体的心电信号信号干扰较小可以准确的分析出心电信号的特征区域,从图5-9与图5-10中测试信号已经可以清楚的显示它的心电信号的基本波形包括P波、QRS波群和T波。
图5-9标准心电信号
图5-10采集获得的心电信号
在本地模式中,需要测试硬件端是否能在脱离上位机的情况下完成心率的测量以及波形的显示,通过按键K1可以将系统在本地模式与上位机模式间进行切换,在本地模式中按K2进行心率测量,在完成设计后,首先多按键的功能进行验证,在确定系统可以正常进行模式切换并且LED灯可以正确显示不同的状态后开始验证系统是否能够测量心率,在按下K2时系统会进行10000次采样完成后将波形显示出来,在测试初期经常遇到波形显示到一半后系统卡死经过多次检验发现是由于数据显示位置超出了OLED显示范围导致出错,经过改正后显示正常,硬件端的心率计算方式和上位机相同,在硬件端也能正常工作,下图5-11中的五张小图是OLED显示模块的不同状态下的界面,包括心电波形,心率以及心率状态是否正常。
图5-11 OLED显示模块界面


心率准确性的计算上由于没有标准的心率设备,但是可以通过自己摸着胸口来测量自己的心率,在测试中我通过三次1分钟心率直接摸胸口读取得到我的当前心率为每分钟72次,接下来使用心电信号采集系统来测量心率,由图5-12可知上位机得出的心率为每分钟72次符合之前的结果,当然在多次测量后心率此时误差在1-2次,符合设计的要求。通过多次测试还发现在心率测量前需要尽量保持穿着较少的衣物,最准确的测量方式为左右胸以及右腹,当然通过左右手以及右腿也能得到理想的波形但是此种方式得到的波形在穿着衣物时效果并不好尤其是冬天衣物摩擦会产生较大的干扰。
图5-12自动测量的测量结果
测量数据完成后点击上位机的保存按键可以将数据存储到本地,这样便于后期的提取分析,在多次测试后保存的文件可以顺利地读取。
完成以上几个步骤后系统的测试基本完成,之后需要通过多次的数据测试来保证系统的稳定性,同时也需要多位同学配合来测试心电信号。同时解决了一些细微错误比如上位机容易崩溃和stm32偶尔无法上传数据等问题后心电信号采集系统的设计总体完成。
表5-1 六人心律检测数据
姓名
测试心律
实际心律
徐XX
75
75
杨XX
69
70
曾XX
73
72
王XX
77
77
郑XX
70
70



单片机源程序如下:
  1. #include "stm32f10x.h"
  2. #include "usart1.h"
  3. #include "adc.h"
  4. #include "math.h"
  5. #include "change.h"
  6. #include "oled.h"
  7. #include "bsp_led.h"
  8. #include "bsp_key.h"

  9. extern __IO u16 BUF1[100];  //缓冲1区
  10. extern __IO u16 BUF2[100];  //缓冲2区

  11. /*********************************状态标志位******************************/
  12. u8 MODE_FLAG=0;
  13. u8 MODE_FLAG_CAL=0;
  14. u8 MODE1_START_FLAG=0;
  15. u8 MODE1_FINISH_FLAG=0;
  16. u8 MODE1_CHANGE_FLAG=0;
  17. u8 MODE1_CHANGE_STATE_FLAG=0;

  18. /*********************************ECG数据*********************************/
  19. int ECG_DATA[10500];
  20. u16 ECG_NUM=0;
  21. u16 BMP_ECG=0;

  22. /**********************************DMA标志位******************************/
  23. #define BUF_Entrance 5
  24. #define BUF1_store_Finish 0
  25. #define BUF1_filter_Finish 1
  26. #define BUF2_store_Finish 2
  27. #define BUF2_filter_Finish 3

  28. extern __IO u8 Data_flag;
  29. extern __IO u8 DMA_flag;     

  30. #define L 100
  31. #define N 11
  32. #define M 5

  33. __IO u16 i=0,j;

  34. /*********************************滤波器数组*******************************/
  35. __IO float buf_N[N]={0};                   //SG平滑滤波数组
  36. __IO float IIR_Filter_Data[100];           //缓存数组,储存滤波之后的时域信号 float类型
  37. __IO float SG_Filter_Data[100];            //SG平滑之后数据
  38. __IO int   Send_int_Data[100];             //转换为int类型数据
  39. __IO char  Send_char_Data[5];              //发送数组


  40. __IO float w0[3]={0};
  41. __IO float w1[3]={0};

  42. __IO float x0=0,x1=0;
  43. __IO float y0=0,y1=0;



  44. /*******************************SG_Filter*****************************/
  45. //N=11 D=2
  46. const float B[N][N]={
  47.     {  0.580420,  0.377622,  0.209790,  0.076923, -0.020979, -0.083916, -0.111888, -0.104895, -0.062937,  0.013986,  0.125874},
  48.         {  0.377622,  0.278322,  0.193007,  0.121678,  0.064336,  0.020979, -0.008392, -0.023776, -0.025175, -0.012587,  0.013986},
  49.         {  0.209790,  0.193007,  0.173893,  0.152448,  0.128671,  0.102564,  0.074126,  0.043357,  0.010256, -0.025175, -0.062937},
  50.         {  0.076923,  0.121678,  0.152448,  0.169231,  0.172028,  0.160839,  0.135664,  0.096503,  0.043357, -0.023776, -0.104895},
  51.         { -0.020979,  0.064336,  0.128671,  0.172028,  0.194406,  0.195804,  0.176224,  0.135664,  0.074126, -0.008392, -0.111888},
  52.         { -0.083916,  0.020979,  0.102564,  0.160839,  0.195804,  0.207459,  0.195804,  0.160839,  0.102564,  0.020979, -0.083916},
  53.         { -0.111888, -0.008392,  0.074126,  0.135664,  0.176224,  0.195804,  0.194406,  0.172028,  0.128671,  0.064336, -0.020979},
  54.         { -0.104895, -0.023776,  0.043357,  0.096503,  0.135664,  0.160839,  0.172028,  0.169231,  0.152448,  0.121678,  0.076923},
  55.         { -0.062937, -0.025175,  0.010256,  0.043357,  0.074126,  0.102564,  0.128671,  0.152448,  0.173893,  0.193007,  0.209790},
  56.         {  0.013986, -0.012587, -0.025175, -0.023776, -0.008392,  0.020979,  0.064336,  0.121678,  0.193007,  0.278322,  0.377622},
  57.         {  0.125874,  0.013986, -0.062937, -0.104895, -0.111888, -0.083916, -0.020979,  0.076923,  0.209790,  0.377622,  0.580420}
  58. };



  59. /******************************50hz_IIR_Fnotch_Filter**************************/
  60. const float IIR_50Notch_B[3] = {
  61.     0.9023977442,
  62.          -0.5577124773,
  63.           0.9023977442
  64. };

  65. const float IIR_50Notch_A[3] = {
  66.           1,
  67.          -0.5577124773,
  68.           0.8047954884
  69. };



  70. /*****************************0.3Hz_IIR__High_Filter****************************/
  71. const float Gain=0.99468273;
  72. const float IIR_High_B[3]={
  73.          1,
  74.         -2,
  75.          1   
  76. };

  77. const float IIR_High_A[3]={
  78.          1,
  79.         -1.9833718117,
  80.          0.9839372834
  81. };



  82. void IIR_Reset()
  83. {
  84.    for(i=0;i<3;i--)
  85.           {
  86.      w0[i]=0;
  87.                  w1[i]=0;
  88.    }
  89.           x0=0;
  90.           y0=0;
  91.           x1=0;
  92.           y1=0;         
  93. }



  94. void DelayMs(unsigned int ms)
  95. {
  96.         unsigned int i;
  97.         while(ms--)
  98.         {
  99.                 for(i=0;i<7200;i++);
  100.         }
  101. }


  102. void First_filter_buf1(void)
  103. {
  104.          for(i=0;i<100;i++)
  105.          {
  106.                         x0=(float)BUF1[i]/4096*3.3-1.23;   // 读取转换的AD值        
  107.                         x0=-x0;   //电压数值翻转
  108.                  
  109.                         w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
  110.                         y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
  111.                         
  112.                         w1[0]=IIR_High_A[0]*y0-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2];
  113.                         y1=Gain*(IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2]);
  114.                         
  115.                         IIR_Filter_Data[i]=y1;         //缓存数组储存滤波之后的信号
  116.                         
  117.                         w0[2]=w0[1];
  118.                         w0[1]=w0[0];
  119.                         w1[2]=w1[1];
  120.                         w1[1]=w1[0];            
  121.         }
  122.         for(i=0;i<=M;i++)            //第0到M,一共M+1个点
  123.   {
  124.                 for(j=0;j<N;j++)
  125.                 {
  126.            SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][i]*IIR_Filter_Data[j];
  127.                 }
  128.   }
  129.         for(i=0;i<N;i++)            //将数据放入缓存数组中
  130.         {
  131.      buf_N[0]=buf_N[1];
  132.                  buf_N[1]=buf_N[2];
  133.                  buf_N[2]=buf_N[3];
  134.                  buf_N[3]=buf_N[4];
  135.                  buf_N[4]=buf_N[5];
  136.                  buf_N[5]=buf_N[6];
  137.                  buf_N[6]=buf_N[7];
  138.                  buf_N[7]=buf_N[8];
  139.                  buf_N[8]=buf_N[9];      //数据移位
  140.                  buf_N[9]=buf_N[10];
  141.            buf_N[10]=IIR_Filter_Data[i];        
  142.   }
  143.         for(i=M+1;i<L-M;i++)            //第M+1到L-1,一共L-M-1个点
  144.   {
  145.                
  146.     for(j=0;j<N;j++)
  147.                 {
  148.       SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][M]*buf_N[j];
  149.                 }
  150.                 buf_N[0]=buf_N[1];
  151.                 buf_N[1]=buf_N[2];
  152.                 buf_N[2]=buf_N[3];
  153.                 buf_N[3]=buf_N[4];
  154.                 buf_N[4]=buf_N[5];
  155.                 buf_N[5]=buf_N[6];
  156.                 buf_N[6]=buf_N[7];
  157.                 buf_N[7]=buf_N[8];
  158.                 buf_N[8]=buf_N[9];     //数据移位
  159.                 buf_N[9]=buf_N[10];
  160.                 buf_N[10]=IIR_Filter_Data[i+M];
  161.         }
  162. }        


  163. void Else_filter_buf1(void)
  164. {
  165.           for(i=0;i<100;i++)
  166.          {
  167.                         x0=(float)BUF1[i]/4096*3.3-1.23;   // 读取转换的AD值        
  168.                         x0=-x0;   //电压数值翻转
  169.                         
  170.                         w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
  171.                         y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
  172.                         
  173.                         w1[0]=IIR_High_A[0]*y0-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2];
  174.                         y1=Gain*(IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2]);
  175.                         
  176.                         IIR_Filter_Data[i]=y1;         //缓存数组储存滤波之后的信号
  177.                         
  178.                         w0[2]=w0[1];
  179.                         w0[1]=w0[0];
  180.                         w1[2]=w1[1];
  181.                         w1[1]=w1[0];            
  182.          }
  183.         for(i=0;i<L;i++)            //第0到M,一共M+1个点
  184.   {
  185.                   buf_N[0]=buf_N[1];
  186.                   buf_N[1]=buf_N[2];
  187.                   buf_N[2]=buf_N[3];
  188.                   buf_N[3]=buf_N[4];
  189.                   buf_N[4]=buf_N[5];
  190.                   buf_N[5]=buf_N[6];
  191.                   buf_N[6]=buf_N[7];
  192.                   buf_N[7]=buf_N[8];
  193.                   buf_N[8]=buf_N[9];    //数据移位
  194.                   buf_N[9]=buf_N[10];
  195.             buf_N[10]=IIR_Filter_Data[i];        
  196.       for(j=0;j<N;j++)
  197.                   {
  198.         SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][M]*buf_N[j];
  199.                   }
  200.         }
  201. }        

  202. void First_filter_buf2(void)
  203. {
  204.          for(i=0;i<100;i++)
  205.          {
  206.                         x0=(float)BUF2[i]/4096*3.3-1.23;   // 读取转换的AD值        
  207.                         x0=-x0;   //电压数值翻转
  208.                  
  209.                         w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
  210.                         y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
  211.                         
  212.                         w1[0]=IIR_High_A[0]*y0-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2];
  213.                         y1=Gain*(IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2]);
  214.                         
  215.                         IIR_Filter_Data[i]=y1;         //缓存数组储存滤波之后的信号
  216.                         
  217.                         w0[2]=w0[1];
  218.                         w0[1]=w0[0];
  219.                         w1[2]=w1[1];
  220.                         w1[1]=w1[0];            
  221.         }
  222.         for(i=0;i<=M;i++)            //第0到M,一共M+1个点
  223.   {
  224.                 for(j=0;j<N;j++)
  225.                 {
  226.       SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][i]*IIR_Filter_Data[j];
  227.                 }
  228.   }
  229.         for(i=0;i<N;i++)            //将数据放入缓存数组中
  230.         {
  231.      buf_N[0]=buf_N[1];
  232.                  buf_N[1]=buf_N[2];
  233.                  buf_N[2]=buf_N[3];
  234.                  buf_N[3]=buf_N[4];
  235.                  buf_N[4]=buf_N[5];
  236.                  buf_N[5]=buf_N[6];
  237.                  buf_N[6]=buf_N[7];
  238.                  buf_N[7]=buf_N[8];
  239.                  buf_N[8]=buf_N[9];      //数据移位
  240.                  buf_N[9]=buf_N[10];
  241.            buf_N[10]=IIR_Filter_Data[i];        
  242.   }
  243.         for(i=M+1;i<L-M;i++)            //第M+1到L-1,一共L-M-1个点
  244.   {
  245.                
  246.     for(j=0;j<N;j++)
  247.                 {
  248.       SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][M]*buf_N[j];
  249.                 }
  250.                 buf_N[0]=buf_N[1];
  251.                 buf_N[1]=buf_N[2];
  252.                 buf_N[2]=buf_N[3];
  253.                 buf_N[3]=buf_N[4];
  254.                 buf_N[4]=buf_N[5];
  255.                 buf_N[5]=buf_N[6];
  256.                 buf_N[6]=buf_N[7];
  257.                 buf_N[7]=buf_N[8];
  258.                 buf_N[8]=buf_N[9];     //数据移位
  259.                 buf_N[9]=buf_N[10];
  260.                 buf_N[10]=IIR_Filter_Data[i+M];
  261.         }
  262. }        


  263. void Else_filter_buf2(void)
  264. {
  265.           for(i=0;i<100;i++)
  266.          {
  267.                         x0=(float)BUF2[i]/4096*3.3-1.23;   // 读取转换的AD值        
  268.                         x0=-x0;   //电压数值翻转
  269.                         
  270.                         w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
  271.                         y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
  272.                         
  273.                         w1[0]=IIR_High_A[0]*y0-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2];
  274.                         y1=Gain*(IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2]);
  275.                         
  276.                         IIR_Filter_Data[i]=y1;         //缓存数组储存滤波之后的信号
  277.                         
  278.                         w0[2]=w0[1];
  279.                         w0[1]=w0[0];
  280.                         w1[2]=w1[1];
  281.                         w1[1]=w1[0];            
  282.          }
  283.         for(i=0;i<L;i++)            //第0到M,一共M+1个点
  284.   {
  285.                   buf_N[0]=buf_N[1];
  286.                   buf_N[1]=buf_N[2];
  287.                   buf_N[2]=buf_N[3];
  288.                   buf_N[3]=buf_N[4];
  289.                   buf_N[4]=buf_N[5];
  290.                   buf_N[5]=buf_N[6];
  291.                   buf_N[6]=buf_N[7];
  292.                   buf_N[7]=buf_N[8];
  293.                   buf_N[8]=buf_N[9];    //数据移位
  294.                   buf_N[9]=buf_N[10];
  295.             buf_N[10]=IIR_Filter_Data[i];        
  296.       for(j=0;j<N;j++)
  297.                   {
  298.         SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][M]*buf_N[j];
  299.                   }
  300.         }
  301. }        

  302. void send_data(unsigned char ascii_code)
  303. {
  304.   USART_SendData(USART1,ascii_code);
  305.   while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){}                                                               
  306. }

  307. void getBMP(int length)
  308. {
  309.         float bmpnummax = 0;
  310.     float bmpnummin = 0;
  311.     float bmpavemax = 0;
  312.     float bmpavemin = 0;
  313.     float bmpfamax = 0;
  314.     float bmpfamin = 0;
  315.         float BMP=0;
  316.         
  317.         for (i = 0; i < length; i++)
  318.     {
  319.                 if (ECG_DATA[i] >= bmpavemax)
  320.                     bmpavemax = ECG_DATA[i];
  321.                 if (ECG_DATA[i] <= bmpavemin)
  322.                     bmpavemin = ECG_DATA[i];
  323.                 if (i == length / 5)
  324.                 {
  325.                     bmpnummax = bmpnummax + bmpavemax / 5;
  326.                     bmpnummin = bmpnummin + bmpavemin / 5;
  327.                     bmpavemax = 0;
  328.                     bmpavemin = 0;
  329.                 }
  330.                 if (i == length / 5 * 2)
  331.                 {
  332.                     bmpnummax = bmpnummax + bmpavemax / 5;
  333.                     bmpnummin = bmpnummin + bmpavemin / 5;
  334.                     bmpavemax = 0;
  335.                     bmpavemin = 0;
  336.                 }
  337.                 if (i == length / 5 * 3)
  338.                 {
  339.                     bmpnummax = bmpnummax + bmpavemax / 5;
  340.                     bmpnummin = bmpnummin + bmpavemin / 5;
  341.                     bmpavemax = 0;
  342.                     bmpavemin = 0;
  343.                 }
  344.                 if (i == length / 5 * 4)
  345.                 {
  346.                     bmpnummax = bmpnummax + bmpavemax / 5;
  347.                     bmpnummin = bmpnummin + bmpavemin / 5;
  348.                     bmpavemax = 0;
  349.                     bmpavemin = 0;
  350.                 }
  351.                 if (i == length - 1)
  352.                 {
  353.                     bmpnummax = bmpnummax + bmpavemax / 5;
  354.                     bmpnummin = bmpnummin + bmpavemin / 5;
  355.                     bmpavemax = 0;
  356.                     bmpavemin = 0;
  357.                 }
  358.    
  359.         }
  360.             bmpfamax = bmpnummax * 3 / 5;
  361.             bmpfamin = bmpnummin * 3 / 5;
  362.             bmpnummax = 0;
  363.             bmpnummin = 0;

  364.             for (i = 0; i < length; i++)
  365.             {
  366.                 if (ECG_DATA[i] >= bmpnummax)// && tipmax == true)
  367.                 {
  368.                     bmpnummax = ECG_DATA[i];
  369.                 }
  370.                 if (ECG_DATA[i] <= bmpnummin)//&& tipmin == true)
  371.                 {
  372.                     bmpnummin = ECG_DATA[i];
  373.                 }
  374.                 if (bmpnummax > bmpfamax)
  375.                 {
  376.                     if (ECG_DATA[i] < bmpfamin)
  377.                     {
  378.                         BMP++;
  379.                         bmpnummax = 0;
  380.                         bmpnummin = 0;
  381.                     }
  382.                 }
  383.             }
  384.                         BMP = (60 * BMP / ((float)length * 0.004));
  385.                         BMP_ECG=BMP;
  386. //            BMP = (int)(60 * (float)BMP / ((float)length * 0.004));
  387. }

  388. int main(void)
  389. {        
  390.         u8 ID=1;
  391.         Data_flag=0;
  392.         USART1_Config();
  393.         
  394.         OLED_Init();                        //初始化OLED
  395.         
  396.         ADC1_Init();
  397.         DMA_flag=BUF_Entrance;    //初始化DMA缓冲区切换标识位
  398.         IIR_Reset();
  399.          
  400.         OLED_Clear();
  401.         
  402.         /* config the led */
  403.         LED_GPIO_Config();
  404.         LED1_OFF;
  405.         LED2_OFF;
  406.         LED3_OFF;
  407.         /*config key*/
  408.         Key_GPIO_Config();        
  409.         
  410.         OLED_ShowString(24,0,ASCII16x16,"ECG SYSTEM");
  411.         OLED_ShowString(12,3,ASCII16x16,"K1:LOCAL MODE");
  412.         OLED_ShowString(12,5,ASCII16x16,"K1:UPPER MODE");
  413.         
  414.         while(1)
  415.         {
  416. //                if( Key_Scan(GPIOA,GPIO_Pin_1) == KEY_ON  )
  417. //                {
  418. //                                LED1_TOGGLE;                        
  419. //                }
  420.                
  421.                 if( Key_Scan(GPIOA,GPIO_Pin_0) == KEY_ON  )
  422.                 {
  423.                         MODE_FLAG=~MODE_FLAG;
  424.                         MODE1_CHANGE_FLAG =0;
  425.                         MODE1_FINISH_FLAG=0;
  426.                         if(MODE_FLAG==0)
  427.                         {
  428.                                 /*LED1反转*/
  429.                                 LED1_ON;
  430.                                 LED2_OFF;
  431.                                 LED3_OFF;
  432.                                 MODE_FLAG_CAL=1;
  433.                                 OLED_Clear();
  434.                                 OLED_ShowString(24,0,ASCII16x16,"LOCAL MODE");
  435.                                 OLED_ShowString(32,4,ASCII16x16,"K2:START");
  436.                                 MODE1_START_FLAG =0;
  437.                         }
  438.                         else
  439.             {
  440.                                 /*LED1反转*/
  441.                                 LED1_OFF;
  442.                                 LED2_ON;
  443.                                 LED3_OFF;
  444.                                 MODE_FLAG_CAL=2;
  445.                                 OLED_Clear();
  446.                                 OLED_ShowString(24,0,ASCII16x16,"UPPER MODE");
  447.                                 OLED_ShowString(12,4,ASCII16x16,"CONNECTING...");
  448.                                 MODE1_START_FLAG =0;
  449.                         }
  450.                 }
  451.                
  452.                 if(MODE_FLAG_CAL==1)
  453.                 {
  454.                         if( Key_Scan(GPIOC,GPIO_Pin_13) == KEY_ON  )
  455.                         {
  456.                                 MODE1_START_FLAG =1;
  457.                                 MODE1_FINISH_FLAG=0;
  458.                                 LED3_ON;
  459.                                 OLED_Clear();
  460.                        OLED_ShowString(8,2,ASCII16x16,"RECEIVING DATA");        
  461.                 OLED_ShowString(24,4,ASCII16x16,"WAITING...");                                
  462.                         }
  463.                         
  464. //                        if(ECG_NUM >8000&&MODE1_START_FLAG ==1)
  465. //                        {
  466. //                           send_data('A');
  467. //                        }
  468.                         
  469.                         if(MODE1_START_FLAG ==1)
  470.                         {
  471.                                                         /****************获取标志位,1管道储存完成,处理管道1数据******************/
  472.                                   if(DMA_flag==BUF1_store_Finish)            
  473.                                   {
  474.                                           if(ID==1)
  475.                                           {                                                         
  476.                                                  First_filter_buf1();       //处理缓冲区1的数据                        
  477.                                                  for(i=0;i<L-M;i++)
  478.                                                  {
  479.                                                         Send_int_Data[i]=(int)(SG_Filter_Data[i]*1000);
  480.                                                         int_to_char(Send_int_Data[i],Send_char_Data);
  481.                                                          
  482.                                                         ECG_DATA [ECG_NUM]=Send_int_Data[i];
  483.                                                         ECG_NUM++;        
  484.                             if(ECG_NUM>10000)
  485.                             {
  486.                                                                 MODE1_START_FLAG =0;
  487.                                                         }                                                               
  488.                                                   }                                                  
  489.                                                for(i=10;i<100;i++)           //清零操作
  490.                                                    {
  491.                                                            IIR_Filter_Data[i]=0;
  492.                                                            SG_Filter_Data[i]=0;
  493.                                                 }
  494.                                                    DMA_flag=BUF1_filter_Finish;
  495.                                                    ID++;
  496.                                             }
  497.                                                 else
  498.                                                 {
  499.                                                    Else_filter_buf1();       //处理缓冲区1的数据                        
  500.                                                    for(i=0;i<L;i++)
  501.                                                    {        
  502.                                                            Send_int_Data[i]=(int)(SG_Filter_Data[i]*1000);
  503.                                                            int_to_char(Send_int_Data[i],Send_char_Data);
  504.                                                            
  505.                                                            ECG_DATA [ECG_NUM]=Send_int_Data[i];
  506.                                                            ECG_NUM++;         
  507.                                if(ECG_NUM>10000)
  508.                                {
  509.                                                                   MODE1_START_FLAG =0;
  510.                                                            }                                                           
  511.                                                         }           
  512.                                                 for(i=0;i<L;i++)      //清零操作
  513.                                                     {
  514.                                                             IIR_Filter_Data[i]=0;
  515.                                                                 SG_Filter_Data[i]=0;
  516.                                                 }
  517.                                                     DMA_flag=BUF1_filter_Finish;
  518.                                                         ID++;   
  519.                                         }
  520.                                  }
  521.                            /****************获取标志位,2管道储存完成,处理管道2数据******************/         
  522.                                  if(DMA_flag==BUF2_store_Finish)        //缓冲区2存储满
  523.                                  {
  524.                                          if(ID==1)
  525.                                          {
  526.                                                  First_filter_buf2();                //处理缓冲区2的数据                                                
  527.                                                  for(i=0;i<L;i++)
  528.                                                  {        
  529.                                                          Send_int_Data[i]=(int)(SG_Filter_Data[i]*1000);
  530.                                                          int_to_char(Send_int_Data[i],Send_char_Data);
  531.                                                          
  532.                                                          ECG_DATA [ECG_NUM]=Send_int_Data[i];
  533.                                                          ECG_NUM++;   
  534.                              if(ECG_NUM>10000)
  535.                              {
  536.                                                                 MODE1_START_FLAG =0;
  537.                                                          }                                                         
  538.                                                   }                        
  539.                                                   for(i=0;i<L;i++)      //清零操作
  540.                                                   {
  541.                                                           IIR_Filter_Data[i]=0;
  542.                                                       SG_Filter_Data[i]=0;
  543.                                                   }
  544.                                                   DMA_flag=BUF2_filter_Finish;        
  545.                                                   ID++;
  546.                                       }
  547.                                           else
  548.                                           {
  549.                                                    Else_filter_buf2();                //处理缓冲区2的数                                                                          
  550.                                                    for(i=0;i<100;i++)
  551.                                                    {        
  552.                                                            Send_int_Data[i]=(int)(SG_Filter_Data[i]*1000);                       
  553.                                                            int_to_char(Send_int_Data[i],Send_char_Data);        
  554.                                                          
  555.                                 ECG_DATA [ECG_NUM]=Send_int_Data[i];
  556.                                                             ECG_NUM++;        
  557.                                 if(ECG_NUM>10000)
  558.                                 {
  559.                                                                     MODE1_START_FLAG =0;
  560.                                                               }                                                           
  561.                                                          }                                       
  562.                                                 for(i=0;i<100;i++)      //清零操作
  563.                                                     {
  564.                                                            IIR_Filter_Data[i]=0;
  565.                                                    SG_Filter_Data[i]=0;
  566.                                                  }
  567.                                                 DMA_flag=BUF2_filter_Finish;        
  568.                                                         ID++;
  569.                                            }               
  570.                                   }
  571.                         }        
  572.                         
  573. /******************************显示波形************************************/
  574.             if(ECG_NUM>10000&&MODE1_START_FLAG ==0)
  575.                         {
  576.                                 OLED_Clear();
  577.                                 LED3_OFF;
  578.                                 
  579.                                 getBMP(10000);
  580.                                 
  581.                                 for(i=0;i<10000;i++)
  582.                                 {
  583.                                         ECG_DATA [i]=-(ECG_DATA [i]/32-32);
  584.                                         if(ECG_DATA [i]<0)
  585.                                                 ECG_DATA [i]=0;
  586.                                         else if(ECG_DATA [i]>63)
  587.                                                 ECG_DATA [i]=63;
  588.                                 }
  589.                                 
  590.                                 for(i=0;i<10000-512;i+=100)
  591.                                 {
  592.                                         OLED_Clear();
  593.                                         for(j=0;j<512;j++)
  594.                                         {
  595.                                                 LCD_DrawLine(j/4,ECG_DATA [j+i],(j+1)/4,ECG_DATA [j+i+1]);
  596.                                         }
  597.                                         delay_ms(50);                                
  598.                                 }
  599.                                 ECG_NUM=0;
  600.                                 MODE1_FINISH_FLAG=1;
  601.                                 MODE1_CHANGE_STATE_FLAG=0;
  602.                         }
  603.                         
  604.                         if( Key_Scan(GPIOA,GPIO_Pin_1) == KEY_ON&&MODE1_FINISH_FLAG ==1)
  605.                         {
  606.                                 MODE1_CHANGE_STATE_FLAG=~MODE1_CHANGE_STATE_FLAG;
  607.                 MODE1_CHANGE_FLAG =1;                                
  608.                         }
  609.                         
  610.                         if(MODE1_CHANGE_FLAG ==1&&MODE1_FINISH_FLAG==1)
  611.                         {
  612.                 MODE1_CHANGE_FLAG =0;
  613.                                 if(MODE1_CHANGE_STATE_FLAG==0)
  614.                                 {
  615.                                         for(i=0;i<10000-512;i+=100)
  616.                                         {
  617.                                                 OLED_Clear();
  618.                                                 for(j=0;j<512;j++)
  619.                                                 {
  620.                                                         LCD_DrawLine(j/4,ECG_DATA [j+i],(j+1)/4,ECG_DATA [j+i+1]);
  621.                                                 }
  622.                                                 delay_ms(50);                                
  623.                                         }
  624.                                 }
  625.                                 else
  626.                                 {
  627.                                         OLED_Clear();
  628.                                     OLED_ShowString(32-16,0,ASCII16x16,"ECG DATA");
  629.                                     OLED_ShowString(24-16,2,ASCII16x16,"NUM:");
  630.                                     OLED_ShowString(24-16,4,ASCII16x16,"STATE:");
  631. //                                        getBMP(10000);
  632.                                        
  633.                                         //OLED_ShowNum(24+32,2,BMP_ECG,4,16);
  634.                                        
  635.                                         if(BMP_ECG>400)
  636.                                         {
  637.                                                 OLED_ShowString(24+32-16,2,ASCII16x16,"ERROR");
  638.                                         }
  639.                                         else if(BMP_ECG<=400&&BMP_ECG>=0)
  640.                                         {
  641.                                                 OLED_ShowNum(24+32-16,2,BMP_ECG,4,16);
  642.                                         }
  643.                                        
  644.                                         if(BMP_ECG>50&&BMP_ECG<300)
  645.                                         {
  646.                                                 OLED_ShowString(24+48-16,4,ASCII16x16,"SAFE");
  647.                                         }
  648.                                         else if(BMP_ECG<=50&&BMP_ECG>=0)
  649.                                         {
  650.                                                 OLED_ShowString(24+48-16,4,ASCII16x16,"DANGERL");
  651.                                         }
  652.                                         else if(BMP_ECG>=300&&BMP_ECG<=400)
  653.                                         {
  654.                                                 OLED_ShowString(24+48-16,4,ASCII16x16,"DANGERH");
  655.                                         }
  656.                                     else if(BMP_ECG>400)
  657.                                         {
  658.                                                 OLED_ShowString(24+48-16,4,ASCII16x16,"ERROR");
  659.                                         }                                       
  660.                                         //OLED_ShowNum(u8 x,u8 y,u16 num,u8 len,u8 size);
  661.                                 }
  662.                         }
  663.                         
  664.                 }
  665.                
  666.                
  667.                
  668.                 if(MODE_FLAG_CAL==2)
  669.                 {
  670.                         /****************获取标志位,1管道储存完成,处理管道1数据******************/
  671.                   if(DMA_flag==BUF1_store_Finish)            
  672.           {
  673.                   if(ID==1)
  674.                   {
  675.                                                 
  676.                                   First_filter_buf1();       //处理缓冲区1的数据                                 
  677.                                   for(i=0;i<L-M;i++)
  678.                                   {
  679. ……………………

  680. …………限于本文篇幅 余下代码请从51黑下载附件…………
复制代码

所有资料51hei提供下载:
完整的word格式设计报告51黑下载地址:
基于stm32的无线蓝牙心电监护仪_论文.doc (3.99 MB, 下载次数: 267)
电路图.zip (144.54 KB, 下载次数: 267)
程序源码.zip (4.3 MB, 下载次数: 348)

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏18 分享淘帖 顶2 踩
回复

使用道具 举报

沙发
ID:402025 发表于 2018-9-25 10:05 | 只看该作者
单导心电的电路和源码
回复

使用道具 举报

板凳
ID:1 发表于 2018-9-26 03:42 | 只看该作者
好资料,51黑有你更精彩!!!
回复

使用道具 举报

地板
ID:44453 发表于 2018-12-5 11:40 | 只看该作者
看的好心动,可惜没有黑币了
回复

使用道具 举报

5#
ID:485969 发表于 2019-3-7 02:46 | 只看该作者
很有用!
回复

使用道具 举报

6#
ID:182535 发表于 2019-3-20 23:53 | 只看该作者
好文章
回复

使用道具 举报

7#
ID:499249 发表于 2019-3-27 16:21 | 只看该作者
写的好有逻辑,给我很多提示,太感谢啦
回复

使用道具 举报

8#
ID:444897 发表于 2019-5-1 22:34 | 只看该作者
2539871219 发表于 2019-3-27 16:21
写的好有逻辑,给我很多提示,太感谢啦

请问为什么这个上位机程序怎么打不开?我用的是vs 2017
回复

使用道具 举报

9#
ID:512514 发表于 2019-5-18 11:23 | 只看该作者
好资料,51黑有你更精彩!!!
回复

使用道具 举报

10#
ID:541521 发表于 2019-5-18 17:09 | 只看该作者
I  II  III 心电导联如何切换呢,电路不完整
回复

使用道具 举报

11#
ID:68875 发表于 2019-6-16 09:55 | 只看该作者
good topic
回复

使用道具 举报

12#
ID:255292 发表于 2019-12-4 14:08 | 只看该作者
上位机我用VS2013打不开?请教咋回事
回复

使用道具 举报

13#
ID:492919 发表于 2019-12-6 12:53 | 只看该作者
学习一下
回复

使用道具 举报

14#
ID:414712 发表于 2019-12-20 11:09 | 只看该作者
单导心电的电路和源码
回复

使用道具 举报

15#
ID:347034 发表于 2020-2-25 10:35 | 只看该作者
学习一下
回复

使用道具 举报

16#
ID:700537 发表于 2020-3-1 14:39 | 只看该作者
zhangkaijin 发表于 2019-5-1 22:34
请问为什么这个上位机程序怎么打不开?我用的是vs 2017

我也是,用2017打不开,请问你现在解决了吗?
回复

使用道具 举报

17#
ID:710769 发表于 2020-3-18 12:09 | 只看该作者
请问有没有单导联的设计
回复

使用道具 举报

18#
ID:661510 发表于 2020-3-18 15:18 | 只看该作者
好资料,学到了很多
回复

使用道具 举报

19#
ID:700459 发表于 2020-3-19 07:32 | 只看该作者
thank you so much
回复

使用道具 举报

20#
ID:604438 发表于 2021-4-28 10:24 | 只看该作者
卫佐 发表于 2020-3-1 14:39
我也是,用2017打不开,请问你现在解决了吗?

你好,问题解决了吗
回复

使用道具 举报

21#
ID:913561 发表于 2021-5-5 11:07 | 只看该作者
有测试数据的下载方式吗
回复

使用道具 举报

22#
ID:818895 发表于 2021-5-5 12:33 | 只看该作者
是不是比较实用。
回复

使用道具 举报

23#
ID:1072611 发表于 2023-4-18 22:30 | 只看该作者
值得收藏
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表