基于STM32的实时心率检测仪设计

发布者:ShiningSmile最新更新时间:2024-02-28 来源: elecfans关键字:STM32 手机看文章 扫描二维码
随时随地手机看文章

一、开发环境介绍

主控芯片STM32F103ZET6


代码编程软件: keil5

心率检测模块: PulseSensor

WIFI模块: ESP8266 --可选的。直接使用串口有线传输给上位机也可以。

上位机: C++(QT) 设计的。 支持PC机电脑、Android手机显示。

与上位机的传输协议: 支持串口传输、WIFI网络传输两种。 如果是PC就可以直接连接串口传输数据,如果不方便可以直接通过WIFI---TCP协议传输。

二、PulseSensor心率模块介绍

PulseSensor 是一款用于脉搏心率测量的光电反射式模拟传感器。

可以将其佩戴于手指、耳垂、手腕等处,通过杜邦线--导线将引脚连接到单片机,可将采集到的模拟信号传输给单片机,单片机配置ADC用来转换为数字信号,再通过单片机简单计算后就可以得到心率数值;为了方便联动健康管理系统,也方便自己了解自己的心率,可将脉搏波形通过串口、WIFI等方式上传到电脑、手机显示波形,然后根据提前配置的参数,结合算法确定是否正常。

PulseSensor 是一款开源硬件, 目前国外官网上已有其对应的单片机程序,也附带有对应的上位机Processing 程序, 比较适用于心率方面的科学研究和教学演示,也非常适合用于二次开发;上位机也可以自己开发,根据自己的需求定制,达到自己想要的功能。

传感器的接口一共 3 个,

其中标有S的为模拟信号输出线

标有+的为电源输入线(中间);

标有-的为地线。

总结一下:

S → 脉搏信号输出(要接单片机 AD 接口)

+ → 5v(或 3.3v)电源输入

- → GND 地

传感器的硬件参数介绍:

电路板直径: 16mm

电路板厚度: 1.2mm

LED 峰值波长: 515nm

供电电压: 3.3~5v

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

输出信号类型:模拟信号

信号放大倍数: 330 倍

输出信号大小: 0~VCC

电流大小: ~4ma(5v 下)

传统的测量方法介绍:

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

一是从心电信号中提取;

二是从测量血压时压力传感器测到的波动来计算脉率;

三是光电容积法。前两种方法提取信号都会限制病人的活动,如果长时间使用会增加病人生理和心理上的不舒适感。而光电容积法脉搏测量作为监护测量中最普遍的方法之一,其具有方法简单、佩戴方便、可靠性高等特点。

光电容积法的基本原理是利用人体组织在血管搏动时造成透光率不同来进行脉搏测量的。其使用的传感器由光源和光电变换器两部分组成,通过绑带或夹子固定在病人的手指或耳垂上。光源一般采用对动脉血中氧和血红蛋白有选择性的一定波长(500nm~700nm)的发光二极管。当光束透过人体外周血管,由于动脉搏动充血容积变化导致这束光的透光率发生改变,此时由光电变换器接收经人体组织反射的光线,转变为电信号并将其放大和输出。由于脉搏是随心脏的搏动而周期性变化的信号,动脉血管容积也周期性变化,因此光电变换器的电信号变化周期就是脉搏率。

根据相关文献和实验结果, 560nm 波长左右的波可以反映皮肤浅部微动脉信息,适合用来提取脉搏信号。

本传感器采用了峰值波长为 515nm 的绿光 LED,型号为 AM2520,而光接收器采用了 APDS-9008, 这是一款环境光感受器,感受峰值波长为 565nm,两者的峰值波长相近,灵敏度较高。此外,由于脉搏信号的频带一般在 0.05~200Hz 之间, 信号幅度均很小,一般在毫伏级水平,容易受到各种信号干扰。在传感器后面使用了低通滤波器和由运放 MCP6001 构成的放大器,将信号放大了 330 倍,同时采用分压电阻设置直流偏置电压为电源电压的 1/2,使放大后的信号可以很好地被单片机的 AD 采集到。

整个心率传感器的结构如下图:

基于STM32的实时心率检测仪设计

由于传感器使用的是固定倍数的放大器, 而人体生理信号是微弱信号,细微的差异会导致放大后的信号产生巨大的差别。 所以下图的示波器显示的波形只是理想情况下的波形,每个人的实际效果会略有区别。

