当前位置:网站首页>B49 - 基于STM32单片机的心率血氧检测与远程定位报警装置
B49 - 基于STM32单片机的心率血氧检测与远程定位报警装置
2022-08-09 15:43:00 【小小工程员】
任务
本系统设计了一个可以进行心率、血氧实时监测、GPS经纬度数据获取的装置,并能够通过无线通信模块将监测数据发送给岸上救生人员配备的无线救生装置中,救生人员能够根据接收到的信息迅速启动应急措施。本章节用于完成救生装置硬件模块的选型和电路的设计。
设计要求如下:
(1)采用心率血氧监测模块:通过MAX30102检测使用者的心率和血氧数据,采集的心率血氧信息通过串口传输到单片机中。该传感器输入电压支持3.3~5.0V,默认上拉电压3.3V,内置ADC精度:16bit。
(2)当佩戴该装置下水时,在水中若检测到用户心率血氧处于不正常水平,可以控制二氧化碳充气救护衣(采用继电器模块控制开启和关闭,继电器模块工作电压为3.3V,高电平关闭,低电平开启),实现漂浮功能。
(3)当佩戴该系统下水时,正常情况下救护系统是没有充气的(继电器关闭),方便用户自由泳。
(5)实现用户自救功能:当用户自我感觉无法自由泳时,且检测系统没有打开充气功能(继电器开启),此时用户可通过按钮手动打开充气功能实现自救。
(6)实现远程控制功能:当用户处于溺水时,岸上救生员可以通过远程控制功能实现充气(远程按键按下,继电器打开,远程传输使用GT-38模块,433M无线通信频率,可传输距离理论为1200米,串口通信),实现及时救生。
(7)当心率和血氧监测异常时,系统配置多颗LED灯发光(心率指示一个红色LED,血氧一个红色LED,报警指示红色LED,正常指示绿色LED,LED灯工作电压3.3V,高电平关闭,低电平点亮),做到提示用户所在位置的功能,方便施救人员及时发现并救助。
(8)当心率和血氧监测异常时,实现蜂鸣器报警(对应LED亮起,同时报警指示红色LED亮,正常指示绿色LED灭,蜂鸣器模块工作电压3.3V,低电平工作,高电平停止)。
(9)实现GPS定位功能,岸上救生员可以通过液晶屏显示的用户GPS坐标寻找水中用户位置(GPS模块工作电压3.3V,串口通信,NMEA协议,定位精度2.5米内,初次上电寻星时间小于40秒,具备记忆功能)。
(10)屏幕采用LCD显示GPS坐标和救生系统状态信息。
本系统设计要求心率监测的误差在±2次/分,当心率发生急速突变超过设定的心率血氧阈值时系统能够自动启动报警功能;血氧饱和度精度为±0.2%,当血氧含量低于设定的阈值时,则启动系统报警功能;本系统设置了远程可控功能用于接收游泳者的身体信息以供水边救生人员人为检测以及远程模块具备按键输入控制游泳者救生系统的功能,当游泳者身体参数突发异常时,救生人员可以通过远程按键及时手动触发救生系统。
本设备配备了LCD屏幕心率和GPS坐标显示,当本设计的报警系统被触发时,远程设备能够接收到从游泳者佩戴的救生系统发出的警报信息、身体血氧、心率指标信息以及GPS坐标信息,并能够在远程设备的LCD显示屏上进行显示。故本系统采集信息需要使用到GPS模块获取GPS坐标,心率血氧模块获取佩戴者心率血氧数据,无线通信模块进行数据通信,LCD显示模块进行数据的可视化显示,按键模块进行配置设置。本系统设计框架如下图所示。
实物


