基于STM32的物联网健康监测系统设计(附源码)

目录

1、项目简介

2、整体项目架构设计

2、硬件架构设计

(1)硬件型号

(2)传感器驱动程序设计

3、基于MQTT协议的数据传输

4、微信小程序上位机设计

1.主界面

2. 健康监测系统 

3. 环境监测系统         

4. 显示实时动态曲线         


1、项目简介

        该项目是利用STM32开发板进行开发的基于MQTT协议的物联网健康监测系统,并开发了微信小程序作为该项目的软件上位机。该产品可以用来实时监测人体的心率、血氧等生理参数,也可以监测家庭环境中的温湿度、烟雾浓度等环境参数,从而为您营造出一个健康的居住环境。此项目是本人在寒假利用业余时间开发的,从理论知识的学习到做出最终成品耗时将近两个月,可用于嵌入式软件方向的毕业设计。

        该项目源码已全部开源,在本文中仅介绍核心代码原理,源码已发布在我的GitHub主页,如对源码有疑惑,或者对该项目有改进性意见,可以评论或私信本人,欢迎大家一起交流学习!

        我的github主页:https://github.com/SichengLong26
        源代码地址:https://github.com/SichengLong26/health-iot

2、整体项目架构设计

        该项目主要分为硬件架构设计和软件架构设计,硬件架构设计包括电路设计、PCB焊接、驱动程序编写、数据传输(数据上云)的程序编写;软件架构设计则包括前端UI界面的设计和后端的数据处理,此次软件上位机是基于微信小程序来开发的。

基于STM32的物联网健康监测系统设计(附源码)_第1张图片

2、硬件架构设计

(1)硬件型号

主控芯片:STM32F103RCT6
传感器:MAX30102心率传感器、DHT11温湿度传感器、MQ2烟雾传感器
通信模块:ESP8266 WIFI模块

(2)传感器驱动程序设计

MAX30102心率血氧传感器(通过IIC驱动)
        MAX3010VCC引脚连接STM32F103mini单片机的5伏引脚,GND连接5伏对应的GND,SCL连PC12,SDA连PC11,INT连PA5。MAX30102的其他引脚没有用到。
        本代码能够正常接收MAX30102心率血氧传感器返回的red与ir的数值,能够比较正常计算出心率血氧数值。当心率或血氧值的计算结果有误时对应的变量值为-999。

main.c

#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "myiic.h"
#include "max30102.h"
#include "algorithm.h"

#define MAX_BRIGHTNESS 255
#define START 100
#define DATA_LENGTH 500


uint32_t aun_ir_buffer[DATA_LENGTH]; //IR LED sensor data
int32_t n_ir_buffer_length;    //data length
uint32_t aun_red_buffer[DATA_LENGTH];    //Red LED sensor data
int32_t n_sp02; //SPO2 value
int8_t ch_spo2_valid;   //indicator to show if the SP02 calculation is valid
int32_t n_heart_rate;   //heart rate value
int8_t  ch_hr_valid;    //indicator to show if the heart rate calculation is valid
uint8_t uch_dummy;


 int main(void)
 { 
	uint32_t un_min, un_max, un_prev_data;  //variables to calculate the on-board LED brightness that reflects the heartbeats
	int i;
	int32_t n_brightness;
	float f_temp;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
	delay_init();	    	 //延时函数初始化	  
	uart_init(9600);	 	//串口初始化为115200
 	IIC_Init();		 	
	
	maxim_max30102_reset(); //resets the MAX30102
	// initialize serial communication at 115200 bits per second:
	//read and clear status register
	maxim_max30102_read_reg(0,&uch_dummy);
	maxim_max30102_init();  //initializes the MAX30102

	n_brightness=0;
	un_min=0x3FFFF;
	un_max=0;
	n_ir_buffer_length=DATA_LENGTH; //buffer length of 100 stores 5 seconds of samples running at 100sps
	//read the first 500 samples, and determine the signal range
	for(i=0;iaun_red_buffer[i])
					un_min=aun_red_buffer[i];    //update signal min
			if(un_maxaun_red_buffer[i])
				un_min=aun_red_buffer[i];
				if(un_maxun_prev_data)//just to determine the brightness of LED according to the deviation of adjacent two AD data
				{
						f_temp=aun_red_buffer[i]-un_prev_data;
						f_temp/=(un_max-un_min);
						f_temp*=MAX_BRIGHTNESS;
						n_brightness-=(int)f_temp;
						if(n_brightness<0)
								n_brightness=0;
				}
				else
				{
						f_temp=un_prev_data-aun_red_buffer[i];
						f_temp/=(un_max-un_min);
						f_temp*=MAX_BRIGHTNESS;
						n_brightness+=(int)f_temp;
						if(n_brightness>MAX_BRIGHTNESS)
								n_brightness=MAX_BRIGHTNESS;
				}
				//re_oxen=(float)aun_red_buffer[i]/(float)aun_ir_buffer[i];
				//oxen=45.06*re_oxen*re_oxen+30.354*re_oxen+94.845;
				//send samples and calculation result to terminal program through UART
//				printf("red=%i,", aun_red_buffer[i]);
//				printf(" ir=%i,", aun_ir_buffer[i]);

		}
		maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); 
		printf(" HR=%i,", n_heart_rate); 
		printf(" HRvalid=%i,", ch_hr_valid);
		printf(" SpO2=%i,", n_sp02);
		printf(" SPO2Valid=%i\r\n", ch_spo2_valid);
	}
}

max30102.c(驱动程序)

#include "max30102.h"
#include "myiic.h"

#define max30102_WR_address 0xAE
bool maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data)
/**
* \brief        Write a value to a MAX30102 register
* \par          Details
*               This function writes a value to a MAX30102 register
*
* \param[in]    uch_addr    - register address
* \param[in]    uch_data    - register data
*
* \retval       true on success
*/
{
    /* 第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(uch_addr);
    if (IIC_Wait_Ack() != 0)
    {
        goto cmd_fail;	/* EEPROM器件无应答 */
    }

    /* 第5步:开始写入数据 */
    IIC_Send_Byte(uch_data);

    /* 第6步:发送ACK */
    if (IIC_Wait_Ack() != 0)
    {
        goto cmd_fail;	/* EEPROM器件无应答 */
    }

    /* 发送I2C总线停止信号 */
    IIC_Stop();
    return true;	/* 执行成功 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
    /* 发送I2C总线停止信号 */
    IIC_Stop();
    return false;
}

