【GD32F427开发板试用】8. 脉搏监控仪

news2024/11/18 7:52:01

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:hehung

之前发帖

【GD32F427开发板试用】1. 串口实现scanf输入控制LED
【GD32F427开发板试用】2. RT-Thread标准版移植
【GD32F427开发板试用】3. 硬件IIC0驱动OLED显示中文
【GD32F427开发板试用】4. ADC采集摇杆模块移动量
【GD32F427开发板试用】5. SPI驱动TFTLCD屏幕
【GD32F427开发板试用】6. 定时器运用之精确定时1s
【GD32F427开发板试用】7. 移植LVGL到GD32F427V

前言

本文章实现了一个心率监控设备,可以通过脉搏传感器采集脉搏信息的ADC值并通过解算采集的信号,将信号转换成实际的脉搏值。该文章实现了如下功能:

  1. ADC采集心率数值并解算出心率值;
  2. SPI驱动TFTLCD显示心率值以及心率波形;(没有使用LVGL,本来想使用的,但是LVGL的lv_line功能画出来的曲线有断点,所以就自己写了波形显示函数)
  3. 基于RT-Thread实现

硬件连接

脉搏传感器

脉搏传感器使用的是Pulse Sensor,关于其描述,网上可以直接搜索到不在赘述,直接说下电路连接。

pulse sensorMCU
+3.3V
-GND
SPA1

原理简述:
心率传感器采集原理是,通过发出绿色光,测量反馈的光的强度,转换成ADC值并放大输出到S引脚,单片机将S引脚连接到ADC上即可,采集的ADC值就是心率变化值,通过计算出两个脉搏之间的距离就可以算出每分钟的心跳次数。

LCD连接

这块不在赘述,直接参考我之前文章即可:【GD32F427开发板试用】5. SPI驱动TFTLCD屏幕
本次驱动没有使用lvgl功能,因为我需要绘制曲线,使用lvgl的lv-line功能绘制出来的曲线存在断点,不够连续,没找到问题原因,所有我就自己通过画点来绘制了心率曲线图,效果还可以。

软件实现

本部分只说明实现的核心部分,整个工程在gitee上,有需要可以参考,见文章末尾。

ADC数据采集

采集ADC数据,并且转换为10bit精度,本来想直接使用10bit精度的,但是设置了发现没生效,不知道为什么。

/* 阅读采集自心率传感器的ADC值 */
static void Ps_ReadSampleValueFromAdc(void)
{
    rt_uint32_t value;

    /* 读取采样值 */
    value = Adc_ChSample(ADC_CHANNEL_1);

    /* 转换采集到的ADC值的精度为10bits */
    Signal = value>>2;
}

心率处理部分

此部分包含了采集的ADC信号中的心率信号进行识别的逻辑,代码中有比较详细的说明,本文不在赘述。

//心率采集相关变量
int BPM;                            //脉搏率==就是心率
int Signal;                         //传入的原始数据。
int IBI = 600;                      //节拍间隔,两次节拍之间的时间(ms)。计算:60/IBI(s)=心率(BPM)
bool Pulse = false;        //脉冲高低标志。当脉波高时为真,低时为假。
bool QS = false;           //当发现一个节拍时,就变成了真实。
int rate[10];                       //数组保存最后10个IBI值。
uint32_t sampleCounter = 0;    //用于确定脉冲定时。
uint32_t lastBeatTime = 0;     //用于查找IBI
int P = 512;                         //用于在脉冲波中寻找峰值
int T = 512;                        //用于在脉冲波中寻找波谷
int thresh = 512;                   //用来寻找瞬间的心跳
int amp = 100;                      //用于保持脉冲波形的振幅
int Num;
uint8_t firstBeat = true;     //第一个脉冲节拍
uint8_t secondBeat = false;   //第二个脉冲节拍,这两个变量用于确定两个节拍