本系统采集心率血氧传感器数据,采集GPS经纬度数据,将采集的数据显示到OLED屏幕上,当将手指放入心率传感器采集的位置后,传感器采集对应的心率血氧值,通过串口传输给单片机,单片机通过接收和解析程序,成功解析后,将数据显示到屏幕上,GPS采集方法与血氧传感器类似,都是串口方式,不同的就是协议以及
内容有所差别。
LCD屏幕显示使用的是串口屏幕,通过指令发送对应信息就可以完成显示功能,如字体大小,颜色等。如图5.4所示为本次通过单片机向液晶屏发的指令,格式为字符串形式。内容为DC16即发送的显示内容字体大小为16 * 16,往后的数字如0,11为显示的位置,0代表X轴方向第0个像素点,11代表Y轴方向第11个像素点,位置是从0开始,屏幕像素大小为128 * 64即宽度为128像素点,高度为64像素点,屏幕支持旋转,本次设置为竖向,即X轴取值范围为0-63,Y轴取值范围为0-123。再往后的单引号括起来的为显示内容,最后放的是显示字体的颜色。本系统通过GT38无线传输模块将主从机信息进行同步,当处于接收范围之内,主机采集的信息会被同步显示到从机,如下图所示为本系统硬件整体显示情况,将手指放在传感器上进行采集后,可以看到采集信息是同步的(为了增加对比度,本次采用了黑色背景,白色字体显示)。
硬件设计
主机

从机

心率血氧传感器模块
MAX30102是一个集成了脉搏血氧仪和心率仪的生物感器模块[3]。本次采用的心率血氧模块为MAX30102[12],该模块采用了一个STM32F07单片机,该单片机是STM32系列的低功耗产品,芯片封装小,性能高,使用该单片机用于读取心率血氧值,然后通过算法处理后,通过模块引出的串口接口传输出去,该模块的串口通信波特率可调,且该模块使用了AT指令的通讯协议。该模块的心率测量范围宽,为20-200次/分钟,血氧的测量范围:50%-100%,该模块使用串口输出,因此无需操心MAX30102的数据读取和处理,主控单片机只需要通过串口发送AT指令来获取心率血氧值即可,这样极大的方便了心率血氧程序的设计。该模块的基本信息如下图所示,从原理图可以看出,该模块是MAX30102传感器和STM32F070F6P6单片机组成,由STM32F070F6P6单片机采集MAX30102的数据,经处理后通过串口使用AT协议进行输出。由此,本系统使用单片机的串口3接口PB10和PB11连接到该传感器模块对应的串口引脚,以获得通信数据,解析其中的心率血氧内容即可。
GPS模块
本设计需要对游泳者的位置数据进行采集,当游泳者出现身体异常时,系统能够及时上报用户位置供救生员准确救援。本次使用的GPS定位模块为ATGM336H-5N。ATGM336H-5N模块具有非常高的灵敏度,功耗很低,其运行时电流仅仅为25mA[4],该模块上电首次定位时间相对来说还是比较短的,在空旷的地方32秒能即可完成首次定位。模块内置了天线检测和天线短路保护的功能。ATGM336H-5N模块输出方式为串口传输。 ATGM336H-5N模块基本情况如下图所示。本系统使用单片机的串口1的接收引脚RXD对应的PA10口和该模块相连。

无线传输模块
本系统使用GT38作为无线通信模块。该模块在空旷地无遮挡的情况下最大的通信距离为1200米,模块的功耗比较低,最大功率为100mW。 如图所示,GT38无线通信模块通过串口线进行交叉连接,即串口的TX连接到其它模块串口的RX,相应的,RX连接到其它模块串口的TX,模块不支持同时收发数据,故仅能工作在半双工状态,收完数据再进行发送即可。本系统使用该模块将主控板上测得的使用者的心率、血氧以及GPS经纬度数据传输给从机,采用此无线传输模块传输距离远。模块通信采用串口,故本次设计使用主控单片机的串口2,从控单片机的串口1来发送或接收数据,连接原理图如图所示。

