文章目錄
- 任務
- 實物
- 硬體設計
-
- 主機
- 從機
- 心率血氧傳感器子產品
- GPS子產品
- 無線傳輸子產品
- 源程式
-
- GPS采集驅動
- 無線通信驅動
- 心率血氧采集驅動
任務
本系統設計了一個可以進行心率、血氧實時監測、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;@: itworkstation@ 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;
}
}
}