bool maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data)
/**
* \brief        Read a MAX30102 register
* \par          Details
*               This function reads a MAX30102 register
*
* \param[in]    uch_addr    - register address
* \param[out]   puch_data    - pointer that stores the register data
*
* \retval       true on success
*/
{
    /* 第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)uch_addr);
    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步:读取数据 */
    {
        *puch_data = IIC_Read_Byte();	/* 读1个字节 */

        IIC_NAck();	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
    }
    /* 发送I2C总线停止信号 */
    IIC_Stop();
    return true;	/* 执行成功 返回data值 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
    /* 发送I2C总线停止信号 */
    IIC_Stop();
    return false;
}

bool maxim_max30102_init(void)
/**
* \brief        Initialize the MAX30102
* \par          Details
*               This function initializes the MAX30102
*
* \param        None
*
* \retval       true on success
*/
{
		GPIO_InitTypeDef GPIO_InitStructure;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTC时钟
		GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//关闭jtag,使能SWD,可以用SWD模式调试
		GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_5;//PA5
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //PA5设置成浮空输入	  
		GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA5
    if(!maxim_max30102_write_reg(REG_INTR_ENABLE_1, 0xc0)) // INTR setting
        return false;
    if(!maxim_max30102_write_reg(REG_INTR_ENABLE_2, 0x00))
        return false;
    if(!maxim_max30102_write_reg(REG_FIFO_WR_PTR, 0x00)) //FIFO_WR_PTR[4:0]
        return false;
    if(!maxim_max30102_write_reg(REG_OVF_COUNTER, 0x00)) //OVF_COUNTER[4:0]
        return false;
    if(!maxim_max30102_write_reg(REG_FIFO_RD_PTR, 0x00)) //FIFO_RD_PTR[4:0]
        return false;
    if(!maxim_max30102_write_reg(REG_FIFO_CONFIG, 0x6f)) //sample avg = 8, fifo rollover=false, fifo almost full = 17
        return false;
    if(!maxim_max30102_write_reg(REG_MODE_CONFIG, 0x03))  //0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
        return false;
    if(!maxim_max30102_write_reg(REG_SPO2_CONFIG, 0x2F)) // SPO2_ADC range = 4096nA, SPO2 sample rate (400 Hz), LED pulseWidth (411uS)
        return false;

    if(!maxim_max30102_write_reg(REG_LED1_PA, 0x17))  //Choose value for ~ 4.5mA for LED1
        return false;
    if(!maxim_max30102_write_reg(REG_LED2_PA, 0x17))  // Choose value for ~ 4.5mA for LED2
        return false;
    if(!maxim_max30102_write_reg(REG_PILOT_PA, 0x7f))  // Choose value for ~ 25mA for Pilot LED
        return false;
    return true;
}

bool maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)

/**
* \brief        Read a set of samples from the MAX30102 FIFO register
* \par          Details
*               This function reads a set of samples from the MAX30102 FIFO register
*
* \param[out]   *pun_red_led   - pointer that stores the red LED reading data
* \param[out]   *pun_ir_led    - pointer that stores the IR LED reading data
*
* \retval       true on success
*/
{
    uint32_t un_temp;
    uint8_t uch_temp;
    *pun_ir_led = 0;
    *pun_red_led = 0;
    maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);
    maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);



    /* 第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)REG_FIFO_DATA);
    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器件无应答 */
    }

    un_temp = IIC_Read_Byte();
    IIC_Ack();
    un_temp <<= 16;
    *pun_red_led += un_temp;
    un_temp = IIC_Read_Byte();
    IIC_Ack();
    un_temp <<= 8;
    *pun_red_led += un_temp;
    un_temp = IIC_Read_Byte();
    IIC_Ack();
    *pun_red_led += un_temp;

    un_temp = IIC_Read_Byte();
    IIC_Ack();
    un_temp <<= 16;
    *pun_ir_led += un_temp;
    un_temp = IIC_Read_Byte();
    IIC_Ack();
    un_temp <<= 8;
    *pun_ir_led += un_temp;
    un_temp = IIC_Read_Byte();
    IIC_Ack();
    *pun_ir_led += un_temp;
    *pun_red_led &= 0x03FFFF; //Mask MSB [23:18]
    *pun_ir_led &= 0x03FFFF; //Mask MSB [23:18]

    /* 发送I2C总线停止信号 */
    IIC_Stop();
    return true;
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
    /* 发送I2C总线停止信号 */
    IIC_Stop();
    return false;
}

bool maxim_max30102_reset()
/**
* \brief        Reset the MAX30102
* \par          Details
*               This function resets the MAX30102
*
* \param        None
*
* \retval       true on success
*/
{
    if(!maxim_max30102_write_reg(REG_MODE_CONFIG, 0x40))
        return false;
    else
        return true;
}

 alogrihm.c心率血氧算法:

/** \file algorithm.cpp ******************************************************
*
* Project: MAXREFDES117#
* Filename: algorithm.cpp
* Description: This module calculates the heart rate/SpO2 level
*
*
* --------------------------------------------------------------------
*
* This code follows the following naming conventions:
*
* char              ch_pmod_value
* char (array)      s_pmod_s_string[16]
* float             f_pmod_value
* int32_t           n_pmod_value
* int32_t (array)   an_pmod_value[16]
* int16_t           w_pmod_value
* int16_t (array)   aw_pmod_value[16]
* uint16_t          uw_pmod_value
* uint16_t (array)  auw_pmod_value[16]
* uint8_t           uch_pmod_value
* uint8_t (array)   auch_pmod_buffer[16]
* uint32_t          un_pmod_value
* int32_t *         pn_pmod_value
*
* ------------------------------------------------------------------------- */
/*******************************************************************************
* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************
*/

#include "algorithm.h"

//uch_spo2_table is approximated as  -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ;
const uint8_t uch_spo2_table[184] = { 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99,
                                      99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
                                      100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97,
                                      97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91,
                                      90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81,
                                      80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67,
                                      66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50,
                                      49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29,
                                      28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5,
                                      3, 2, 1
                                    } ;


void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid,
        int32_t *pn_heart_rate, int8_t *pch_hr_valid)