基于STM32的实时心率检测仪设计

三、STM32的控制代码

STM32的采集代码比较简单,因为就只需要配置对应引脚的ADC功能采集即可。

可以采集10次,去掉最大值最小值取平均值,拿到最终结果再传递给上位机显示。


3.1 ADC的配置代码


/*

函数功能: 初始化ADC1

硬件连接: PA1  --ADC1的通道1

配置的模式:模拟输入

*/

void ADC1_Init(void)

{

   /*1. 配置GPIO口*/

  RCC->APB2ENR|=1<<2; //开启PA时钟

  GPIOA->CRL&=0xFFFFFF0F;

  GPIOA->CRL|=0x00000000;

  

  /*2. 配置ADC相关寄存器*/

  RCC->APB2ENR|=1<<9;//开启ADC1时钟

  

  RCC->APB2RSTR|=1<<9;   //开启ADC1复位时钟

  RCC->APB2RSTR&=~(1<<9);//关闭ADC1复位时钟

  

  RCC->CFGR&=~(0x3<<14); //清除ADC的时钟配置

  RCC->CFGR|=0x2<<14;    //配置6分频

  

  ADC1->CR2|=1<<20;      //开启外部事件转换

  ADC1->CR2|=0x7<<17;    //SW开关方式控制ADC转换(作为外部事件)


  ADC1->SMPR2|=0x7<<3;   //配置通道1的采样时间

  

  ADC1->CR2|=1<<0;//开启ADC并启动转换

  ADC1->CR2|=1<<3;//开启ADC校准初始化

  while(ADC1->CR2&1<<3){}//等待初始化完成

  ADC1->CR2|=1<<2;//开启ADC校准

  while(ADC1->CR2&1<<2){} //等待ADC校准完成   

}


/*

函数功能: 根据传入的通道编号获取一次该通道的ADC值

*/

u16 ADC1_GetData(u8 ch)

{

   ADC1->SQR3&=0xFFFFFFE0; //0xE0-->11100000 //清除原来的通道编号

   ADC1->SQR3|=ch<<0; //配置现在即将转换的通道号

   ADC1->CR2|=1<<22;  //开启一次ADC规则通道转换

   while(!(ADC1->SR&1<<1)){} //等待转换完成

   return ADC1->DR;  //读出ADC的结果值

}



3.2 ESP8266 WIFI 配置代码

#include "esp8266.h"


/*

函数功能:向ESP82668266发送命令

函数参数:

cmd:发送的命令字符串

ack:期待的应答结果,如果为空,则表示不需要等待应答

waittime:等待时间(单位:10ms)

返 回 值:

0,发送成功(得到了期待的应答结果)

         1,发送失败

*/

u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime)

{

u8 res=0; 

USART3_RX_STA=0;

USART3_RX_CNT=0;

UsartStringSend(USART3,cmd);//发送命令

if(ack&&waittime) //需要等待应答

{

while(--waittime) //等待倒计时

{

DelayMs(10);

if(USART3_RX_STA)//接收到期待的应答结果

{

if(ESP8266_CheckCmd(ack))

{

res=0;

//printf("cmd->ack:%s,%srn",cmd,(u8*)ack);

break;//得到有效数据 

}

USART3_RX_STA=0;

USART3_RX_CNT=0;

}

if(waittime==0)res=1; 

}

return res;

}



/*

函数功能:ESP8266发送命令后,检测接收到的应答

函数参数:str:期待的应答结果

返 回 值:0,没有得到期待的应答结果

其他,期待应答结果的位置(str的位置)

*/

u8* ESP8266_CheckCmd(u8 *str)

{

char *strx=0;

if(USART3_RX_STA)  //接收到一次数据了

USART3_RX_BUF[USART3_RX_CNT]=0;//添加结束符

strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否应答成功

//printf("RX=%s",USART3_RX_BUF);

}

return (u8*)strx;

}


/*

函数功能:向ESP8266发送指定数据

函数参数:

data:发送的数据(不需要添加回车)

ack:期待的应答结果,如果为空,则表示不需要等待应答

waittime:等待时间(单位:10ms)

返 回 值:0,发送成功(得到了期待的应答结果)luojian

*/

u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime)