源程序
/******************************************************************************* \* 文件名称:基于STM32单片机的心率血氧检测与远程定位报警装置 \* 实验目的:1. \* 2. \* 程序说明:完整程序Q:277 227 2579;@: [email protected] hotmail.com \* 日期版本:本项目分享关键细节,熟悉使用单片机的可做参考代码。完整讲解+源代码工程可联系获取,可定制。 *******************************************************************************/
GPS采集驱动
#ifndef __gps_h
#define __gps_h
#include "stm32f10x.h"
#define GPS_USART_REC_LEN 200 //定义最大接收字节数 200
extern char GPS_USART_RX_BUF[GPS_USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
//定义数组长度
#define GPS_Buffer_Length 80
#define UTCTime_Length 11
#define latitude_Length 11
#define N_S_Length 2
#define longitude_Length 12
#define E_W_Length 2
#define false 0
#define true 1
typedef struct SaveData
{
char GPS_Buffer[GPS_Buffer_Length];
char isGetData; //是否获取到GPS数据
char isParseData; //是否解析完成
char UTCTime[UTCTime_Length]; //UTC时间
char latitude[latitude_Length]; //纬度
char N_S[N_S_Length]; //N/S
char longitude[longitude_Length]; //经度
char E_W[E_W_Length]; //E/W
char isUsefull; //定位信息是否有效
} _SaveData;
extern _SaveData Save_Data;
void CLR_Buf(void);
void clrStruct(void);
void GPS_RecHandle(u8 res);
void parseGpsBuffer(void); //parse:分析GPS数据
#endif
#include "gps.h"
#include <string.h>
#include <stdio.h>
#include "led.h"
u16 point1 = 0; //接收数组定位
char GPS_USART_RX_BUF[GPS_USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
_SaveData Save_Data;
void CLR_Buf(void) // 串口缓存清理
{
memset(GPS_USART_RX_BUF, 0, GPS_USART_REC_LEN); //清空
point1 = 0;
}
void clrStruct(void)
{
Save_Data.isGetData = false;
Save_Data.isParseData = false;
Save_Data.isUsefull = false;
memset(Save_Data.GPS_Buffer, 0, GPS_Buffer_Length); //清空
memset(Save_Data.UTCTime, 0, UTCTime_Length);
memset(Save_Data.latitude, 0, latitude_Length);
memset(Save_Data.N_S, 0, N_S_Length);
memset(Save_Data.longitude, 0, longitude_Length);
memset(Save_Data.E_W, 0, E_W_Length);
}
void GPS_RecHandle(u8 Res)
{
if(Res == '$')
{
point1 = 0;
}
GPS_USART_RX_BUF[point1++] = Res;
if(GPS_USART_RX_BUF[0] == '$' && GPS_USART_RX_BUF[4] == 'M' && GPS_USART_RX_BUF[5] == 'C') //确定是否收到"GPRMC/GNRMC"这一帧数据
{
if(Res == '\n')
{
memset(Save_Data.GPS_Buffer, 0, GPS_Buffer_Length); //清空
memcpy(Save_Data.GPS_Buffer, GPS_USART_RX_BUF, point1); //保存数据
Save_Data.isGetData = true;
point1 = 0;
memset(GPS_USART_RX_BUF, 0, GPS_USART_REC_LEN); //清空
}
}
if(point1 >= GPS_USART_REC_LEN)
{
point1 = GPS_USART_REC_LEN;
}
}
void errorLog(int num)
{
// while (1)
// {
// printf("ERROR%d\r\n",num);
// }
LED_Control(ON);
}
void parseGpsBuffer(void) //parse:分析GPS数据
{
char *subString;
char *subStringNext;
char i = 0;
if (Save_Data.isGetData)
{
Save_Data.isGetData = false;
// printf("**************\r\n");
// printf(Save_Data.GPS_Buffer);
for (i = 0 ; i <= 6 ; i++)
{
if (i == 0)
{
if ((subString = strstr(Save_Data.GPS_Buffer, ",")) == NULL)
errorLog(1); //解析错误
}
else
{
subString++;
if ((subStringNext = strstr(subString, ",")) != NULL)
{
char usefullBuffer[2];
switch(i)
{
case 1:memcpy(Save_Data.UTCTime, subString, subStringNext - subString);break; //获取UTC时间
case 2:memcpy(usefullBuffer, subString, subStringNext - subString);break; //获取UTC时间
case 3:memcpy(Save_Data.latitude, subString, subStringNext - subString);break; //获取纬度信息
case 4:memcpy(Save_Data.N_S, subString, subStringNext - subString);break; //获取N/S
case 5:memcpy(Save_Data.longitude, subString, subStringNext - subString);break; //获取经度信息
case 6:memcpy(Save_Data.E_W, subString, subStringNext - subString);break; //获取E/W
default:break;
}
subString = subStringNext;
Save_Data.isParseData = true;
if(usefullBuffer[0] == 'A')
Save_Data.isUsefull = true;
else if(usefullBuffer[0] == 'V')
Save_Data.isUsefull = false;
}
else
{
errorLog(2); //解析错误
}
}
}
}
}
无线通信驱动
#ifndef __WUXIAN_H
#define __WUXIAN_H
#include "Def_config.h"
#include "stm32f10x.h"
#define XinLv_Length 5
#define XueYang_Length 5
#define latitude_Length 11
#define N_S_Length 2
#define longitude_Length 12
#define E_W_Length 2
#define WuXian_RX_BUF_Length 100
#define JUDGE_Length 2
typedef struct
{
char WuXian_Buffer[WuXian_RX_BUF_Length];
char isGetData; //是否获取到 数据
char isParseData; //是否解析完成
char XinLv[XinLv_Length]; //心率值,字符串形式存储
char XueYang[XueYang_Length]; //血氧数据
char latitude[latitude_Length]; //纬度
char N_S[N_S_Length]; //N/S
char longitude[longitude_Length]; //经度
char E_W[E_W_Length]; //E/W
char is_XinLvWarn[JUDGE_Length];
char is_XueYangWarn[JUDGE_Length];
char is_HandWarn[JUDGE_Length];
} _SendData;
extern _SendData Send_Data;
#define false 0
#define true 1
void parseWuXianBuffer(void);
void WuXian_Rec(u8 Res);
void WuXian_Clear(void);
#endif
#include "wuxian.h"
#include <string.h>
#include <stdio.h>
#include "lcdUart.h"
#include "usart1.h"
u8 point1 = 0;
u8 WuXian_RX_BUF[WuXian_RX_BUF_Length];
_SendData Send_Data;
void WuXian_Clear(void)
{
memset(Send_Data.WuXian_Buffer,'\0', WuXian_RX_BUF_Length);
memset(Send_Data.XinLv,'\0', WuXian_RX_BUF_Length);
memset(Send_Data.XueYang,'\0', WuXian_RX_BUF_Length);
memset(Send_Data.latitude,'\0', WuXian_RX_BUF_Length);
memset(Send_Data.N_S,'\0', WuXian_RX_BUF_Length);
memset(Send_Data.longitude,'\0', WuXian_RX_BUF_Length);
memset(Send_Data.E_W,'\0', WuXian_RX_BUF_Length);
memset(Send_Data.is_XinLvWarn,'\0', WuXian_RX_BUF_Length);
memset(Send_Data.is_XueYangWarn,'\0', WuXian_RX_BUF_Length);
memset(Send_Data.is_HandWarn,'\0', WuXian_RX_BUF_Length);
Send_Data.isGetData = FALSE;
Send_Data.isParseData = FALSE;
}
void WuXian_Rec(u8 Res)
{
if(Res == '$')
{
point1 = 0;
}
WuXian_RX_BUF[point1++] = Res;
if(Res == '\n')
{
memset(Send_Data.WuXian_Buffer,'\0', WuXian_RX_BUF_Length); //清空
memcpy(Send_Data.WuXian_Buffer, WuXian_RX_BUF, point1); //保存数据
Send_Data.isGetData = true;
point1 = 0;
USART1_SendString((u8 *)Send_Data.WuXian_Buffer);
// printf("DCV16(0,7,'%s',%d);\r\n",WuXian_RX_BUF,1);
// LCD_CheckBusy();
memset(WuXian_RX_BUF,'\0', WuXian_RX_BUF_Length); //清空
}
if(point1 >= WuXian_RX_BUF_Length)
{
point1 = WuXian_RX_BUF_Length;
}
}
void parseWuXianBuffer(void) //parse:分析数据
{
char *subString;
char *subStringNext;
char i = 0;
if (Send_Data.isGetData)
{
Send_Data.isGetData = false;
for(i=0;i<9;i++)
{
if (i == 0)
{
if ((subString = strstr(Send_Data.WuXian_Buffer, "$")) != NULL)
{
subString++;
if ((subStringNext = strstr(subString, ",")) != NULL)
{
memcpy(Send_Data.XinLv, subString, subStringNext - subString);
subString = subStringNext;
}
}
}
else if(i<8)
{
subString++;
if((subStringNext = strstr(subString, ",")) != NULL)
{
switch(i)
{
case 1:
memcpy(Send_Data.XueYang, subString, subStringNext - subString);
break;
case 2:
memcpy(Send_Data.latitude, subString, subStringNext - subString);
break;
case 3:
memcpy(Send_Data.N_S, subString, subStringNext - subString);
break;
case 4:
memcpy(Send_Data.longitude, subString, subStringNext - subString);
break;
case 5:
memcpy(Send_Data.E_W, subString, subStringNext - subString);
break;
case 6:
memcpy(Send_Data.is_XinLvWarn, subString, subStringNext - subString);
break;
case 7:
memcpy(Send_Data.is_XueYangWarn, subString, subStringNext - subString);
break;
default:break;
}
subString = subStringNext;
}
}
else
{
subString++;
if((subStringNext = strstr(subString, "\r")) != NULL)
{
memcpy(Send_Data.is_HandWarn, subString, subStringNext - subString);
}
}
}
Send_Data.isParseData = true;
}
}
心率血氧采集驱动
#ifndef __XINLV_H
#define __XINLV_H
#include "stm32f10x.h"
#define XinLv_Buffer_Length 200
#define XinLv_Length 5
#define XueYang_Length 5
#define false 0
#define true 1
typedef struct
{
char XinLv_Rec_Buffer[XinLv_Buffer_Length];
char isGetData; //是否获取到数据
char isParseData; //是否解析完成
char XinLv[XinLv_Length]; //心率值,字符串形式存储
char XueYang[XueYang_Length]; //血氧数据
char isUsefull; //信息是否有效
int count_erroTime;
} _XinLvData;
extern _XinLvData XinLv_Data;
void XinLv_RecHandle(u8 Res);
void parseXinLvBuffer(void);
#endif
#include "xinLv.h"
#include <string.h>
#include <stdio.h>
#include "usart3.h"
u8 point2 = 0;
char XinLv_RX_BUF[XinLv_Buffer_Length]; //接收缓冲,最大XinLv_Buffer_Length个字节.末字节为换行符
_XinLvData XinLv_Data;
u8 XinLv_Find(char *a) // 串口命令识别函数
{
if(strstr(XinLv_Data.XinLv_Rec_Buffer,a)!=NULL)
return 1;
else
return 0;
}
void XinLv_Clear_Data(void)
{
XinLv_Data.isGetData = false;
XinLv_Data.isParseData = false;
XinLv_Data.isUsefull = false;
memset(XinLv_Data.XinLv, 0, XinLv_Length); //清空
memset(XinLv_Data.XueYang, 0, XueYang_Length); //清空
memset(XinLv_Data.XinLv_Rec_Buffer, 0, XinLv_Buffer_Length); //清空
}
void XinLv_RecHandle(u8 Res)
{
if(Res == '+')
{
point2 = 0;
}
XinLv_RX_BUF[point2++] = Res;
if(Res == 'K')
{
memset(XinLv_Data.XinLv_Rec_Buffer, 0, XinLv_Buffer_Length); //清空
memcpy(XinLv_Data.XinLv_Rec_Buffer, XinLv_RX_BUF, point2); //保存数据
XinLv_Data.isGetData = true;
point2 = 0;
memset(XinLv_RX_BUF, 0, XinLv_Buffer_Length); //清空
if(XinLv_Find("NULL"))
{
XinLv_Clear_Data();
}
}
if(point2 >= XinLv_Buffer_Length)
{
point2 = XinLv_Buffer_Length;
}
}
void parseXinLvBuffer(void)
{
char *subString;
char *subStringNext;
if (XinLv_Data.isGetData)
{
XinLv_Data.isGetData = false;
if(XinLv_Find("HEART"))
{
subString = strstr(XinLv_Data.XinLv_Rec_Buffer, "=")+1;
subStringNext = strstr(XinLv_Data.XinLv_Rec_Buffer, "\r");
memset(XinLv_Data.XinLv,'\0', XinLv_Length); //清空
memset(XinLv_Data.XinLv,' ', 3); //清空
memcpy(XinLv_Data.XinLv, subString, subStringNext - subString);
XinLv_Data.isParseData = true;
XinLv_Data.isUsefull = true;
XinLv_Data.count_erroTime = 0;
}
if(XinLv_Find("SPO2"))
{
subString = strstr(XinLv_Data.XinLv_Rec_Buffer, "=")+1;
subStringNext = strstr(XinLv_Data.XinLv_Rec_Buffer, "\r");
memset(XinLv_Data.XueYang,'\0', XueYang_Length); //清空
memset(XinLv_Data.XueYang,' ', 3); //清空
memcpy(XinLv_Data.XueYang, subString, subStringNext - subString);
// USART3_SendString((char *)XinLv_Data.XueYang);
XinLv_Data.isParseData = true;
XinLv_Data.isUsefull = true;
XinLv_Data.count_erroTime = 0;
}
}
}
边栏推荐
- [Server data recovery] Data recovery case of file system data loss caused by SAN LUN mapping error
- Base64工具类
- margin:auto实现盒子水平垂直居中
- Smart Light Pole Gateway Smart Transportation Application
- Chapter 2: Creating Interactive Maps (2.4-2.6)
- 【嵌入式入门篇】嵌入式0基础沉浸式刷题篇1
- uni-app覆盖组件样式h5生效,微信小程序不生效的问题
- 巧用Prometheus来扩展kubernetes调度器
- C语言循环结构之万恶之源goto语句
- 二分法
猜你喜欢

NFT+IDO预售代币合约模式系统开发

<IDEA 使用小技巧&&常用键联合操作>

2022钉钉杯A题思路及代码:银行卡电信诈骗危险预测

开源星「001 号」落地 FlyFish,欢迎登陆赢神秘大礼包!

Swagger2 knife4j NullPointerException 空指针问题

2022年华数杯C题插层熔喷完整解题思路(附代码+详细讲解视频)

网络——IPv6(一)

IDEA中操作数据库 以MySQL为例,可以放弃Navicat了

August 9, 2022: Build .NET apps in C# -- use the Visual Studio Code debugger to interactively debug .NET apps (won't, fail)

二.sizeof和strlen的区别
随机推荐
推荐一些面向 Web 开发者的杀手级网站
Access Characteristics of Constructor under Inheritance Relationship
【嵌入式入门篇】嵌入式0基础沉浸式刷题篇1
Md5加密方法
1. Introducing GEE and Geemap
良匠-手把手教你写NFT抢购软(一)
第一篇博客
微信开发者工具报错,提示 未找到入口 app.json 文件
网络——局域网和广域网
五.初始指针
qiucode.cn网站之文章详情实现代码块可点击按钮进行复制
网络——涉及的相关协议和设备汇总
The Chinese Academy of Sciences slaps Google in the face: ordinary computers catch up with quantum superiority, and can solve calculations that would have taken 10,000 years in a few hours...
PHP 补全日期区间中缺少的日期/返回缺少的日期
转行做程序员,从月薪5k到30k,45岁测试员道出了一路的心酸
uni-app覆盖组件样式h5生效,微信小程序不生效的问题
5G NR Paging
Three ways to find prime numbers
AVL树的插入操作
网络——IPv6(一)