/**
* \brief        Calculate the heart rate and SpO2 level
* \par          Details
*               By detecting  peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the an_ratio for the SPO2 is computed.
*               Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
*               Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each an_ratio.
*
* \param[in]    *pun_ir_buffer           - IR sensor data buffer
* \param[in]    n_ir_buffer_length      - IR sensor data buffer length
* \param[in]    *pun_red_buffer          - Red sensor data buffer
* \param[out]    *pn_spo2                - Calculated SpO2 value
* \param[out]    *pch_spo2_valid         - 1 if the calculated SpO2 value is valid
* \param[out]    *pn_heart_rate          - Calculated heart rate value
* \param[out]    *pch_hr_valid           - 1 if the calculated heart rate value is valid
*
* \retval       None
*/
{
    uint32_t un_ir_mean;
    int32_t k, n_i_ratio_count;
    int32_t i, n_exact_ir_valley_locs_count, n_middle_idx;
    int32_t n_th1, n_npks;
    int32_t an_ir_valley_locs[15] ;
    int32_t n_peak_interval_sum;

    int32_t n_y_ac, n_x_ac;
    int32_t n_spo2_calc;
    int32_t n_y_dc_max, n_x_dc_max;
    int32_t n_y_dc_max_idx, n_x_dc_max_idx;
    int32_t an_ratio[5], n_ratio_average;
    int32_t n_nume, n_denom ;

    // calculates DC mean and subtract DC from ir
    un_ir_mean = 0;
    for (k = 0 ; k < n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ;
    un_ir_mean = un_ir_mean / n_ir_buffer_length ;

    // remove DC and invert signal so that we can use peak detector as valley detector
    for (k = 0 ; k < n_ir_buffer_length ; k++ )
        an_x[k] = -1 * (pun_ir_buffer[k] - un_ir_mean) ;

    // 4 pt Moving Average
    for(k = 0; k < BUFFER_SIZE - MA4_SIZE; k++)
    {
        an_x[k] = ( an_x[k] + an_x[k + 1] + an_x[k + 2] + an_x[k + 3]) / (int)4;
    }
    // calculate threshold
    n_th1 = 0;
    for ( k = 0 ; k < BUFFER_SIZE ; k++)
    {
        n_th1 +=  an_x[k];
    }
    n_th1 =  n_th1 / ( BUFFER_SIZE);
    if( n_th1 < 30) n_th1 = 30; // min allowed
    if( n_th1 > 60) n_th1 = 60; // max allowed

    for ( k = 0 ; k < 15; k++) an_ir_valley_locs[k] = 0;
    // since we flipped signal, we use peak detector as vSalley detector
    maxim_find_peaks( an_ir_valley_locs, &n_npks, an_x, BUFFER_SIZE, n_th1, 4, 15 );//peak_height, peak_distance, max_num_peaks
    n_peak_interval_sum = 0;
    if (n_npks >= 2)
    {
        for (k = 1; k < n_npks; k++) n_peak_interval_sum += (an_ir_valley_locs[k] - an_ir_valley_locs[k - 1] ) ;
        n_peak_interval_sum = n_peak_interval_sum / (n_npks - 1);
        *pn_heart_rate = (int32_t)( (FS * 60) / n_peak_interval_sum );
        *pch_hr_valid  = 1;
    }
    else
    {
        *pn_heart_rate = -999; // unable to calculate because # of peaks are too small
        *pch_hr_valid  = 0;
    }

    //  load raw value again for SPO2 calculation : RED(=y) and IR(=X)
    for (k = 0 ; k < n_ir_buffer_length ; k++ )
    {
        an_x[k] =  pun_ir_buffer[k] ;
        an_y[k] =  pun_red_buffer[k] ;
    }

    // find precise min near an_ir_valley_locs
    n_exact_ir_valley_locs_count = n_npks;

    //using exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration an_ratio
    //finding AC/DC maximum of raw

    n_ratio_average = 0;
    n_i_ratio_count = 0;
    for(k = 0; k < 5; k++) an_ratio[k] = 0;
    for (k = 0; k < n_exact_ir_valley_locs_count; k++)
    {
        if (an_ir_valley_locs[k] > BUFFER_SIZE )
        {
            *pn_spo2 =  -999 ; // do not use SPO2 since valley loc is out of range
            *pch_spo2_valid  = 0;
            return;
        }
    }
    // find max between two valley locations
    // and use an_ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2
    for (k = 0; k < n_exact_ir_valley_locs_count - 1; k++)
    {
        n_y_dc_max = -16777216 ;
        n_x_dc_max = -16777216;
        if (an_ir_valley_locs[k + 1] - an_ir_valley_locs[k] > 3)
        {
            for (i = an_ir_valley_locs[k]; i < an_ir_valley_locs[k + 1]; i++)
            {
                if (an_x[i] > n_x_dc_max)
                {
                    n_x_dc_max = an_x[i];
                    n_x_dc_max_idx = i;
                }
                if (an_y[i] > n_y_dc_max)
                {
                    n_y_dc_max = an_y[i];
                    n_y_dc_max_idx = i;
                }
            }
            n_y_ac = (an_y[an_ir_valley_locs[k + 1]] - an_y[an_ir_valley_locs[k] ] ) * (n_y_dc_max_idx - an_ir_valley_locs[k]); //red
            n_y_ac =  an_y[an_ir_valley_locs[k]] + n_y_ac / (an_ir_valley_locs[k + 1] - an_ir_valley_locs[k])  ;
            n_y_ac =  an_y[n_y_dc_max_idx] - n_y_ac;   // subracting linear DC compoenents from raw
            n_x_ac = (an_x[an_ir_valley_locs[k + 1]] - an_x[an_ir_valley_locs[k] ] ) * (n_x_dc_max_idx - an_ir_valley_locs[k]); // ir
            n_x_ac =  an_x[an_ir_valley_locs[k]] + n_x_ac / (an_ir_valley_locs[k + 1] - an_ir_valley_locs[k]);
            n_x_ac =  an_x[n_y_dc_max_idx] - n_x_ac;     // subracting linear DC compoenents from raw
            n_nume = ( n_y_ac * n_x_dc_max) >> 7 ; //prepare X100 to preserve floating value
            n_denom = ( n_x_ac * n_y_dc_max) >> 7;
            if (n_denom > 0  && n_i_ratio_count < 5 &&  n_nume != 0)
            {
                an_ratio[n_i_ratio_count] = (n_nume * 100) / n_denom ; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ;
                n_i_ratio_count++;
            }
        }
    }
    // choose median value since PPG signal may varies from beat to beat
    maxim_sort_ascend(an_ratio, n_i_ratio_count);
    n_middle_idx = n_i_ratio_count / 2;

    if (n_middle_idx > 1)
        n_ratio_average = ( an_ratio[n_middle_idx - 1] + an_ratio[n_middle_idx]) / 2; // use median
    else
        n_ratio_average = an_ratio[n_middle_idx ];

    if( n_ratio_average > 2 && n_ratio_average < 184)
    {
        n_spo2_calc = uch_spo2_table[n_ratio_average] ;
        *pn_spo2 = n_spo2_calc ;
        *pch_spo2_valid  = 1;//  float_SPO2 =  -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ;  // for comparison with table
    }
    else
    {
        *pn_spo2 =  -999 ; // do not use SPO2 since signal an_ratio is out of range
        *pch_spo2_valid  = 0;
    }
}


void maxim_find_peaks( int32_t *pn_locs, int32_t *n_npks,  int32_t  *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num )
/**
* \brief        Find peaks
* \par          Details
*               Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE
*
* \retval       None
*/
{
    maxim_peaks_above_min_height( pn_locs, n_npks, pn_x, n_size, n_min_height );
    maxim_remove_close_peaks( pn_locs, n_npks, pn_x, n_min_distance );
    *n_npks = min( *n_npks, n_max_num );
}

void maxim_peaks_above_min_height( int32_t *pn_locs, int32_t *n_npks,  int32_t  *pn_x, int32_t n_size, int32_t n_min_height )
/**
* \brief        Find peaks above n_min_height
* \par          Details
*               Find all peaks above MIN_HEIGHT
*
* \retval       None
*/
{
    int32_t i = 1, riseFound = 0, holdOff1 = 0, holdOff2 = 0, holdOffThresh = 4;
    *n_npks = 0;

    while (i < n_size - 1)
    {
        if (holdOff2 == 0)
        {
            if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i - 1])     // find left edge of potential peaks
            {
                riseFound = 1;
            }
            if (riseFound == 1)
            {
                if ((pn_x[i] < n_min_height) && (holdOff1 < holdOffThresh))     // if false edge
                {
                    riseFound = 0;
                    holdOff1 = 0;
                }
                else
                {
                    if (holdOff1 == holdOffThresh)
                    {
                        if ((pn_x[i] < n_min_height) && (pn_x[i - 1] >= n_min_height))
                        {
                            if ((*n_npks) < 15 )
                            {
                                pn_locs[(*n_npks)++] = i;   // peak is right edge
                            }
                            holdOff1 = 0;
                            riseFound = 0;
                            holdOff2 = 8;
                        }
                    }
                    else
                    {
                        holdOff1 = holdOff1 + 1;
                    }
                }
            }
        }
        else
        {
            holdOff2 = holdOff2 - 1;
        }
        i++;
    }
}