{

u8 res=0; 

USART3_RX_STA=0;

UsartStringSend(USART3,data);//发送数据

if(ack&&waittime) //需要等待应答

{

while(--waittime) //等待倒计时

{

DelayMs(10);

if(USART3_RX_STA)//接收到期待的应答结果

{

if(ESP8266_CheckCmd(ack))break;//得到有效数据 

USART3_RX_STA=0;

USART3_RX_CNT=0;

}

if(waittime==0)res=1; 

}

return res;

}


/*

函数功能:ESP8266退出透传模式

返 回 值:0,退出成功;

         1,退出失败

*/

u8 ESP8266_QuitTrans(void)

{

while((USART3->SR&0X40)==0); //等待发送空

USART3->DR='+';      

DelayMs(15); //大于串口组帧时间(10ms)

while((USART3->SR&0X40)==0); //等待发送空

USART3->DR='+';      

DelayMs(15); //大于串口组帧时间(10ms)

while((USART3->SR&0X40)==0); //等待发送空

USART3->DR='+';      

DelayMs(500); //等待500ms

return ESP8266_SendCmd("ATrn","OK",20);//退出透传判断.

}


/*

函数功能:获取ESP8266模块的连接状态

返 回 值:0,未连接;1,连接成功.

*/

u8 ESP8266_ConstaCheck(void)

{

u8 *p;

u8 res;

if(ESP8266_QuitTrans())return 0;   //退出透传 

ESP8266_SendCmd("AT+CIPSTATUSrn",":",50); //发送AT+CIPSTATUS指令,查询连接状态

p=ESP8266_CheckCmd("+CIPSTATUSrn:"); 

res=*p; //得到连接状态

return res;

}


/*

函数功能:获取ip地址

函数参数:ipbuf:ip地址输出缓存区

*/

void ESP8266_GetWanip(u8* ipbuf)

{

  u8 *p,*p1;

if(ESP8266_SendCmd("AT+CIFSRrn","OK",50))//获取WAN IP地址失败

{

ipbuf[0]=0;

return;

}

p=ESP8266_CheckCmd(""");

p1=(u8*)strstr((const char*)(p+1),""");

*p1=0;

sprintf((char*)ipbuf,"%s",p+1);

}


四、QT设计的上位机代码

4.1 软件运行效果图

软件有两个版本: 1. 网络版本 2. 串口版本

网络版本主要是通过TCP协议传输数据显示,串口版本直接通过串口传输。


基于STM32的实时心率检测仪设计

    基于STM32的实时心率检测仪设计

4.2 widget.cpp代码

基于STM32的实时心率检测仪设计

代码较多,这里就主UI的部分代码。#include "widget.h"


#include "ui_widget.h"


#define AppFontName "Microsoft YaHei"

#define AppFontSize 9


#define TextColor QColor(255,255,255)

#define Plot_NoColor QColor(0,0,0,0)


//曲线1的颜色

#define HeartRate_Plot_DotColor QColor(236,110,0)

#define HeartRate_Plot_LineColor QColor(246,98,0)

#define HeartRate_Plot_BGColor QColor(246,98,0,80)


//曲线2的颜色

#define HeartRate_Plot_DotColor_2 Qt::blue

#define HeartRate_Plot_LineColor_2 Qt::blue

#define HeartRate_Plot_BGColor_2 Qt::blue


#define TextWidth 1

#define LineWidth 2

#define DotWidth 5


//一个刻度里的小刻度数量--太小的话显示的时间会重叠

#define HeartRate_Plot_Count 5

//Y轴最大范围值

#define HeartRate_Plot_MaxY 3000


/*

 * 设置QT界面的样式

*/

void Widget::SetStyle(const QString &qssFile) {

    QFile file(qssFile);

    if (file.open(QFile::ReadOnly)) {

        QString qss = QLatin1String(file.readAll());

        qApp->setStyleSheet(qss);

        QString PaletteColor = qss.mid(20,7);

        qApp->setPalette(QPalette(QColor(PaletteColor)));

        file.close();

    }

    else

    {

        qApp->setStyleSheet("");

    }

}


Widget::Widget(QWidget *parent)

    : QWidget(parent)

    , ui(new Ui::Widget)

{

    ui->setupUi(this);


    /*服务器线程*/

    //开始信号

    connect(this,SIGNAL(StartServerThread()),&tcp_server_class,SLOT(run()));

    //日志信号

    connect(&tcp_server_class,SIGNAL(LogSend(QString)),this,SLOT(Log_Display(QString)));

    //移动到线程

    tcp_server_class.moveToThread(&tcp_server_thread);

    tcp_server_thread.start(); //启动线程

    StartServerThread(); //创建服务器


    this->setWindowTitle("万邦易嵌-健康监控管家");


    //波形图界面初始化

    InitForm();

    InitPlot();

    HeartRate_InitPlot();

    HeartRate_LoadPlot();

    SetStyle(":/blue.css");

    //开始加载数据

    plot_timer->start(100);

}


Widget::~Widget()

{

    delete ui;

}


//日志显示

void Widget::Log_Display(QString text)

{

    QPlainTextEdit *plainTextEdit_log=ui->plainTextEdit_log;

    //设置光标到文本末尾

    plainTextEdit_log->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);

    //当文本数量超出一定范围就清除

    if(plainTextEdit_log->toPlainText().size()>1024*4)

    {

        plainTextEdit_log->clear();

    }

    plainTextEdit_log->insertPlainText(text);

    //移动滚动条到底部

    QScrollBar *scrollbar = plainTextEdit_log->verticalScrollBar();

    if(scrollbar)

    {

        scrollbar->setSliderPosition(scrollbar->maximum());

    }

}


