基于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-24 07:22

STM32之PWM君
简介:PWM、、英语好的人估计又知道这三个大写字母代表哪三个英语单词了、小弟不才,就说中文意思好了:脉冲宽度调制,玩过飞思卡尔的人估计对PWM非常的不陌生吧、电机驱动需要PWM,控制舵机的转向需要PWM,总之、可以说,PWM,you are so good。 好了、、言归正传,广大的互联网的网友们,咱们又见面了,大家早上晚上中午好好好、额、、好像也没见过面,STM32的PWM,可谓是小强中的小强,STM32的PWM,就是由定时器产生的,但是奇怪的是除了定时器TIM6和TIM7不能产生PWM外,其他的定时器都可以产生,而且还有多路之分,“高级官员”TIM1和TIM8说:老子可以产生多达7路,而其他的定时器默默的哀伤,因为自己最多
[单片机]
stm32 外部PB8中断
一、初始化该引脚时钟 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB ,ENABLE); 二、初始化该引脚为外部中断 void GPIO_Config_Init(void) { GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //PB8 dog_wake GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); } 三、初始化外
[单片机]
STM32串口中断接收和中断发送
先贴出中断函数: void USART1_IRQHandler(void){ if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { USART_ClearITPendingBit(USART1, USART_IT_RXNE); USART1_Buffer =USART_ReceiveData(USART1); //USART1_Buffesh是一个自己定义的接收数组 if(i 3){ SendFlag = 1; } } if(USAR
[单片机]
9G-STM32 LwIP测试过程简介
一,准备STM32 LwIP软件包 1,在http://www.st.com/mcu/devicedocs-STM32F107RC-110.html 下载lwIP TCP/IP stack demonstration for STM32F107xx connectivity line microcontrollers软件包 an3102.zip http://www.st.com/stonline/products/support/micro/files/an3102.zip 2,在http://www.st.com/mcu/devicedocs-STM32F107RC-110.html 下载lwIP TCP
[单片机]
STM32之_keil编译内存大小解析
Program Size: Code=28784 RO-data=6480 RW-data=60 ZI-data=3900 的含义 1. Code: 程序所占用的FLASH大小,存储在FLASH. 2. RO-data: Read-only-data,程序定义的常量,存储在FLASH中。 3. RW-data:Read-write-data,已经被初始化的变量,存储在SRAM中。 4. ZI-data:Zero-Init-data,未被初始化的变量,存储在SRAM中。 简单的说就是在烧写的时候是FLASH中的被占用的空间为: ROM(Flash) size = Code+RO-data+RW-data; 上面代码大小
[单片机]
<font color='red'>STM32</font>之_keil编译内存大小解析
STM32中PWM频率捕获的相关配置
先大体说一下频率捕获 根据我个人的理解 频率捕获用到计数器 当发生定时器中断时(应该就是下图的Autoreload register记满时) 在2个相邻的定时器中断记录2个值 算捕获到的信号的差值 最后的频率就是你的定时器时钟 除以你的捕获的值 当然如果直接调用官方3.5库中的PWM_Input例程中的函数直接捕获 会发现在低于大约980Hz 测量的值会非常不准(STM32F103RBT6下) 这时候就需要使用TIM_TimeBaseInit()进行预分频 参数的配置在于PSC和ARR 下面贴图 两张Prescaler改变后的图 ARR决定的是Autoreload register的装值 上面2张图显示改变PSC后
[单片机]
再造STM32---第十一部分:GPIO—位带操作
本章参考资料:《STM32F4xx 中文参考手册》存储器和总线构架章节、 GPIO 章节,《Cortex®-M4 内核编程手册》 2.2.5 Bit-banding。学习本章时,配套这些参考资料学习效果会更佳。 11.1 位带简介: 位操作就是可以单独的对一个比特位读和写,这个在 51 单片机中非常常见。 51 单片机中通过关键字 sbit 来实现位定义, F429 中没有这样的关键字,而是通过访问位带别名区来实现。 在 F429 中,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,另一个是外设区最低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的
[单片机]
再造STM32---第十一部分:GPIO—位带操作
stm32看门狗复位技巧编辑
一、看门狗复位的应用技巧包括三个方面: 1:判断是否需要使用。如果要使用看门狗的话,需要做一些寄存器的配置,在程序区的某些地方也要加入喂狗指令来防止看门狗复位,有一定的工作量,所以用与不用需要考虑一下。能不使用看门狗的场合,要求是系统即使死机也问题不大,等待人过来断电复位即可的情况。但是这种情况已经很少了,所以绝大多数情况下看门狗都要加上。比如有温控功能的电热水器,假如电加热已经启动,但是系统死机了,温控失效,电加热也不会关闭,这时水温就会一直升高,直到水被蒸干,然后电加热损坏或引发火灾,或者人被开水烫伤。这时有看门狗复位,系统就会恢复正常,检测到温度够了,就会关闭电加热的。 2、保证看门狗工作正常。看门狗除了进行寄存器配置之外,
[单片机]
<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