/* 采集心率信号的处理函数 */
static void Ps_HeartRateDeal(void)
{
    unsigned int runningTotal;
    uint8_t i;

    sampleCounter += 2;
    Num = sampleCounter - lastBeatTime;             //监控最后一次节拍后的时间,以避免噪声

    //找到脉冲波的波峰和波谷
    if((Signal < thresh) && (Num > (IBI/5)*3))  //为了避免需要等待3/5个IBI的时间
    {
        if(Signal < T)
        {                                       //T是阈值
            T = Signal;                         //跟踪脉搏波的最低点,改变阈值
        }
    }
    if((Signal > thresh) && (Signal > P))       //采样值大于阈值并且采样值大于峰值
    {
        P = Signal;                             //P是峰值,改变峰值
    }
    //现在开始寻找心跳节拍
    if (Num > 250)              //避免高频噪声
    {
        if ((Signal > thresh) && (Pulse == false) && (Num > (IBI/5)*3))
        {
            Pulse = true;                               //当有脉冲的时候就设置脉冲信号。
            IBI = sampleCounter - lastBeatTime;         //测量节拍的ms级的时间
            lastBeatTime = sampleCounter;               //记录下一个脉冲的时间。
            if(secondBeat)          //如果这是第二个节拍,如果secondBeat == TRUE,表示是第二个节拍
            {
                secondBeat = false;                  //清除secondBeat节拍标志
                for(i=0; i<=9; i++)     //在启动时,种子的运行总数得到一个实现的BPM。
                {
                    rate[i] = IBI;
                }
            }
            if(firstBeat)           //如果这是第一次发现节拍,如果firstBeat == TRUE。
            {
                firstBeat = false;                   //清除firstBeat标志
                secondBeat = true;                   //设置secongBeat标志
                return;                              //IBI值是不可靠的,所以放弃它。
            }
            //保留最后10个IBI值的运行总数。
            runningTotal = 0;                  //清除runningTotal变量

            for(i=0; i<=8; i++)             //转换数据到rate数组中
            {
                rate[i] = rate[i+1];                  //去掉旧的的IBI值。
                runningTotal += rate[i];              //添加9个以前的老的IBI值。
            }

            rate[9] = IBI;                          //将最新的IBI添加到速率数组中。
            runningTotal += rate[9];                //添加最新的IBI到runningTotal。
            runningTotal /= 10;                     //平均最后10个IBI值。
            BPM = 60000/runningTotal;               //一分钟有多少拍。即心率BPM
            if(BPM>200)
                BPM=200;         //限制BPM最高显示值
            if(BPM<30)
                BPM=30;               //限制BPM最低显示值
            QS = true;
        }
    }

    if (Signal < thresh && Pulse == true)       //当值下降时,节拍就结束了。
    {
        Pulse = false;                         //重设脉冲标记,这样方便下一次的计数
        amp = P - T;                           //得到脉冲波的振幅。
        thresh = amp/2 + T;                    //设置thresh为振幅的50%。
        P = thresh;                            //重新设置下一个时间
        T = thresh;
    }

    if (Num > 2500)             //如果2.5秒过去了还没有节拍
    {
        thresh = 512;                          //设置默认阈值
        P = 512;                               //设置默认P值
        T = 512;                               //设置默认T值
        lastBeatTime = sampleCounter;          //把最后的节拍跟上来。
        firstBeat = true;                      //设置firstBeat为true方便下一次处理
        secondBeat = false;
    }
}

波形处理

此部分包含了TFTLCD需要显示的波形数据,将采集的波形数据放置到一个数组中,用于显示。

//波形处理函数,放在定时器中执行,20ms执行一次
static void Ps_WaveformDeal(uint16_t adc_value)
{
    int16_t temp;
    uint16_t i = 0;

    temp = adc_value - 224;
    temp = 500 - temp;
    if (temp < 0)
        temp = 0;
    else if (temp > 500)
        temp = 500;
    temp = (uint8_t)(temp/2);

#if (COMMON_USE_LVGL == COMMON_OFF)
    /* 超过LCD显示范围之后从新开始 */
    for(i = 0; i < (PS_WAVE_POINT_NUM-1); i++)
    {
        ps_waveformlist[i] = ps_waveformlist[i+1];
    }
    ps_waveformlist[PS_WAVE_POINT_NUM-1] = temp;
#else
    /* 超过LCD显示范围之后从新开始 */
    for(i = 0; i < (PS_WAVE_POINT_NUM-1); i++)
    {
        ps_waveformlist[i].x = i;
        ps_waveformlist[i].y = ps_waveformlist[i+1].y;
    }
    ps_waveformlist[PS_WAVE_POINT_NUM-1].x = PS_WAVE_POINT_NUM-1;
    ps_waveformlist[PS_WAVE_POINT_NUM-1].y = temp;
#endif

    QS = 0;
}

定时器中断

软件使能了一个2ms的定时器,用于没2ms采集一次数据并进行分析。

/* Timer interrupt service function */
void TIMER1_IRQHandler(void)
{
    if(SET == timer_interrupt_flag_get(TIMER1, TIMER_INT_UP))
    {
        /* Sample adc every 2ms */
        Ps_ReadSampleValueFromAdc();
        /* Calculate heart rate */
        Ps_HeartRateDeal();
        /* clear TIMER interrupt flag */
        timer_interrupt_flag_clear(TIMER1, TIMER_INT_UP);
    }
}

LCD显示函数

此部分包含了心率显示,心率采集波形显示等。