//查看服务器状态

void Widget::on_toolButton_server_stat_clicked()

{

    QString text="TCP服务器IP地址列表:n";

    QList list = QNetworkInterface::allAddresses();

    for(int i=0;isocketDescriptor()==-1)

        {

            text+="设备未连接n";

        }

        else

        {

            text+="设备连接成功n";

        }

    }

    else

    {

        text+="设备未连接n";

    }


    text+="数据协议:n";

    text+="A:心电数据1,B:新电数据2,C:运动步数,D:运动距离,E:体表温度n";

    text+="例如: "A:1633215,B:1833215,C:45,D:28,E:66.55"";


    QMessageBox::about(this,"状态信息",text);

}



//窗口关闭事件

void Widget::closeEvent(QCloseEvent *event)

{

    tcp_server_thread.quit();

    tcp_server_thread.wait();

}


void Widget::InitForm()

[1] [2]
关键字:STM32 引用地址:基于STM32的实时心率检测仪设计

上一篇:STM32串口DMA接收与发送
下一篇:为什么学习STM32时还要学习汇编

推荐阅读最新更新时间:2024-11-13 19:18

STM32单片机中使用SPI通信的方法
  在本教程中,我们将使用 STM32F103C8 的 Blue Pill 板替换一个 Arduino 板,并将使用 SPI 总线与 Arduino 板进行通信。在这个STM32 SPI 示例中,我们将使用Arduino UNO作为 Slave,STM32F103C8 作为 Master,两个16X2 LCD 显示器分别连接在一起。两个电位器还与STM32(PA0)和Arduino(A0)相连,通过改变电位器来确定主机到从机和从机到主机的发送值(0到255)。   STM32F103C8中的SPI   比较 Arduino 和 STM32F103C8 Blue Pill 板中的 SPI 总线,STM32 有2 条 SPI 总线
