赞
踩
目录
该项目是利用STM32开发板进行开发的基于MQTT协议的物联网健康监测系统,并开发了微信小程序作为该项目的软件上位机。该产品可以用来实时监测人体的心率、血氧等生理参数,也可以监测家庭环境中的温湿度、烟雾浓度等环境参数,从而为您营造出一个健康的居住环境。此项目是本人在寒假利用业余时间开发的,从理论知识的学习到做出最终成品耗时将近两个月,可用于嵌入式软件方向的毕业设计。
该项目源码已全部开源,在本文中仅介绍核心代码原理,源码已发布在我的GitHub主页,如对源码有疑惑,或者对该项目有改进性意见,可以评论或私信本人,欢迎大家一起交流学习!
我的github主页:https://github.com/SichengLong26
源代码地址:https://github.com/SichengLong26/health-iot
该项目主要分为硬件架构设计和软件架构设计,硬件架构设计包括电路设计、PCB焊接、驱动程序编写、数据传输(数据上云)的程序编写;软件架构设计则包括前端UI界面的设计和后端的数据处理,此次软件上位机是基于微信小程序来开发的。
主控芯片:STM32F103RCT6
传感器:MAX30102心率传感器、DHT11温湿度传感器、MQ2烟雾传感器
通信模块:ESP8266 WIFI模块
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;i<n_ir_buffer_length;i++)
- {
- while(PAin(5)==1); //wait until the interrupt pin asserts
- maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i)); //read from MAX30102 FIFO
- if(un_min>aun_red_buffer[i])
- un_min=aun_red_buffer[i]; //update signal min
- if(un_max<aun_red_buffer[i])
- un_max=aun_red_buffer[i]; //update signal max
- printf("心率:%i次/min,", aun_red_buffer[i]/1000);
- printf("血氧=%i % \r\n", aun_ir_buffer[i]/1000+16);
- }
- un_prev_data=aun_red_buffer[i];
- //calculate heart rate and SpO2 after first 500 samples (first 5 seconds of samples)
- 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);
- while(1)
- {
- i=0;
- un_min=0x3FFFF;
- un_max=0;
- //dumping the first 100 sets of samples in the memory and shift the last 400 sets of samples to the top
- for(i=START;i<DATA_LENGTH;i++)
- {
- aun_red_buffer[i-START]=aun_red_buffer[i];
- aun_ir_buffer[i-START]=aun_ir_buffer[i];
-
- //update the signal min and max
- if(un_min>aun_red_buffer[i])
- un_min=aun_red_buffer[i];
- if(un_max<aun_red_buffer[i])
- un_max=aun_red_buffer[i];
- }
- //take 100 sets of samples before calculating the heart rate.
- for(i=400;i<DATA_LENGTH;i++)
- {
- un_prev_data=aun_red_buffer[i-1];
- while(PAin(5)==1);
- maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i));
-
- if(aun_red_buffer[i]>un_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 <math.h>
- 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;
- }
-
-
STM32开发板通过ESP8266模块连接上局域网(也就是我们家庭中的路由器、WIFI),同时ESP8266通过互联网连接到远程MQTT服务器(MQTT Server),这样便达到了将传感器所采集到的数据储存在云端,可供多个客户端进行访问。
在数据传输中,用到了一种很核心的物联网数据传输协议:MQTT协议。MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。它的基本原理是:一个客户端(ESP8266)向服务器发布(Publish)一个带有传感采集到的数据的主题,则另外一个客户端(软件上位机)则通过向服务器订阅(Subscribe)服务器上ESP8266客户端发布的主题,即可以接收到各传感器采集到的数据,从而达到实时显示、实时检测的目的。
同时数据在传输的过程中都是以JSON格式进行传输的。
核心代码介绍:
由于该部分核心代码过多,就不在本文展示,请到我的github源码仓库进行访问,在这里简要对一些程序文件进行说明:
esp8266.c : ESP8266模块的驱动程序
onenet.c : 数据上传云域网程序
MqttKit.c : 客户端对服务器进行一系列数据传输程序(例如主题的发布、订阅)
小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。同时因为它不像安卓APP一样需要下载,可以免安装使用,有一定的便利性,所以本项目采用微信小程序来开发上位机。下面该软件上位机的各个UI界面及其所对应的功能
这是该软件的首页,通过和风天气平台的支持,可以获取到当前你所在地的天气状况,同时正中间的三个控件可以进入三个不同的系统。
代码:
index.wxml:
- <!--pages/home/home.wxml-->
- <view class="wrapper">
- <view class="header-wrapper" bindtap="toDetail">
- <view class="header-title">
- <text>空气质量-{{weather_quality}}</text>
- <text>{{city}}-{{area}}</text>
- </view>
- <view class="header-text">
- <text>{{weather_temp}}℃</text>
- <text>{{weather_text}}</text>
- </view>
- <view class="weather-advice">
- <text>{{advice}}</text>
- </view>
- </view>
- <view class="botton-wrapper">
- <button id="btn1" bindtap="toEnv">环境监测系统</button>
- <button id="btn2" bindtap="toHealth">健康监测系统</button>
- <button id="btn2" bindtap="toCanvas">查看实时动态曲线</button>
- </view>
- </view>
-
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 () {
-
- }
- })
用于显示MAX30102心率血氧传感器所采集到的人体的心率、血氧值,可以对其进行实时监测并显示
healthy.wxml :
- <view class="body-wrapper">
- <view class="sensor-others">
- <image class="sensor-logo-water" src="../../icon/heart.png"/>
- <view class="sensor-text">
- <view class="sensor-title">实时心率</view>
- <view class="sensor-value">{{Heart}}</view>
- </view>
- </view>
- </view>
- <view class="body-wrapper">
- <view class="sensor-others">
- <image class="sensor-logo-oxygen" src="../../icon/oxygen.png"/>
- <view class="sensor-text">
- <view class="sensor-title">血氧浓度</view>
- <view class="sensor-value">{{Spo2}}%</view>
- </view>
- </view>
- </view>
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)
- })
- }
-
- })
用于显示DHT11温湿度传感器、MQ2传感器所采集到的环境参数数据,可以对其进行实时监测并显示。
env.wxml:
- <!--pages/env/env.wxml-->
- <view class="body-wrapper">
- <view class="sensor">
- <image class="sensor-logo" src="../../icon/temperature.png"/>
- <view class="sensor-text">
- <view class="sensor-title">实时温度</view>
- <view class="sensor-value">{{Temp}}℃</view>
- </view>
- </view>
- </view>
- <view class="body-wrapper">
- <view class="sensor-others">
- <image class="sensor-logo-water" src="../../icon/water.png"/>
- <view class="sensor-text">
- <view class="sensor-title">实时湿度</view>
- <view class="sensor-value">{{Hum}}%</view>
- </view>
- </view>
- </view>
- <view class="body-wrapper">
- <view class="sensor-others">
- <image class="sensor-logo-smoke" src="../../icon/smoke.png"/>
- <view class="sensor-text">
- <view class="sensor-title">烟雾浓度</view>
- <view class="sensor-value">{{Smoke}}</view>
- </view>
- </view>
- </view>
- <view class="body-wrapper">
- <view class="sensor-others">
- <image class="sensor-logo" src="../../icon/lx.png"/>
- <view class="sensor-text">
- <view class="sensor-title">光照度</view>
- <view class="sensor-value">29lx</view>
- </view>
- </view>
- </view>
- <view class="control-wrapper">
- <view class="body-wrapper">
- <view class="control">
- <image class="control-logo" src="../../icon/beep.png"/>
- <view class="control-text">
- <view class="control-title">报警器</view>
- <view class="control-value">
- <switch bindchange="handleLED" checked="{{true}}"></switch>
- </view>
- </view>
- </view>
- </view>
- <view class="body-wrapper">
- <view class="control">
- <image class="control-logo" src="../../icon/led.png"/>
- <view class="control-text">
- <view class="control-title">房间灯</view>
- <view class="control-value">
- <switch bindchange="handleLED" checked="{{true}}"></switch>
- </view>
- </view>
- </view>
- <view>{{event}}</view>
- </view>
- </view>
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("成功发布关灯命令")
- }
- })
- }
-
- }
- })
为了能显示出一段时间内各个参数的动态变化过程,这里开发了一个实时动态曲线功能,每当接收到服务端传来的消息,则刷新一次曲线从而达到实时更新的目的。
curves.wxml
- <Tabs list="{{list}}" binditemChange="handleItemChange">
- <block wx:if="{{list[0].isActive}}" class="tabs">
- <view class="body">
- <view class="body-content">
- <view class="body-title">温湿度、烟雾浓度实时曲线图</view>
- <view class="meandata-body">
- <view class="meandata">
- <view>
- <view>平均浓度</view>
- <view>{{smoke_average}}bpm</view>
- </view>
- <view>
- <view>平均湿度</view>
- <view>{{hum_average}}%</view>
- </view>
- <view>
- <view>平均温度</view>
- <view>{{temp_average}}℃</view>
- </view>
- </view>
- </view>
- <canvas canvas-id="lineCanvas" disable-scroll="true" class="canvas" bindtouchstart="touchHandler"></canvas>
- <view class="timestyle">{{time}}</view>
- </view>
- </view>
- </block>
- <block wx:elif="{{list[1].isActive}}">
- <view class="detail-title">
- <view class="title-time">时间</view>
- <view class="title-temp">温度</view>
- <view class="title-hum">湿度</view>
- <view class="title-smoke">烟雾浓度</view>
- </view>
- <view class="data">
- <view class="data-time">
- <view wx:for="{{time_array}}" wx:key="*this">{{item}}</view>
- </view>
- <view class="data-temp">
- <view wx:for="{{Temp_array}}" wx:key="*this" class="data-temp">{{item}}℃</view>
- </view>
- <view class="data-hum">
- <view wx:for="{{Hum_array}}" wx:key="*this" class="data-hum">{{item}}%</view>
- </view>
- <view class="data-smoke">
- <view wx:for="{{Smoke_array}}" wx:key="*this" class="data-smoke">{{item}}bpm</view>
- </view>
- </view>
- </block>
-
- </Tabs>
-
-
-
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
- })
- }
-
- })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。