void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance)
/**
* \brief        Remove peaks
* \par          Details
*               Remove peaks separated by less than MIN_DISTANCE
*
* \retval       None
*/
{

    int32_t i, j, n_old_npks, n_dist;

    /* Order peaks from large to small */
    maxim_sort_indices_descend( pn_x, pn_locs, *pn_npks );

    for ( i = -1; i < *pn_npks; i++ )
    {
        n_old_npks = *pn_npks;
        *pn_npks = i + 1;
        for ( j = i + 1; j < n_old_npks; j++ )
        {
            n_dist =  pn_locs[j] - ( i == -1 ? -1 : pn_locs[i] ); // lag-zero peak of autocorr is at index -1
            if ( n_dist > n_min_distance || n_dist < -n_min_distance )
                pn_locs[(*pn_npks)++] = pn_locs[j];
        }
    }

    // Resort indices int32_to ascending order
    maxim_sort_ascend( pn_locs, *pn_npks );
}

void maxim_sort_ascend(int32_t  *pn_x, int32_t n_size)
/**
* \brief        Sort array
* \par          Details
*               Sort array in ascending order (insertion sort algorithm)
*
* \retval       None
*/
{
    int32_t i, j, n_temp;
    for (i = 1; i < n_size; i++)
    {
        n_temp = pn_x[i];
        for (j = i; j > 0 && n_temp < pn_x[j - 1]; j--)
            pn_x[j] = pn_x[j - 1];
        pn_x[j] = n_temp;
    }
}

void maxim_sort_indices_descend(  int32_t  *pn_x, int32_t *pn_indx, int32_t n_size)
/**
* \brief        Sort indices
* \par          Details
*               Sort indices according to descending order (insertion sort algorithm)
*
* \retval       None
*/
{
    int32_t i, j, n_temp;
    for (i = 1; i < n_size; i++)
    {
        n_temp = pn_indx[i];
        for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j - 1]]; j--)
            pn_indx[j] = pn_indx[j - 1];
        pn_indx[j] = n_temp;
    }
}




DHT11温湿度传感器驱动程序: 

dht11.c

#include "dht11.h"
#include "delay.h"


//由于DHT11为单总线通信,即发送、接收都为同一根数据线,STM32的GPIO无法像51的IO同时配置为输入输出模式,
//因此需要将与DHT11数据线相连的GPIO写两套初始化函数,向DHT11发送数据时先调用DHT11_IO_OUT()函数,再
//发送数据,接收DHT11的数据时先调用DHT11_IO_IN()函数,再接收数据
void DHT11_IO_OUT(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
 	
 //	RCC_APB2PeriphClockCmd(DHT11_GPIO_CLK|RCC_APB2Periph_AFIO, ENABLE);	 //使能PG端口时钟
	//GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //由于PA15为JTAG调试接口,需要先禁用JTAG功能才能作为普通的GPIO口
	//使用,若使用的是普通的GPIO,可将   RCC_APB2Periph_AFIO  与  GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)  去掉    //禁用JTAG
	
 	GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN;				 //PG11端口配置
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure);				 //初始化IO口
}

void DHT11_IO_IN(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
 	
 	RCC_APB2PeriphClockCmd(DHT11_GPIO_CLK|RCC_APB2Periph_AFIO, ENABLE);	 //使能PG端口时钟
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);     //禁用JTAG
	
 	GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN;				 //PG11端口配置
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 		 //推挽输出
 	GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure);				 //初始化IO口
}