[单片机]
在<font color='red'>STM32</font>单片机中使用SPI通信的方法
STM32中的优先级概念
STM32(Cortex-M3)中有两个优先级的概念:抢占式优先级和响应优先级,也把响应优先级称作 亚优先级 或 副优先级 ,每个中断源都需要被指定这两种优先级。 1.什么是占先式优先级(pre-emption priority) 高占先式优先级的中断事件会打断当前的主程序/中断程序运行 抢断式优先响应,俗称中断嵌套。 2.什么是副优先级(subpriority) 在占先式优先级相同的情况下,高副优先级的中断优先被响应; 在占先式优先级相同的情况下,如果有低副优先级中断正在执行,高副优先级的中断要等待已被响应的低副优先级中断执行结束后才能得到响应 非抢断式响应(不能嵌套)。 3.判断中断是否
[单片机]
什么是时钟树架构
2.1 时钟树结构图 STM32属于Cortex-M3内核的单片机,时钟结构比之前的51单片机较复杂的多,根据数据手册,STM32F103的时钟结构如下图所示。 根据上图可以看到,STM32F103系列单片机具有4个时钟源,内部的8MHz时钟发生器,外部的晶体振荡器接口,最高支持16MHz,外部的32.768kHz晶体振荡器接口和内部的40kHz时钟发生器,其中32.768kHz和40kHz主要用于内部RTC时钟脉冲,8MHz的晶振通过PLL时钟倍乘器,将系统总线时钟提高为72MHz。 STM32F103系列内部具有2条外设时钟总线,APB1和APB2,其中APB2的时钟最高可达72MHz,APB1的时钟最高可达36MHz
[单片机]
什么是时钟树架构
STM32单片机芯片介绍
无论做什么芯片的开发,都需要看芯片手册及其附带的资料。针对STM32单片机,我们能够借鉴的手册有《Cortex-M3权威指南(中文)》《STM32中文参考手册》(通常都是英文的,我们再翻译,这也是懂一些基础英语的重要性),此外,还有官方的《ST MCU选型手册》《STM32F103RC_Datasheet》。 其实除了ST意法半导体公司出厂的芯片,还有许多像NXP出品的32位芯片,也并不是只有简简单单的STM32F103RC系列的芯片,还有F2、F4系列等等。 上图就是官方提供的选型图片,相信看完就大概知道自己根据功能选什么系列的芯片了。正是由于芯片的多样性,业内使用ST意法半导体制作的芯片的公司还是处于多数的。 那么ST
[单片机]
<font color='red'>STM32</font>单片机芯片介绍
STM32入门学习笔记之uCOS-II系统移植3
⑤这两个函数都用于任务切换,它们的本质都是触发PendSV中断,具体切换过程在PendSV的中断函数中进行,其中OSCtxSw是任务级切换,OSIntCtxSw是中断级切换,是从中断退出时切换到一个任务中,从中断切换到任务的过程中,CPU的寄存器入栈工作已经完成。 OSCtxSw PUSH {R4, R5} LDR R4, =NVIC_INT_CTRL ;触发PendSV异常 LDR R5, =NVIC_PENDSVSET STR R5, ;向NVIC_INT_CTRL写入NVIC_PENDSVSET触发PendSV中断 POP {R4, R5} BX LR OSIntCt
[单片机]
STM32采集AD电压
是否使用DMA传输 使用DMA传输,那么流程为: ADC初始化,DMA初始化,TIM2初始化 其中:TIM2作为ADC的中断源 当发生一次定时器的中断时,进入AD转换,在DMA的初始化时与ADC-DR寄存器进行绑定,在该寄存器获得数据时,直接通过DMA通道将该寄存器的数据保存在给定的数组里面,把缓存数组装满后,会触发一次DMA的中断,在DMA的中断里面将缓存数组保存到100个电压值的数组里面。 定时器设为200us发生一次中断,来进行一次AD转换,获得寄存器里面的数据以后,保存在数组,保存100个数组后停止定时器的工作以及DMA的工作,对这份数据进行处理。 至此,DMA工作的流程已经结束,那么其中又怎样的缺漏导致不用DMA进行
[单片机]
STM32 内部FLASH读写操作
关于STM32内部FLASH读写操作 单片机程序flash对应的内部地址。 falsh内部128bytes为一页,32页一个扇区。为了不会破坏到程序一般我们把要存的数据放后面,或者放到最后一页,来读取保存。STM32程序起始地址一般为0x08000000。 2.读取数据 //faddr 要读取的地址 uint16_t STMFLASH_ReadHalfWord ( uint32_t faddr ) { return *(__IO uint16_t*)faddr; } uint16_t STMFLASH_Read (uint32_t Addr ) { uint16_t Date =
[单片机]
UCGUI在STM32平台移植经验(无操作系统)
ucgui 移植的前提是已经具备了LCD驱动函数,已经能够实现点亮LCD屏幕,并实现画点以及获取指定点颜色值的功能。一般的显示屏供应商会提供对应的驱动函数。主要有初始化函数void LCD_Init(),屏幕画点函数 Void LCD_DrawPoint(u16 x,u16 y,u16 color),以及获取指定点颜色值的U16 LCD_ReadPoint(u16 x,u16 y)函数。移植的关键在于把这三个函数与ucgui提供的接口函数匹配。 打开GUILCDDriver中的LCDDummy.c文件,找到int LCD_L0_Init(void)初始化函数,LCD_L0_SetPixelIndex(int x, int y,
[单片机]
UCGUI在<font color='red'>STM32</font>平台移植经验(无操作系统)
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved