【毕业设计】基于单片机的心率血氧健康监测手表 - 物联网 嵌入式

news/2024/10/15 10:14:37/

文章目录

  • 0 前言
  • 1 简介
  • 2 主要器件
  • 3 实现效果
  • 4 设计原理
    • 4.1 **硬件准备**
    • 4.2 **传感器和算法**
  • 5 部分核心代码
  • 5 最后


0 前言

🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。

为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是

🚩 基于PID控制的智能平衡车设计与实现

🥇学长这里给一个题目综合评分(每项满分5分)

  • 难度系数:4分
  • 工作量:4分
  • 创新点:3分

🧿 选题指导, 项目分享:

https://gitee.com/dancheng-senior/project-sharing-1/blob/master/%E6%AF%95%E8%AE%BE%E6%8C%87%E5%AF%BC/README.md


1 简介

本项目基于Arduino平台,使用 MAX30102、XIAO 和一个微型显示器来构建血氧和心率计。

2 主要器件

  • Arduino MAX30102脉搏心率传感器
  • Seeeduino XIAO
  • 彩色显示器

3 实现效果

在这里插入图片描述
用 Apple Watch Series 6 交叉检查了读数,MolecularD 算法可以给出与 Apple Watch 相当的非常精确的数字。
在这里插入图片描述

在这里插入图片描述

4 设计原理

4.1 硬件准备

在这里插入图片描述
单片机

脉搏血氧计计算算法需要一些内存。Arduino 经典系列没有足够的内存用于数据缓冲区。而且我希望所有组件都可以安装在一个 400 孔的面包板上,这里选用了一个Seeeduino XIAO。

显示

任何支持 Arduino_GFX 的显示器都应该没问题,刚好手头有一个 1.14" 135x240 彩色显示器。

传感器

Maxim Integrated MAX30102 生物传感器。MAX30102这款传感器:Maxim公司的MAX30102是高灵敏度血氧和心率生物传感器,包括内部的LED,光电检测器,光学元件,以及环境光抑制的低噪音电子学。单个1.8V电源,内部LED电源5.0V,通信通过标准的I2C接口进行,工作温度-40℃ 到 +85℃,微细5.6mm x 3.3mm x 1.55mm 14引脚封装,主要用在可穿戴设备以及健美辅助设备。

面包板连接
在这里插入图片描述

在这里插入图片描述

具体连接如下:
在这里插入图片描述

软件准备

4.2 传感器和算法

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

工作原理

MAX30102采用PPG光电容积脉搏波描记法(PhotoPlethysmoGraphy)测量数据,主要通过血液及血管的动态改变,反应出透光率的关系进行测算。微控制器对这些数据进行处理运算过后得到心率血氧数值,再通过I2C或UART接口输出,大大降低了传感器的使用难度和对主控的资源占用。同时该传感器还配备有对应的上位机,可通过电脑直接读取数据。

光电容积法的基本原理是利用人体组织在血管搏动时造成透光率不同来进行脉搏和血氧饱和度测量的。其使用的传感器由光源和光电变换器两部分组成,通过绑带或夹子固定在病人的手指、手腕或耳垂上。光源一般采用对动脉血中氧合血红蛋白(HbO2)和去氧血红蛋白(Hb)有选择性的特定波长的发光二极管(一般选用660nm附近的红光和900nm附近的红外光)。当光束透过人体外周血管,由于动脉搏动充血容积变化导致这束光的透光率发生改变,此时由光电变换器接收经人体组织反射的光线,转变为电信号并将其放大和输出。由于脉搏是随心脏的搏动而周期性变化的信号,动脉血管容积也呈现周期性变化,因此光电变换器的电信号变化周期就是脉搏率。同时根据血氧饱和度的定义,其表示为:
在这里插入图片描述
MAX30102本身集成了完整的发光LED及驱动部分,光感应和AD转换部分,环境光干扰消除及数字滤波部分,只将数字接口留给用户,极大地减轻了用户的设计负担。用户只需要使用单片机通过硬件I2C或者模拟I2C接口来读取MAX30102本身的FIFO,就可以得到转换后的光强度数值,通过编写相应算法就可以得到心率值和血氧饱和度。

5 部分核心代码

#include "max30102.h"
#include "myiic.h"
#include "delay.h"u8 max30102_Bus_Write(u8 Register_Address, u8 Word_Data)
{/* 采用串行EEPROM随即读取指令序列,连续读取若干字节 *//* 第1步:发起I2C总线启动信号 */IIC_Start();/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_WR);	/* 此处是写指令 *//* 第3步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第4步:发送字节地址 */IIC_Send_Byte(Register_Address);if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第5步:开始写入数据 */IIC_Send_Byte(Word_Data);/* 第6步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 发送I2C总线停止信号 */IIC_Stop();return 1;	/* 执行成功 */cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 *//* 发送I2C总线停止信号 */IIC_Stop();return 0;
}u8 max30102_Bus_Read(u8 Register_Address)
{u8  data;/* 第1步:发起I2C总线启动信号 */IIC_Start();/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_WR);	/* 此处是写指令 *//* 第3步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第4步:发送字节地址, */IIC_Send_Byte((uint8_t)Register_Address);if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第6步:重新启动I2C总线。下面开始读取数据 */IIC_Start();/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_RD);	/* 此处是读指令 *//* 第8步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第9步:读取数据 */{data = IIC_Read_Byte(0);	/* 读1个字节 */IIC_NAck();	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */}/* 发送I2C总线停止信号 */IIC_Stop();return data;	/* 执行成功 返回data值 */cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 *//* 发送I2C总线停止信号 */IIC_Stop();return 0;
}void max30102_FIFO_ReadWords(u8 Register_Address,u16 Word_Data[][2],u8 count)
{u8 i=0;u8 no = count;u8 data1, data2;/* 第1步:发起I2C总线启动信号 */IIC_Start();/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_WR);	/* 此处是写指令 *//* 第3步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第4步:发送字节地址, */IIC_Send_Byte((uint8_t)Register_Address);if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第6步:重新启动I2C总线。下面开始读取数据 */IIC_Start();/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_RD);	/* 此处是读指令 *//* 第8步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第9步:读取数据 */while (no){data1 = IIC_Read_Byte(0);	IIC_Ack();data2 = IIC_Read_Byte(0);IIC_Ack();Word_Data[i][0] = (((u16)data1 << 8) | data2);  //data1 = IIC_Read_Byte(0);	IIC_Ack();data2 = IIC_Read_Byte(0);if(1==no)IIC_NAck();	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */elseIIC_Ack();Word_Data[i][1] = (((u16)data1 << 8) | data2); no--;	i++;}/* 发送I2C总线停止信号 */IIC_Stop();cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 *//* 发送I2C总线停止信号 */IIC_Stop();
}void max30102_FIFO_ReadBytes(u8 Register_Address,u8* Data)
{	max30102_Bus_Read(REG_INTR_STATUS_1);max30102_Bus_Read(REG_INTR_STATUS_2);/* 第1步:发起I2C总线启动信号 */IIC_Start();/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_WR);	/* 此处是写指令 *//* 第3步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第4步:发送字节地址, */IIC_Send_Byte((uint8_t)Register_Address);if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第6步:重新启动I2C总线。下面开始读取数据 */IIC_Start();/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_RD);	/* 此处是读指令 *//* 第8步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;	/* EEPROM器件无应答 */}/* 第9步:读取数据 */Data[0] = IIC_Read_Byte(1);	Data[1] = IIC_Read_Byte(1);	Data[2] = IIC_Read_Byte(1);	Data[3] = IIC_Read_Byte(1);Data[4] = IIC_Read_Byte(1);	Data[5] = IIC_Read_Byte(0);/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) *//* 发送I2C总线停止信号 */IIC_Stop();cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 *//* 发送I2C总线停止信号 */IIC_Stop();//	u8 i;
//	u8 fifo_wr_ptr;
//	u8 firo_rd_ptr;
//	u8 number_tp_read;
//	//Get the FIFO_WR_PTR
//	fifo_wr_ptr = max30102_Bus_Read(REG_FIFO_WR_PTR);
//	//Get the FIFO_RD_PTR
//	firo_rd_ptr = max30102_Bus_Read(REG_FIFO_RD_PTR);
//	
//	number_tp_read = fifo_wr_ptr - firo_rd_ptr;
//	
//	//for(i=0;i<number_tp_read;i++){
//	if(number_tp_read>0){
//		IIC_ReadBytes(max30102_WR_address,REG_FIFO_DATA,Data,6);
//	}//max30102_Bus_Write(REG_FIFO_RD_PTR,fifo_wr_ptr);
}void max30102_init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_14;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_Init(GPIOB, &GPIO_InitStructure);IIC_Init();max30102_reset();//	max30102_Bus_Write(REG_MODE_CONFIG, 0x0b);  //mode configuration : temp_en[3]      MODE[2:0]=010 HR only enabled    011 SP02 enabled
//	max30102_Bus_Write(REG_INTR_STATUS_2, 0xF0); //open all of interrupt
//	max30102_Bus_Write(REG_INTR_STATUS_1, 0x00); //all interrupt clear
//	max30102_Bus_Write(REG_INTR_ENABLE_2, 0x02); //DIE_TEMP_RDY_EN
//	max30102_Bus_Write(REG_TEMP_CONFIG, 0x01); //SET   TEMP_EN//	max30102_Bus_Write(REG_SPO2_CONFIG, 0x47); //SPO2_SR[4:2]=001  100 per second    LED_PW[1:0]=11  16BITS//	max30102_Bus_Write(REG_LED1_PA, 0x47); 
//	max30102_Bus_Write(REG_LED2_PA, 0x47); max30102_Bus_Write(REG_INTR_ENABLE_1,0xc0);	// INTR settingmax30102_Bus_Write(REG_INTR_ENABLE_2,0x00);max30102_Bus_Write(REG_FIFO_WR_PTR,0x00);  	//FIFO_WR_PTR[4:0]max30102_Bus_Write(REG_OVF_COUNTER,0x00);  	//OVF_COUNTER[4:0]max30102_Bus_Write(REG_FIFO_RD_PTR,0x00);  	//FIFO_RD_PTR[4:0]max30102_Bus_Write(REG_FIFO_CONFIG,0x0f);  	//sample avg = 1, fifo rollover=false, fifo almost full = 17max30102_Bus_Write(REG_MODE_CONFIG,0x03);  	//0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LEDmax30102_Bus_Write(REG_SPO2_CONFIG,0x27);  	// SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS)  max30102_Bus_Write(REG_LED1_PA,0x24);   	//Choose value for ~ 7mA for LED1max30102_Bus_Write(REG_LED2_PA,0x24);   	// Choose value for ~ 7mA for LED2max30102_Bus_Write(REG_PILOT_PA,0x7f);   	// Choose value for ~ 25mA for Pilot LED//	// Interrupt Enable 1 Register. Set PPG_RDY_EN (data available in FIFO)
//	max30102_Bus_Write(0x2, 1<<6);//	// FIFO configuration register
//	// SMP_AVE: 16 samples averaged per FIFO sample
//	// FIFO_ROLLOVER_EN=1
//	//max30102_Bus_Write(0x8,  1<<4);
//	max30102_Bus_Write(0x8, (0<<5) | 1<<4);//	// Mode Configuration Register
//	// SPO2 mode
//	max30102_Bus_Write(0x9, 3);//	// SPO2 Configuration Register
//	max30102_Bus_Write(0xa,
//			(3<<5)  // SPO2_ADC_RGE 2 = full scale 8192 nA (LSB size 31.25pA); 3 = 16384nA
//			| (1<<2) // sample rate: 0 = 50sps; 1 = 100sps; 2 = 200sps
//			| (3<<0) // LED_PW 3 = 411μs, ADC resolution 18 bits
//	);//	// LED1 (red) power (0 = 0mA; 255 = 50mA)
//	max30102_Bus_Write(0xc, 0xb0);//	// LED (IR) power
//	max30102_Bus_Write(0xd, 0xa0);}void max30102_reset(void)
{max30102_Bus_Write(REG_MODE_CONFIG,0x40);max30102_Bus_Write(REG_MODE_CONFIG,0x40);
}void maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data)
{
//  char ach_i2c_data[2];
//  ach_i2c_data[0]=uch_addr;
//  ach_i2c_data[1]=uch_data;
//	
//  IIC_WriteBytes(I2C_WRITE_ADDR, ach_i2c_data, 2);IIC_Write_One_Byte(I2C_WRITE_ADDR,uch_addr,uch_data);
}void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data)
{
//  char ch_i2c_data;
//  ch_i2c_data=uch_addr;
//  IIC_WriteBytes(I2C_WRITE_ADDR, &ch_i2c_data, 1);
//	
//  i2c.read(I2C_READ_ADDR, &ch_i2c_data, 1);
//  
//   *puch_data=(uint8_t) ch_i2c_data;IIC_Read_One_Byte(I2C_WRITE_ADDR,uch_addr,puch_data);
}void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)
{uint32_t un_temp;unsigned char uch_temp;char ach_i2c_data[6];*pun_red_led=0;*pun_ir_led=0;//read and clear status registermaxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);IIC_ReadBytes(I2C_WRITE_ADDR,REG_FIFO_DATA,(u8 *)ach_i2c_data,6);un_temp=(unsigned char) ach_i2c_data[0];un_temp<<=16;*pun_red_led+=un_temp;un_temp=(unsigned char) ach_i2c_data[1];un_temp<<=8;*pun_red_led+=un_temp;un_temp=(unsigned char) ach_i2c_data[2];*pun_red_led+=un_temp;un_temp=(unsigned char) ach_i2c_data[3];un_temp<<=16;*pun_ir_led+=un_temp;un_temp=(unsigned char) ach_i2c_data[4];un_temp<<=8;*pun_ir_led+=un_temp;un_temp=(unsigned char) ach_i2c_data[5];*pun_ir_led+=un_temp;*pun_red_led&=0x03FFFF;  //Mask MSB [23:18]*pun_ir_led&=0x03FFFF;  //Mask MSB [23:18]
}

5 最后


http://www.ppmy.cn/news/234176.html

相关文章

代码随想录刷题第38天|LeetCode509斐波那切数列、LeetCode70爬楼梯、LeetCode746使用最小花费爬楼梯

1、LeetCode509斐波那切数列 题目链接&#xff1a;509、斐波那契数列 递归法&#xff1a; class Solution { public:int fib(int n) {if (n < 1) return n;return ( fib(n-1) fib(n-2) );} }; 动态规划五部曲&#xff1a; 确定dp数组以及下标的含义&#xff1a;dp[i]的…

基于PPGiPPG的心率检测原理

引言 本人所在实验室研究组是从事视频医学方向研究&#xff0c;目前主要针对非接触式的视频脉搏波提取技术被称作图像光电容积脉搏波描记法**(image Photoplethysmography ,iPPG)。具体为从人面部或者指尖等皮肤区域提取由于心脏跳动引起的颜色变化信号**BVP(Blood Volume Pul…

基于视频分析的rPPG心率检测

基于视频分析的rPPG心率检测 YTimo PKU EECS 注:本文内容主要来自于综述文章:Video-Based Heart Rate Measurement: Recent Advances and Future Prospects[1],笔者加入了一些原理性的以及自己实际经验的补充。如果想了解更多详细信息,欢迎阅读原论文。 摘要 心率估计和…