void Gui_Init(void)
{
    /* Initialize lcd */
    lcd_init();
    lcd_clear(WHITE);

    lcd_draw_font_gbk16(5, 0, BLUE, WHITE, "GD32F427V-START | aijishu.com | hehung");
}

static void Gui_MainFunction(void)
{
    char bpm_str[20];
    uint16_t x;
    uint16_t y;

    sprintf(bpm_str, "BPM:%d     ", Ps_GetBpm());
    lcd_draw_font_gbk16(5, 16, RED, WHITE, bpm_str);

    pulse_data_point = Ps_GetWaveformList();
    /* 显示波形 */
    LCD_CS_CLR;
    for (x = 0; x < PS_WAVE_POINT_NUM; x++)
    {
        for (y = 0; y < 200; y++)
        {
            if (pulse_data_point[x] == y)
            {
                lcd_draw_point(x, y+40, BLUE);
            }
            else if ((x != (PS_WAVE_POINT_NUM-1)) && (y > Gui_GetNumMin(pulse_data_point[x], pulse_data_point[x+1])) &&
                    (y < Gui_GetNumMax(pulse_data_point[x], pulse_data_point[x+1])))
            {
//                rt_kprintf("test:%d\n",y);
                lcd_draw_point(x, y+40, BLUE);
            }
            else
            {
                lcd_draw_point(x, y+40, WHITE);
            }
        }
    }
    LCD_CS_SET;
}

显示效果

左上角显示当前采集的心率值,下部显示心率波形。

演示视频见bilibili:

https://www.bilibili.com/video/BV1M8411p7dj​www.bilibili.com/video/BV1M8411p7dj

程序代码

程序放置到了gitee,其中还包括了硬件IIC实现了OLED的驱动代码,只不过在这个工程中并没有使用。
https://gitee.com/hehung/GD32F427_pulse_monitor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/184083.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动态内存开辟必看,一篇就能学会贯通

目录 为什么存在动态内存分配 动态内存函数的介绍 1.malloc函数和free函数 2.calloc函数 3.realloc函数 常见的动态内存错误 经典的笔试题 柔性数组 今天来介绍一下动态内存&#xff0c;让我们直入主题&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 为…

【Cocos新手入门】cocos creator 的研发思路和工具操作说明

本篇文章主要讲解cocos creator 的研发思路和工具操作说明 作者&#xff1a;任聪聪 日期&#xff1a;2023年1月29日 研发思路 关于cocos creator 工具说明 首先cocos creator 是一个编辑游戏界面的窗口&#xff0c;省去了我们日常开发游戏时频繁修改参数调整动画、场景的工作…

Siam R-CNN: 通过重检测进行视觉跟踪

Siam R-CNN: 通过重检测进行视觉跟踪Siam R-CNN: Visual Tracking by Re-DetectionContributionsMethodSiam RCNNVideo Hard Example MiningTracklet Dynamic Programming Algorithm实验总结更多Ai资讯&#xff1a;公主号AiCharm Siam R-CNN: Visual Tracking by Re-Detectio…

DMETL5单机版安装使用

DMETL5安装使用 1.创建dm8数据库 使用dm数据库配置助手dbca创建数据库 2.根据dmetl在线文档创建HOTEL模式 DROP USER IF EXISTS HOTEL CASCADE; DROP TABLESPACE IF EXISTS HOTEL; CREATE TABLESPACE HOTEL DATAFILE HOTEL.DBF SIZE 150 AUTOEXTEND ON NEXT 10; CREATE USE…

基于springboot的仓库管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

增鑫科技更新招股书,冲刺深交所上市,正邦集团是其主要股东

近日&#xff0c;江西增鑫科技股份有限公司&#xff08;下称“增鑫科技”&#xff09;预披露更新招股书&#xff0c;准备在深圳证券交易所主板上市。据贝多财经了解&#xff0c;增鑫科技曾于2022年7月1日递交招股书&#xff0c;此次更新了截至2022年6月30日的财务数据等信息。 …

权威报告!免费解锁IBM最新《2022-2023年Salesforce状态报告》

前不久&#xff0c;IBM发布了2022-2023年Salesforce状态报告&#xff0c;揭示了一些热门趋势&#xff0c;报告显示Salesforce仍然是许多企业客户成功战略的重要力量。 Salesforce状态报告是一项全球性的、数据驱动的调查&#xff0c;主要调查业务战略、投资和发展&#xff0c;同…

Linux常用命令——rpm命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) rpm RPM软件包的管理工具 补充说明 rpm命令是RPM软件包的管理工具。rpm原本是Red Hat Linux发行版专门用来管理Linux各项套件的程序&#xff0c;由于它遵循GPL规则且功能强大方便&#xff0c;因而广受欢迎。逐…