//复位DHT11
void DHT11_Rst(void)	   
{                 
	DHT11_IO_OUT(); 	//SET OUTPUT
  DHT11_DQ_OUT=0; 	//拉低DQ
  delay_ms(20);    	//拉低至少18ms
  DHT11_DQ_OUT=1; 	//DQ=1 
	delay_us(30);     	//主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void) 	   
{   
	u8 retry=0;
	DHT11_IO_IN();//SET INPUT	 
    while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
	{
		retry++;
		delay_us(1);
	};	 
	if(retry>=100)return 1;
	else retry=0;
    while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
	{
		retry++;
		delay_us(1);
	};
	if(retry>=100)return 1;	    
	return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void) 			 
{
 	u8 retry=0;
	while(DHT11_DQ_IN&&retry<100)//等待变为低电平
	{
		retry++;
		delay_us(1);
	}
	retry=0;
	while(!DHT11_DQ_IN&&retry<100)//等待变高电平
	{
		retry++;
		delay_us(1);
	}
	delay_us(40);//等待40us
	if(DHT11_DQ_IN)return 1;
	else return 0;		   
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)    
{        
	u8 i,dat;
	dat=0;
	for (i=0;i<8;i++) 
	{
		dat<<=1; 
		dat|=DHT11_Read_Bit();
  }						    
  return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)    
{        
 	u8 buf[5];
	u8 i;
	DHT11_Rst();
	if(DHT11_Check()==0)
	{
		for(i=0;i<5;i++)//读取40位数据
		{
			buf[i]=DHT11_Read_Byte();
		}
		if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
		{
			*humi=buf[0];
			*temp=buf[2];
		}
	}else return 1;
	return 0;	    
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在    	 
u8 DHT11_Init(void)
{	 
 	
			    
	DHT11_Rst();  //复位DHT11
	return DHT11_Check();//等待DHT11的回应
} 







MQ2烟雾传感器驱动程序: 

bsp_adc.c

#include "bsp_adc.h"
#define ADC1_DR_Address    ((u32)0x40012400+0x4c)  //定义ADC的内存地址
#include 
static int floag1=0;
#define CAL_PPM 20  // 校准环境中PPM值
#define RL			5		// RL阻值
static float R0=1; // 元件在洁净空气中的阻值
float ppm;
__IO uint16_t ADC_ConvertedValue;
static void ADC1_GPIO_Config(void) //ADC端口配置
{
	GPIO_InitTypeDef GPIO_InitStructure;//GPIO初始化结构体;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA的时钟;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC, ENABLE);//打开ADC1和GPIOC的时钟;
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//配置PC0引脚;
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//设置工作模式为模拟输入;
	
	GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIOC;
}
static void ADC1_Mode_Config(void)  //配置ADC1的模式
{
	/********以下是有关DMA的相关配置*************/
	
	DMA_InitTypeDef DMA_InitStructure;//DMA初始化结构体定义DMA初始化变量
	
	ADC_InitTypeDef ADC_InitStructure;//ADC初始化结构体定义ADC初始化变量
	
	DMA_DeInit(DMA1_Channel1);//设置DMA1通道1
	
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//设定ADC的地址;
	
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue;//内存地址,采集的数据存在这里;
	
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//设为源,表示数据是从这里出发的;
	
	DMA_InitStructure.DMA_BufferSize = 1;//因为一次只发送一个数据所以设为1;
	
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;//因为只涉及一路数据的采集发送因此内存地址不变
	
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//因为只涉及一路数据的采集发送因此外设地址不变
	
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设至少要半字即16位才可以满足要求
	
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存至少要半字即16位才可以满足要求
	
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA模式为循环传输,因为要采集多次;
	
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;//设置为高、中、低优先级都可以因为只有一路在采集
	
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//关闭内存到内存的传输,因为我们需要的是外设传到内存的传输
	
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);//DMA1通道1最后初始化
	
	DMA_Cmd(DMA1_Channel1, ENABLE);//使能DMA1的通道1;
	

	
	/********以下是有关ADC的相关配置*************/
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//设置为独立ADC模式,因为其采集只有一个通道;
	
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;//禁止扫描模式,扫描模式适用于多通道采集
	
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//开启连续转换,以不停地进行ADC转换
	
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发转换,而使用内部软件触发
	
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//采集数据右对齐
	
	ADC_InitStructure.ADC_NbrOfChannel = 1;//ADC number of channel,即要转换的通道数目;
	
	ADC_Init(ADC1, &ADC_InitStructure);//调用ADC初始化库函数
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);//配置ADC时钟为8分频,9MHZ
	
	ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5);//配置ADC1的通道为55.5个采样周期,
	
	ADC_DMACmd(ADC1, ENABLE);//使能ADC1的DMA传输
	
	ADC_Cmd(ADC1, ENABLE);//使能ADC1;
	
	while(ADC_GetResetCalibrationStatus(ADC1));//等待校准寄存器复位完成;
	
	ADC_StartCalibration(ADC1);//调用校准函数开始ADC校准;
	
	while(ADC_GetResetCalibrationStatus(ADC1));//等待校准寄存器复位完成;
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//前面不采用外部触发,而是采用内部软件触发,此处使能软件触发
	

}

void ADC1_Init(void)
{
	 ADC1_GPIO_Config();
	 ADC1_Mode_Config();
		
}	


 // 传感器校准函数
void MQ2_PPM_Calibration(float RS)
{
    R0 = RS / pow(CAL_PPM / 613.9f, 1 / -2.074f);
}
 
 // MQ2传感器数据处理
float MQ2_GetPPM(void)
{
    float Vrl = (float) ADC_ConvertedValue/4096*3.3;
    float RS = (3.3f - Vrl) / Vrl * RL; 
    if(Vrl>1&&floag1==0) // 获取系R0
    {
		MQ2_PPM_Calibration(RS);
		floag1=1;
    }
    ppm = 613.9f * pow(RS/R0, -2.074f);
    return  ppm;
}


3、基于MQTT协议的数据传输

        STM32开发板通过ESP8266模块连接上局域网(也就是我们家庭中的路由器、WIFI),同时ESP8266通过互联网连接到远程MQTT服务器(MQTT Server),这样便达到了将传感器所采集到的数据储存在云端,可供多个客户端进行访问。
         在数据传输中,用到了一种很核心的物联网数据传输协议:MQTT协议。MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。它的基本原理是:一个客户端(ESP8266)向服务器发布(Publish)一个带有传感采集到的数据的主题,则另外一个客户端(软件上位机)则通过向服务器订阅(Subscribe)服务器上ESP8266客户端发布的主题,即可以接收到各传感器采集到的数据,从而达到实时显示、实时检测的目的。
        同时数据在传输的过程中都是以JSON格式进行传输的。

基于STM32的物联网健康监测系统设计(附源码)_第2张图片

 核心代码介绍:

        由于该部分核心代码过多,就不在本文展示,请到我的github源码仓库进行访问,在这里简要对一些程序文件进行说明:

esp8266.c : ESP8266模块的驱动程序
onenet.c : 数据上传云域网程序
MqttKit.c : 客户端对服务器进行一系列数据传输程序(例如主题的发布、订阅)

4、微信小程序上位机设计

        小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。同时因为它不像安卓APP一样需要下载,可以免安装使用,有一定的便利性,所以本项目采用微信小程序来开发上位机。下面该软件上位机的各个UI界面及其所对应的功能

1.主界面

        这是该软件的首页,通过和风天气平台的支持,可以获取到当前你所在地的天气状况,同时正中间的三个控件可以进入三个不同的系统。

基于STM32的物联网健康监测系统设计(附源码)_第3张图片

代码:

index.wxml:



    
         
            空气质量-{{weather_quality}}
            {{city}}-{{area}}
        
        
            {{weather_temp}}℃
            {{weather_text}}
        
        
            {{advice}}
        
    
    
        
        
        
    

    

 index.wxss

/* pages/home/home.wxss */
.wrapper{
  padding: 30rpx 20rpx;
  /* background-color: beige; */
}
.header-wrapper{
  background-color: #3d7ef6;
  border-radius: 40rpx;
  padding: 30rpx 50rpx;
  box-shadow: #d6d6d6 1px 1px 1px;
  color: floralwhite;
}
.header-title{
  display: flex;
  font-size: 30rpx;
  justify-content: space-between;
}
.header-text{
  display: flex;
  font-size: 48rpx;
  font-weight: 400;
  padding: 7rpx 0rpx;
  justify-content: space-between;
}
.weather-advice{
  font-size: 26rpx;
  margin-top: 50rpx;
}
.botton-wrapper{
  padding: 240rpx 30rpx;
  
}