【毕业设计】基于单片机的心率检测系统 - stm32 物联网 嵌入式

1 简介 Hi&#xff0c;大家好&#xff0c;这里是丹成学长&#xff0c;今天向大家介绍一个学长做的单片机项目 基于STM32的血氧心率检测器的设计与实现 大家可用于 课程设计 或 毕业设计 单片机-嵌入式毕设选题大全及项目分享: https://blog.csdn.net/m0_71572576/article/d…

android计算心率方法,计算目标心率最简单的方法 | 跑者

摘要 当我们在目标心率区跑步或进行其它锻炼时&#xff0c;我们的身体将会从中获得最大的益处。 当我们在目标心率区跑步或进行其它锻炼时&#xff0c;我们的身体将会从中获得最大的益处。以每分钟心跳次数(bpm)为单位&#xff0c;目标心率是最大心率的50%到85%&#xff0c;也就…

心跳之旅——iOS用手机摄像头检测心率(PPG)

转载于https://www.jianshu.com/p/6a504afe179e [前情提要] 光阴似箭&#xff0c;日月如梭&#xff0c;最近几年&#xff0c;支持心率检测的设备愈发常见了&#xff0c;大家都在各种测空气测雪碧的&#xff0c;如火如荼&#xff0c;于是我也来凑一凑热闹。[0] 这段时间&#xf…

光学心率测量原理

光学心率测量原理 原博地址 http://blog.csdn.net/richard_liujh/article/details/49615395 简介&#xff1a; 在这个什么都要和“智能”串联的年代&#xff0c;除了我们司空见款的手机外也就是一些智能手表和手环之类的穿戴设备了。这些智能穿戴设备集成了很多的传感器&#…

基于STM32设计的健康检测设备(测温心率计步)

1. 项目介绍 本文介绍的项目是基于STM32设计的健康检测设备,支持体温测量,心率检测,支持运动计步(采用MPU6050陀螺仪实现),支持WIFI传输数据到手机APP打印显示。 硬件环境介绍: MCU采用STM32F103C8T6 心率传感器采用PulseSensor 体温检测传感器采用红外测温传感器 运动…