5.2 晶体管的高频等效模型

从晶体管的物理结构出发&#xff0c;考虑发射结和集电结电容的影响&#xff0c;就可以得到在高频信号作用下的物理模型&#xff0c;称为混合 π\pmb{π}π 模型。由于晶体管的混合 πππ 模型与 hhh 参数等效模型在低频信号作用下具有一致性&#xff0c;因此&#xff0c;可用 …

Unity Native Plugin C#和C++互相调用

官方链接 1.DLL的方式&#xff1a; C代码&#xff1a;编译成DLL&#xff0c;导入Unity #pragma once #include <map> #include <string>//导出宏定义 #define _DllExport _declspec(dllexport)//函数指针 typedef void (*NativeCallback)(const char*);extern &…

【28】C语言 | 关于指针练习(2)

目录 10、下列关于二维数组输出 11、下列关输出 12、下列代码输出什么 13、下列代码输出什么 14、下列代码输出什么 15、下列代码输出什么 16、下列代码输出什么 17、下列代码输出什么 18、杨氏矩阵 19、左旋转两个字符 10、下列关于二维数组输出 int main() {int …

文献阅读:Improving Language Understanding by Generative Pre-Training

文献阅读&#xff1a;Improving Language Understanding by Generative Pre-Training 1. 文章简介2. 模型介绍3. 实验考察 1. 训练数据2. 实验结果3. 消解实验 4. 总结 & 思考 文献链接&#xff1a;https://cdn.openai.com/research-covers/language-unsupervised/languag…

P1464 Function————C++

文章目录题目Function题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1解题思路Code运行结果题目 Function 题目描述 对于一个递归函数 w(a,b,c)w(a,b,c)w(a,b,c) 如果 a≤0a \le 0a≤0 或 b≤0b \le 0b≤0 或 c≤0c \le 0c≤0 就返回值$ 1$。如果 a>20a>20a&…

【Java】-【并发】

文章目录堆和方法区中的数据是可以被共享的堆中的数据是被栈中的变量所持用的&#xff0c;栈是线程隔离的&#xff0c;每个线程私有一个栈&#xff0c;所以栈中的数据不共享调用a方法时&#xff0c;jvm会给a方法创建一块内存区&#xff0c;让其入栈&#xff0c;这块区域被称为a…

linux(信号量)

信号量几个基本概念临界资源临界区原子性互斥信号量后台进程前台进程信号储存信号处理信号(信号捕捉)发送信号1、键盘产生&#xff1a;2、系统调用接口发送信号3、由软件条件产生信号4、硬件异常发送信号内核中的信号量**信号量在内核中的数据结构****信号集操作函数**信号的检…

YOLOv7 Falsk Web 监测平台 | YOLOv7 Falsk Web 部署

YOLOv7 Falsk Web 监测平台图片效果展示 YOLOv7 Falsk Web 监测平台视频效果展示 YOLOv7 Flask Web 检测平台 什么是Flask? 简介 Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开…

电子技术——MOS管的小信号模型

电子技术——MOS管的小信号模型 在上一节&#xff0c;我们已经学习过了MOS管的基本线性放大原理&#xff0c;本节我们继续深入MOS管的小信号放大&#xff0c;建立MOS管的小信号模型。 我们本节继续使用上节的电路&#xff0c;如下图所示&#xff1a; DC偏置点 根据上节的知识…

2.单例模式,工厂模式,建造者模式,原型模式

单例模式 单例模式的优点&#xff1a; 处理资源访问冲突表示全局唯一类 实现单例的关键&#xff1a; 构造函数需要是 private 访问权限的&#xff0c;这样才能避免外部通过 new 创建实例&#xff1b;考虑对象创建时的线程安全问题&#xff1b;考虑是否支持延迟加载&#xff1b…

Deformable DETR TBD范式的不二选择

TBD范式检测和跟踪是不分家的。当前,性能较优的目标检测方法大都基于Transformer来做,CNN在目标检测的表现逐渐走低。DETR是基于Transformer的目标检测开山作,其解决了霸榜的yolo系列一些令人讨厌的事情,不需要前处理和后处理,做到了真正意义上的end to end: 前处理:Anc…

测试做得好,犯错少不了【30个最容易犯的错误】谨记

最近跟一些刚刚进入软件测试行业的朋友去交流&#xff0c;发现了一个有趣的现象&#xff0c;就是对于这个行业的很多问题的认识都是一致的片面&#xff0c;当然也可以理解为误区。自己利用一点时间&#xff0c;把他们对于这个行业的认识误区都罗列出来&#xff0c;然后结合自己…