#btn1{
  height: 80rpx;
  width: 60% ;
  color: white;
  font-size: 30rpx ;
  background-color: #3d7ef6 ;
  margin-top: 5rpx;
  justify-content: center;
  box-shadow: #d6d6d6 2px 2px 2px;
  border-radius: 40rpx;
}
#btn2{
  height: 80rpx;
  width: 60% ;
  color: white;
  font-size: 30rpx ;
  background-color: #3d7ef6 ;
  margin-top: 50rpx;
  justify-content: center;
  box-shadow: #d6d6d6 2px 2px 2px;
  border-radius: 40rpx;
}

 index.js

Page({
  /**
   * 页面的初始数据
   */
  data: {
    weather_quality:"请求中",
    weather_text:"请求中",
    weather_temp:"请求中",
    city:"请求中",
    area:"请求中",
    advice:"请求中"

  },
  toEnv(){
    wx.navigateTo({
      url: '../env/env'
    })
  },
  toHealth(){
    wx.navigateTo({
      url: '../healthy/healthy'
    })
  },
  toCanvas(){
    wx.navigateTo({
      url: '../curves/curves'
    })
  },
  toDetail(){
    wx.navigateTo({
      url: '../weather/weather'
    })
  },
  toBluetooth(){
    wx.navigateTo({
      url: '../bluetooth/bluetooth'
    })
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    
  },

  onShow: function () {
    var that=this
    wx.getLocation({
      success(res){
        const {latitude} = res
        const {longitude} = res
        const key = '1c6a3dc86f2544a3b18828ca409858c9'
        // 请求温度与天气状况
        wx.request({
          url: `https://devapi.qweather.com/v7/weather/now?location=${longitude},${latitude}&key=${key}`,
          success(res){
          //  console.log(res)
            that.setData({
              weather_temp:res.data.now.temp,
              weather_text:res.data.now.text
            })
          }
        })
        // 地理位置
        wx.request({
          url: `https://geoapi.qweather.com/v2/city/lookup?location=${longitude},${latitude}&key=${key}`,
          success(res){
          //  console.log(res)
            that.setData({
              city:res.data.location[0].adm2,
              area:res.data.location[0].name
            })
          }
        })
        //指数
        wx.request({
          url: `https://devapi.qweather.com/v7/indices/1d?location=${longitude},${latitude}&key=${key}&type=${0}`,
          success(res){
           // console.log(res)
            that.setData({
              advice:res.data.daily[2].text
            })
          }
        })
        //空气质量
        wx.request({
          url: `https://devapi.qweather.com/v7/air/now?location=${longitude},${latitude}&key=${key}`,
          success(res){
            console.log(res)
            that.setData({
              weather_quality:res.data.now.category
            })
          }
        })
      }
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {
    
  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {
    
  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {
    
  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    
  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {
    
  }
})

2. 健康监测系统 

用于显示MAX30102心率血氧传感器所采集到的人体的心率、血氧值,可以对其进行实时监测并显示

基于STM32的物联网健康监测系统设计(附源码)_第4张图片

healthy.wxml :


    
        
        
            实时心率
            {{Heart}}
        
    


    
        
        
            血氧浓度
            {{Spo2}}%
        
    

healty.wxss: 

/* pages/healthy/healthy.wxss *//* pages/env/env.wxss */
.body-wrapper{
    padding: 30rpx 20rpx;
}
.sensor{
    width: 100%;
    height: 190rpx;
    border-radius: 40rpx;
    box-shadow: #d6d6d6 1px 1px 5px;
    margin-top: 50rpx;
    display: flex;
    justify-content: space-between;
}
.sensor-logo{
    padding: 20rpx 30rpx;
    height: 130rpx;
    width: 190rpx;
    margin-top: 10rpx;
}
.sensor-logo-water{
    padding: 20rpx 50rpx;
    height: 130rpx;
    width: 180rpx;
    margin-top: 10rpx;
}
.sensor-logo-oxygen{
    padding: 20rpx 90rpx;
    height: 118rpx;
    width: 120rpx;
    margin-top: 10rpx;
}
.sensor-text{
    padding: 0rpx 120rpx;
    margin-top: 26rpx;
    color: #2e2e2e;
}
.sensor-title{
    font-size: 35rpx;
}
.sensor-value{
    font-size: 66rpx;
}
.sensor-others{
    width: 100%;
    height: 190rpx;
    border-radius: 40rpx;
    box-shadow: #d6d6d6 1px 1px 5px;
    margin-top: 0rpx;
    display: flex;
    justify-content: space-between;
}

healty.js 

var mqtt=require('../../utils/mqtt.min.js')
let client=null
Page({
    data: {
        Heart:0,
        Spo2:0
    },
    /**
     * 生命周期函数--监听页面加载
     */
    onLoad: function (options) {
        this.connectmqtt()
    },
    connectmqtt:function(){
        var that=this
        const options={
            connectTimeout:4000,
            clientId:"d1f22er224",
            port:8084,
            username:'f585c3d5f499ffea9b710f13709a855d',
            password:'123456'
        }
        client=mqtt.connect('wxs://t.yoyolife.fun/mqtt',options)
        client.on('connect',(e)=>{
            console.log("mqtt服务器连接成功")
            client.subscribe('/iot/943/pub',
            {qos:0},function(err){
                if(!err){
                    console.log("订阅成功!")
                }
            })
        })
        client.on('message',function(topic,message){
            let dataFrameDev=[]
            dataFrameDev=JSON.parse(message)
            console.log(dataFrameDev)
        })
    }
  
})

3. 环境监测系统         

        用于显示DHT11温湿度传感器、MQ2传感器所采集到的环境参数数据,可以对其进行实时监测并显示。

基于STM32的物联网健康监测系统设计(附源码)_第5张图片

env.wxml:



    
        
        
            实时温度
            {{Temp}}℃
        
    


    
        
        
            实时湿度
            {{Hum}}%
        
    


    
        
        
            烟雾浓度
            {{Smoke}}
        
    


    
        
        
            光照度
            29lx
        
    


    
    
        
        
            报警器
            
                
            
        
    


    
        
        
            房间灯
            
                
            
        
    
    {{event}}

 env.wxss

/* pages/env/env.wxss */
.body-wrapper{
    padding: 30rpx 20rpx;
}
.sensor{
    width: 100%;
    height: 190rpx;
    border-radius: 40rpx;
    box-shadow: #d6d6d6 1px 1px 5px;
    margin-top: 50rpx;
    display: flex;
    justify-content: space-between;
}
.sensor-logo{
    padding: 20rpx 30rpx;
    height: 130rpx;
    width: 190rpx;
    margin-top: 10rpx;
}
.sensor-logo-water{
    padding: 20rpx 50rpx;
    height: 130rpx;
    width: 180rpx;
    margin-top: 10rpx;
}
.sensor-logo-smoke{
    padding: 20rpx 50rpx;
    height: 130rpx;
    width: 140rpx;
    margin-top: 10rpx;
}
.sensor-text{
    padding: 0rpx 120rpx;
    margin-top: 26rpx;
    color: #2e2e2e;
}
.sensor-title{
    font-size: 35rpx;
}
.sensor-value{
    font-size: 66rpx;
}
.sensor-others{
    width: 100%;
    height: 190rpx;
    border-radius: 40rpx;
    box-shadow: #d6d6d6 1px 1px 5px;
    margin-top: 0rpx;
    display: flex;
    justify-content: space-between;
}
.control{
    width: 100%;
    height: 190rpx;
    border-radius: 40rpx;
    box-shadow: #d6d6d6 1px 1px 5px;
    margin-top: 0rpx;
    display: flex;
    justify-content: space-between;
}
.control-logo{
    padding: 40rpx 30rpx;
    height: 100rpx;
    width: 100rpx;
    margin-top: 10rpx;
}
.control-text{
    padding: 0rpx 30rpx;
    margin-top: 26rpx;
    color: #2e2e2e;
}
.control-title{
    font-size: 33rpx;
}
.control-value{
    font-size: 60rpx;
}
.control-wrapper{
    display: flex;
    padding: 0rpx 19.5rpx;
}

 env.js

var mqtt=require('../../utils/mqtt.min.js')
var client=null
Page({
    /**
     * 页面的初始数据
     */
    data: {
        Temp:0,
        Hum:0,
        Smoke:0,
        Led:false,
        Beep:false,
        event:""
    },
    /**
     * 生命周期函数--监听页面加载
     */
    onLoad(){
        this.connectmqtt()
    },
    connectmqtt:function(){
        var that=this
        const options={
            connectTimeout:4000,
            clientId:"df2er24",
            port:8084,
            username:'f585c3d5f499ffea9b710f13709a855d',
            password:'123456'
        }
        client=mqtt.connect('wxs://t.yoyolife.fun/mqtt',options)
        client.on('connect',(e)=>{
            console.log("服务器连接成功")
            client.subscribe('/iot/943/pub',{qos:0},function(err){
                if(!err){
                    console.log("订阅成功")
                }
            })
        })
        // 信息监听事件
        client.on('message', function(topic,message){
           // console.log(topic)
            let dataFrameDev ={}
            dataFrameDev = JSON.parse(message)
            console.log(dataFrameDev)
            that.setData({
                Temp:dataFrameDev.Temp,
                Hum:dataFrameDev.Hum,
                Smoke:dataFrameDev.Smoke
            })
            console.log(that.data.Temp)
        })
        client.on('reconnect', (error)=>{
            console.log('正在重新连接'+error)
        })
        client.on('error', (error)=>{
            console.log('连接失败')
        })
    },
    handleLED(e){
        var that=this
      //  console.log(e)
        let {value}=e.detail
      //  console.log(value)
      that.setData({
          Led:value,
      })
      if(value===true){
          that.setData({
            event:"您已开灯!",
          })
          client.publish('/iot/943/sub','{"target":"LED","value":1}',function(err){
            if(!err){
              console.log("成功发布开灯命令")
            }
          })
      }else{
        that.setData({
            event:""
          })
          client.publish('/iot/943/sub','{"target":"LED","value":0}',function(err){
            if(!err){
              console.log("成功发布关灯命令")
              }
            })
      }

    }
})

4. 显示实时动态曲线         

        为了能显示出一段时间内各个参数的动态变化过程,这里开发了一个实时动态曲线功能,每当接收到服务端传来的消息,则刷新一次曲线从而达到实时更新的目的。

基于STM32的物联网健康监测系统设计(附源码)_第6张图片

curves.wxml


     
        
            
        温湿度、烟雾浓度实时曲线图
            
                
                
                    平均浓度
                    {{smoke_average}}bpm
                
                
                    平均湿度
                    {{hum_average}}%
                
                  
                    平均温度
                    {{temp_average}}℃
                
                
            
        
        {{time}}
            
        
    
     
        
            时间
            温度
            湿度
            烟雾浓度
        
        
            
                {{item}}
            
            
                {{item}}℃
            
            
                {{item}}%
            
            
                {{item}}bpm
        
        
    





curves.wxss 

page {
  background-color: rgba(239, 239, 240);
}
.body{
  padding: 0rpx 20rpx;
  margin-top: 100rpx;
}
.body-content{
  background-color:#ffffff;
  border-radius: 40rpx;
}
.body-title{
  display: flex;
  justify-content: center;
  font-size: 30rpx;
  padding: 50rpx 0rpx 20rpx;
}
.meandata-body{
  padding: 20rpx 39rpx;
}
.meandata{
  display: flex;
  justify-content: space-around;
  border-bottom: 1rpx solid rgba(216, 216, 216, 1);
  border-top: 1rpx solid rgba(216, 216, 216, 1);
  padding: 24rpx;
}
.canvas {
  width: 100%;
  height: 550rpx;
  
}
.timestyle{
  padding: 50rpx 165rpx;
}

.detail-title{
  display: flex;
}

.data-temp{
  padding: 19rpx 30rpx;
}
.data-hum{
  padding: 19rpx 30rpx;
}
.data-smoke{
  padding: 19rpx 30rpx;
}
.data{
  display: flex;
}
.title-time{
  padding: 0rpx 0rpx 0rpx 60rpx;
}
.title-temp{
  padding: 0rpx 0rpx 0rpx 111rpx;
}
.title-hum{
  padding: 0rpx 0rpx 0rpx 117rpx;
}
.title-smoke{
  padding: 0rpx 0rpx 0rpx 140rpx;
}

curves.js

// pages/index/lookrecord/lookrecord.js
var wxCharts = require('../../utils/wxcharts.js');   //引入wxChart文件
var mqtt=require('../../utils/mqtt.min.js') // 引入mqtt文件
var util = require("../../utils/util.js");
var client=null
var app = getApp();
var lineChart = null;
Page({

  /**
   * 页面的初始数据
   */
  data: {
    list:[
      {
        id:0,
        name:"趋势图",
        isActive:true
      },{
          id:1,
          name:"数据记录",
          isActive:false
      },
    ],
    time:"",
    xtime:"",
    Temp:0,
    temp_average:0,
    temp_sum:0,

    Hum:0,
    hum_average:0,
    hum_sum:0,

    Smoke:0,
    smoke_average:0,
    smoke_sum:0,

    Temp_array:[],
    Hum_array:[],
    Smoke_array:[],
    time_array:[],
    waterwaterdata:[50, 100, 80, 115, 120, 90, 125],
    smokesmokedata:[60, 70, 90, 105, 120, 130, 95],
    tempdata: [60,90, 60, 110,120,105,70],  //数据点
    categories: ['2018-6-13', '2018-6-14', '2018-6-15', '2018-6-16', '2018-6-17', '2018-6-18', '2018-6-19'],    //模拟的x轴横坐标参数
  },

  touchHandler: function (e) {
    lineChart.showToolTip(e, {
      // background: '#7cb5ec',
      format: function (item, category) {
        return category + ' ' + item.name + ':' + item.data
      }
    });
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(){
    this.curve()
    this.connectmqtt()
  //  this.gettime()
  },
  onShow:function(){
    this.notification()  // 调用方法
  },
  connectmqtt:function(){
    var that=this
    const options={
        connectTimeout:4000,
        clientId:"df2er24",
        port:8084,
        username:'f585c3d5f499ffea9b710f13709a855d',
        password:'123456'
    }
    client=mqtt.connect('wxs://t.yoyolife.fun/mqtt',options)
    client.on('connect',(e)=>{
        console.log("服务器连接成功")
        client.subscribe('/iot/943/pub',{qos:0},function(err){
            if(!err){
                console.log("订阅成功")
            }
        })
    })
    // 信息监听事件
    client.on('message', function(topic,message){
       // console.log(topic)
        let dataFrameDev ={}
        dataFrameDev = JSON.parse(message)
        console.log(dataFrameDev)
        that.setData({
            Temp:dataFrameDev.Temp,
            Hum:dataFrameDev.Hum,
            Smoke:dataFrameDev.Smoke,
        })
        // 设置温度、湿度、烟雾浓度的数组
        that.setData({
            Temp_array:that.data.Temp_array.concat(that.data.Temp),
            Hum_array:that.data.Hum_array.concat(that.data.Hum),
            Smoke_array:that.data.Smoke_array.concat(that.data.Smoke)
        })
        console.log(that.data.Temp)
        console.log(that.data.Temp_array)
        console.log(that.data.Hum_array)
        console.log(that.data.Smoke_array)

        // 获取到sensor data 后开始获取本地时间
        var xtime=that.data.xtime
        that.setData({
            xtime:util.formatTime(new Date())
        })
        console.log(that.data.xtime)
        that.setData({
          time_array:that.data.time_array.concat(that.data.xtime)
        })


        // 求烟雾浓度平均值
        var smoke_average=that.data.smoke_average
        var Smoke_array=that.data.Smoke_array
        var smoke_sum=that.data.smoke_sum
        that.setData({
          smoke_sum:smoke_sum+that.data.Smoke,
          smoke_average:parseInt(that.data.smoke_sum/(Smoke_array.length))
        })
        console.log(that.data.time_array)
        console.log("平均浓度"+that.data.smoke_average)

        // 求温度平均值
        var temp_average=that.datatempe_average
        var Temp_array=that.data.Temp_array
        var temp_sum=that.data.temp_sum
        that.setData({
          temp_sum:temp_sum+that.data.Temp,
          temp_average:parseInt(that.data.temp_sum/(Temp_array.length))
        })
        console.log(that.data.time_array)
        console.log("平均温度"+that.data.temp_average)

        // 求平均湿度
        var hum_average=that.data.hum_average
        var Hum_array=that.data.Hum_array
        var hum_sum=that.data.hum_sum
        that.setData({
          hum_sum:hum_sum+that.data.Hum,
          hum_average:parseInt(that.data.hum_sum/(Hum_array.length))
        })
        console.log(that.data.time_array)
        console.log("平均湿度"+that.data.hum_average)

    })
    client.on('reconnect', (error)=>{
        console.log('正在重新连接'+error)
    })
    client.on('error', (error)=>{
        console.log('连接失败')
    })
},
  curve (e) {
    var that=this
    
    var windowWidth = '', windowHeight='';    //定义宽高
    that.data.setInter = setInterval(function(){
      var waterwaterdata=that.data.Hum_array
      var smokesmokedata=that.data.Smoke_array
      var tempdata=that.data.Temp_array
      
      var categories=that.data.time_array
      try {
        var res = wx.getSystemInfoSync();    //试图获取屏幕宽高数据
        windowWidth = res.windowWidth / 750 * 690;   //以设计图750为主进行比例算换
        windowHeight = res.windowWidth / 750 * 550    //以设计图750为主进行比例算换
      } catch (e) {
        console.error('getSystemInfoSync failed!');   //如果获取失败
      }
      lineChart = new wxCharts({     //定义一个wxCharts图表实例
        canvasId: 'lineCanvas',     //输入wxml中canvas的id
        type: 'line',       //图标展示的类型有:'line','pie','column','area','ring','radar'
        categories: categories,
        animation: true,  //是否开启动画
        series: [{   //具体坐标数据
          name: '温度',  //名字
          data: tempdata, //数据点
          format: function (val, name) {  //点击显示的数据注释
            return val + '℃';
          }
        }, {
          name: '烟雾浓度',
          data: smokesmokedata,
          format: function (val, name) {
            return val + 'bpm';
          }
        }, {
          name: '湿度',
          data: waterwaterdata,
          format: function (val, name) {
            return val + '%';
          }
        }
        ],
        xAxis: {   //是否隐藏x轴分割线
          disableGrid: true,
        },
        yAxis: {      //y轴数据
          title: '数值',  //标题
          format: function (val) {  //返回数值
            return val.toFixed(2);
          },
          min: 30,   //最小值
          max:180,   //最大值
          gridColor: '#D8D8D8',
        },
        width: windowWidth,  //图表展示内容宽度
        height: windowHeight,  //图表展示内容高度
        dataLabel: false,  //是否在图表上直接显示数据
        dataPointShape: true, //是否在图标上显示数据点标志
        extra: {
          lineStyle: 'curve'  //曲线
        },
      });
    },9000)
  },
  notification: function () {
    var _this = this;
    var time = _this.data.time;
    _this.data.setInter = setInterval(function () {
       _this.setData({
        time: util.formatTime(new Date())
        }); 
        //console.log("时间为"+_this.data.time);   
    }, 1000); 
  },
  // gettime(){
  //   var that=this
  //   var xtime=that.data.xtime
  //   that.setData({
  //     xtime:util.formatTime(new Date())
  //   })

  // }
  handleItemChange(e){
    //    console.log(e)
        const {index}=e.detail;
        let {list}=this.data;
        list.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);
        this.setData({
            list
        })  
    }

})

你可能感兴趣的