开发要求
1、通过温湿度、烟雾、光敏电阻传感器采集家庭环境中的温湿度、烟雾浓度、光照强度,数据超 过阈值产生报警提示;
2、通过心率模块检测人体心率,通过温度传感器检测人体体温,数据超过阈值时产生报警提醒;
3、通过OLED显示屏显示人体每半小时的平均心率和平均体温;
4、单片机将数据通过MQTT协议和阿里云物联网平台交互,手机端APP查看数据,实现远程监控、 数据内容显示;
硬件选材
芯片: stm32 F407(我用的是集成开发板内部集成stm32 F407芯片)
温湿度模块: DHT11(老朋友了 数字信号嘎嘎友好)
两块 测体温和测家庭环境的 (测体温可以用别的体温模块我图方便就暂时先用这个)
烟雾传感器: MQ-2
光照强度:GL55光敏电阻 (开发板内置)
心率模块:max30102
显示:OLED(老朋友了)
wifi模块:esp8266(正点原子)别的也可以
下面正式开始
第一步 新建工程
好的目录结构更容易 为后续开发工作提供便利
1.headwear:存放开发的头文件
2.project:工程文件
3.system:存放滴答计时器的用于延时
4.user:用户目录 存放主函数
工程创建很基础这里就先不演示了详见下面链接用keil5新建STM32工程(超详细的图解操作)_keil5新建stm32工程步骤-CSDN博客
第二步 规划资源分配
就是怎么合理运用单片机内部的资源,好的开始能让项目更顺利。
1.资源分配
我们需要一个定时中断在获取模块的数值 我们选用基本定时器 f407有两个基本定时器
TIM6 和 TIM7 我们选用一个即可 。
上文提到半小时平均值 我们在定时器中断加一个累加即可 简单方便。一分钟采样一次
第三步 开发OLED模块
先开发OLED模块易于后期调试
1.引脚部分
GND | GND |
VDD | 3.3V |
SCK | PB8 |
SDA | PB9 |
iic协议不会的看这里【通信协议】一文搞懂I2C(IIC)_iic?!,孞↖。↖∴:i…-CSDN博客
2.代码部分
源码如下
i2c.h
#ifndef __I2C_H__
#define __I2C_H__
#include "stm32f4xx.h"
#include "systick.h"
#define SCL_CLK RCC_AHB1Periph_GPIOB
#define SCL_PIN GPIO_Pin_8
#define SDA_PORT GPIOB
#define SDA_CLK RCC_AHB1Periph_GPIOB
#define SDA_PIN GPIO_Pin_9
#define SCL_PORT GPIOB
#define IIC_DELAY_US(nus) Delay_us(nus)
#define IIC_DELAY_MS(nms) Delay_ms(nms)
#define IIC_SCL_SET(n) (n) ? (GPIO_SetBits(SCL_PORT, SCL_PIN)) : (GPIO_ResetBits(SCL_PORT, SCL_PIN))
#define IIC_SDA_SET(n) (n) ? (GPIO_SetBits(SDA_PORT, SDA_PIN)) : (GPIO_ResetBits(SDA_PORT, SDA_PIN))
#define IIC_SDA_READ GPIO_ReadInputDataBit(SDA_PORT, SDA_PIN)
void I2C_SIM_Config(void);
void IIC_SetSDAOutputMode(void);
void IIC_SetSDAInputMode(void);
void IIC_Start(void);
void IIC_Stop(void);
void IIC_SendByte(uint8_t data);
uint8_t IIC_WaitSlaveAck(void);
void IIC_MasterAck(uint8_t ack);
uint8_t IIC_ReadByte(void);
#endif
i2c.c
#include "i2c.h"
/*
模拟IIC接口初始化函数
*/
void I2C_SIM_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = SCL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(SCL_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = SDA_PIN;
GPIO_Init(SDA_PORT, &GPIO_InitStructure);
IIC_SDA_SET(1);
IIC_SCL_SET(1);
}
/*
配置SDA引脚为输入模式函数
*/
void IIC_SetSDAInputMode(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(SDA_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/*
配置SDA引脚为输出模式函数
*/
void IIC_SetSDAOutputMode(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(SDA_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/*
IIC协议 起始信号函数
*/
void IIC_Start(void)
{
IIC_SetSDAOutputMode();
IIC_SDA_SET(1);
IIC_SCL_SET(1);
delay_us(5);
IIC_SDA_SET(0);
delay_us(5);
IIC_SCL_SET(0);
}
/*
IIC协议 停止信号函数
*/
void IIC_Stop(void)
{
IIC_SetSDAOutputMode();
IIC_SDA_SET(0);
IIC_SCL_SET(0);
delay_us(5);
IIC_SCL_SET(1);
delay_us(5);
IIC_SDA_SET(1);
delay_us(5);
}
/*
IIC协议 发送数据或者地址函数
*/
void IIC_SendByte(uint8_t data)
{
uint8_t i = 0;
IIC_SetSDAOutputMode();
IIC_SDA_SET(0);
IIC_SCL_SET(0);
for (i = 0; i < 8; i++)
{
if (data & 0x80)
IIC_SDA_SET(1);
else
IIC_SDA_SET(0);
delay_us(5);
data <<= 1;
IIC_SCL_SET(1);
delay_us(5);
IIC_SCL_SET(0);
delay_us(5);
}
}
/*
IC协议 主机等待从机应答函数
返回值为:0 表示应答,1 表示无应答
*/
uint8_t IIC_WaitSlaveAck(void)
{
uint8_t ack = 0;
IIC_SetSDAInputMode();
IIC_SCL_SET(0);
delay_us(5);
IIC_SCL_SET(1);
delay_us(5);
if (IIC_SDA_READ == 0)
ack = 0;
else
ack = 1;
IIC_SCL_SET(0);
delay_us(5);
return ack;
}
/*
IIC协议 主机应答函数
ack:0表示应答,1表示不应答。
*/
void IIC_MasterAck(uint8_t ack)
{
IIC_SetSDAOutputMode();
IIC_SDA_SET(0);
IIC_SCL_SET(0);
if (ack == 0)
IIC_SDA_SET(0);
else
IIC_SDA_SET(1);
delay_us(5);
IIC_SCL_SET(1);
delay_us(5);
IIC_SCL_SET(0);
delay_us(5);
}
/*
IIC协议 主机读取数据函数
*/
uint8_t IIC_ReadByte(void)
{
uint8_t i = 0;
uint8_t data = 0;
IIC_SetSDAInputMode();
IIC_SCL_SET(0);
delay_us(5);
for (i = 0; i < 8; i++)
{
IIC_SCL_SET(1);
delay_us(5);
data <<= 1;
if (IIC_SDA_READ == 1)
{
data |= 0x01;
}
IIC_SCL_SET(0);
delay_us(5);
}
return data;
}
接下来就是OLED的驱动代码 是建立在上面I2C代码基础之上的
OLED.h
#ifndef __OLED_H
#define __OLED_H
#include "stm32f4xx.h" // Device header
void OLED_Init(void);
void OLED_WriteCommand(uint8_t command);
void OLED_WriteData(uint8_t Data);
void OLED_SetCursor(uint8_t Page, uint8_t X);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
#endif
OLED.c
#include "OLED.h"
#include "i2c.h"
#include "systick.h"
#include "OLED_Font.h"
void OLED_Clear(void);
/*oled写命令*/
void OLED_WriteCommand(uint8_t command)
{
IIC_Start();
IIC_SendByte(0x78);
IIC_WaitSlaveAck();
IIC_SendByte(0x00);
IIC_WaitSlaveAck();
IIC_SendByte(command);
IIC_WaitSlaveAck();
IIC_Stop();
}
/*oled初始化*/
void OLED_Init(void)
{
I2C_SIM_Config();
delay_ms(100);
OLED_WriteCommand(0xAE); //设置显示开启/关闭,0xAE关闭,0xAF开启
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80); //0x00~0xFF
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F); //0x0E~0x3F
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00); //0x00~0x7F
OLED_WriteCommand(0x40); //设置显示开始行,0x40~0x7F
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常,0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常,0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度
OLED_WriteCommand(0xCF); //0x00~0xFF
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/反色显示,0xA6正常,0xA7反色
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear();
delay_ms(100);
}
/*oled写数据*/
void OLED_WriteData(uint8_t Data)
{
IIC_Start();
IIC_SendByte(0x78);
IIC_WaitSlaveAck();
IIC_SendByte(0x40);
IIC_WaitSlaveAck();
IIC_SendByte(Data);
IIC_WaitSlaveAck();
IIC_Stop();
}
/*oled设置光标*/
void OLED_SetCursor(uint8_t Page, uint8_t X)
{
OLED_WriteCommand(0x00 | (X & 0x0F));
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));
OLED_WriteCommand(0xB0 | Page);
}
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}
/*OLED显示一个字符*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]);
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);
}
}
/* OLED显示字符串*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
/*次方函数*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //结果默认为1
while (Y --) //累乘Y次
{
Result *= X; //每次把X累乘到结果上
}
return Result;
}
/* OLED显示十进制数字*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
第四步 温度传感器的配置
前面OLED部分配置好了可以直接配置dht11并调试, 会方便很多。
dht11有单总线驱动 时序图在这里 详见DHT11详细介绍(内含51和STM32代码)-CSDN博客
1.引脚部分
室内的温湿度
vcc | 3.3v |
DATA | PG9 |
GND | GND |
体温(先凑合一下)
vcc | 3.3v |
DATA | PB6 |
GND | GND |
2.代码部分
dht11.h
#ifndef __DHT11_H__
#define __DHT11_H__
#include "stm32f4xx.h"
#include "systick.h"
#define DHT11_PIN GPIO_Pin_9 //室内温湿度
#define DHT11_PIN_1 GPIO_Pin_6 //体温
void DHT11_Read_Data(void); //获取室内温湿度
void DHT11_2_Read_Data(void); //获取体温
#endif
dht11.c
#include "dht11.h"
/* PG9配置为推挽输出模式 */
void DHT11_Output_Mode(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOG, &GPIO_InitStructure);
}
/* 引脚输出高低电平 */
void DHT11_GPIO_OUT(uint8_t x)
{
if( x == 1 )
GPIO_SetBits(GPIOG, DHT11_PIN);
else
GPIO_ResetBits(GPIOG, DHT11_PIN);
}
/* PG9配置成浮空输入模式 */
void DHT11_Input_Moda(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOG, &GPIO_InitStructure);
}
/* 读取外部引脚的高低电平 */
uint8_t DHT11_GPIO_IN(void)
{
return GPIO_ReadInputDataBit(GPIOG, DHT11_PIN);
}
/* STM32输出起始信号 */
void DHT11_Start(void)
{
DHT11_Output_Mode();
DHT11_GPIO_OUT(0);
delay_ms(20);
DHT11_GPIO_OUT(1);
delay_us(30);
}
/* DHT11的应答信号 */
/* 成功返回0,失败返回1 */
uint8_t DHT11_ACK(void)
{
uint16_t count;
DHT11_Input_Moda();
if(DHT11_GPIO_IN() == Bit_RESET)
{
count = 0;
while(DHT11_GPIO_IN() == Bit_RESET)
{
count++;
if(count > 10)
{
return 1;
}
delay_us(10);
}
count = 0;
while(DHT11_GPIO_IN() == Bit_SET)
{
count ++;
if(count > 9)
{
return 1;
}
delay_us(10);
}
return 0;
}
return 1;
}
/* 读取传输每比特的数据 */
/* 返回对应的数据 */
uint8_t Read_Byte(void)
{
uint8_t i, temp =0;
for (i = 0; i < 8; i++)
{
while (DHT11_GPIO_IN() == Bit_RESET)
;
delay_us(35);
if (DHT11_GPIO_IN() == Bit_SET)
{
while (DHT11_GPIO_IN() == Bit_SET)
;
temp |= (uint8_t)(0x01 << (7 - i));
}
else
{
temp &= (uint8_t) ~(0x01 << (7 - i));
}
}
return temp;
}
uint8_t humi_int = 0;
uint8_t humi_deci = 0;
uint8_t temp_int = 0;
uint8_t temp_deci = 0;
uint8_t check_sum = 0;
void DHT11_Read_Data(void)
{
DHT11_Start();
if(DHT11_ACK() == 0)
{
humi_int = Read_Byte(); // 8bit湿度整数数据
humi_deci = Read_Byte(); // 8bit湿度小数数据
temp_int = Read_Byte(); // 8bit温度整数数据
temp_deci = Read_Byte(); // 8bit温度小数数据
check_sum = Read_Byte(); // 8bit校验和
DHT11_Output_Mode();
DHT11_GPIO_OUT(1);
delay_ms(200);
}
}
/* 下面是体温的函数 */
/* PC7,配置为推挽输出模式 */
void DHT11_2_Output_Mode(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = DHT11_PIN_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/* 引脚输出高低电平 */
void DHT11_2_GPIO_OUT(uint8_t x)
{
if( x == 1 )
GPIO_SetBits(GPIOB, DHT11_PIN_1);
else
GPIO_ResetBits(GPIOB, DHT11_PIN_1);
}
/* 配置成浮空输入模式 */
void DHT11_2_Input_Moda(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DHT11_PIN_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/* 读取外部引脚的高低电平 */
uint8_t DHT11_2_GPIO_IN(void)
{
return GPIO_ReadInputDataBit(GPIOB, DHT11_PIN_1);
}
/* STM32输出起始信号 */
void DHT11_2_Start(void)
{
DHT11_2_Output_Mode();
DHT11_2_GPIO_OUT(0);
delay_ms(20);
DHT11_2_GPIO_OUT(1);
delay_us(30);
}
/* DHT11的应答信号 */
/* 成功返回0,失败返回1 */
uint8_t DHT11_2_ACK(void)
{
uint16_t count;
DHT11_2_Input_Moda();
if(DHT11_2_GPIO_IN() == Bit_RESET)
{
count = 0;
while(DHT11_2_GPIO_IN() == Bit_RESET)
{
count++;
if(count > 10)
{
return 1;
}
delay_us(10);
}
count = 0;
while(DHT11_2_GPIO_IN() == Bit_SET)
{
count ++;
if(count > 9)
{
return 1;
}
delay_us(10);
}
return 0;
}
return 1;
}
/* 读取传输每比特的数据 */
/* 返回对应的数据 */
uint8_t Read_2_Byte(void)
{
uint8_t i, temp =0;
for (i = 0; i < 8; i++)
{
while (DHT11_2_GPIO_IN() == Bit_RESET)
;
delay_us(35);
if (DHT11_2_GPIO_IN() == Bit_SET)
{
while (DHT11_2_GPIO_IN() == Bit_SET)
;
temp |= (uint8_t)(0x01 << (7 - i));
}
else
{
temp &= (uint8_t) ~(0x01 << (7 - i));
}
}
return temp;
}
uint8_t humi_2_int = 0;
uint8_t humi_2_deci = 0;
uint8_t temp_2_int = 0;
uint8_t temp_2_deci = 0;
uint8_t check_2_sum = 0;
void DHT11_2_Read_Data(void)
{
DHT11_2_Start();
if(DHT11_2_ACK() == 0)
{
humi_2_int = Read_2_Byte(); // 8bit湿度整数数据
humi_2_deci = Read_2_Byte(); // 8bit湿度小数数据
temp_2_int = Read_2_Byte(); // 8bit温度整数数据
temp_2_deci = Read_2_Byte(); // 8bit温度小数数据
check_2_sum = Read_2_Byte(); // 8bit校验和
DHT11_2_Output_Mode();
DHT11_2_GPIO_OUT(1);
delay_ms(200);
}
}
调试代码 main.c
#include "stm32f4xx.h" // Device header
#include "OLED.h"
#include "systick.h"
#include "dht11.h"
extern uint8_t temp_2_int;
extern uint8_t temp_2_deci;
void init(void)
{
OLED_Init();
}
void show_oled(void)
{
DHT11_2_Read_Data(); //获取体温
OLED_ShowString(1, 1, "average:");
OLED_ShowString(2, 1, "temp:");
OLED_ShowNum(2 , 8 ,temp_2_int , 2 );
OLED_ShowString(2, 10, ".");
OLED_ShowNum(2 , 11 ,temp_2_deci , 2 );
OLED_ShowString(2, 14, "C");
OLED_ShowString(3, 1, "HR:");
}
int main(void)
{
init();
while(1)
{
show_oled();
}
}
效果如下
第五步 烟雾模块
1.引脚部分
VCC | 5V |
AO | PA4 |
DO | |
GND | GND |
这个模块涉及到ADC转换 需要有adc功能的引脚
2.代码部分
MQ2.h
#ifndef _MQ2_H
#define _MQ2_H
#include "stm32f4xx.h" // Device header
#include "systick.h"
#include "math.h"
#define SMOG_READ_TIMES 10 //定义烟雾传感器读取次数,读这么多次,然后取平均值
void MQ2_Init(void);
float Smog_Get_Vol(void); //读取MQ7传感器的电压值
float Smog_GetPPM(void);
#endif
MQ2.c
#include "MQ_adc.h"
#define CAL_PPM 10 // 校准环境中PPM值
#define RL 10 // RL阻值
#define R0 26 // R0阻值
void MQ2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
/* 引脚和ADC的时钟使能 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
/* 配置引脚为模拟功能模式 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //模拟功能模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* ADC的常规配置 */
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; //84MHz/2 = 42MHz
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //禁止MDA
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; //ADC通道采用间隔
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //分辨率
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //禁止扫描
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换模式
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//不需要外部触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
ADC_InitStructure.ADC_NbrOfConversion = 1; //一次转换
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_3Cycles);
/* 使能 ADC1 */
ADC_Cmd(ADC1, ENABLE);
}
uint32_t MQ2_ADC_Read(void)
{
ADC_SoftwareStartConv(ADC1);
while( ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}
u16 ADC1_Average_Data(u8 ADC_Channel)
{
u16 temp_val=0;
u8 t;
for(t=0;t<SMOG_READ_TIMES;t++)
{
temp_val+=MQ2_ADC_Read();
delay_ms(5);
}
temp_val/=SMOG_READ_TIMES;
return (u16)temp_val;
}
//读取MQ7传感器的电压值
float Smog_Get_Vol(void)
{
u16 adc_value = 0;
float voltage = 0;
adc_value = ADC1_Average_Data(ADC_Channel_4);
delay_ms(5);
voltage = (3.3/4096.0)*(adc_value);
return voltage;
}
// 计算Smog_ppm
float Smog_GetPPM()
{
float RS = (3.3f - Smog_Get_Vol()) / Smog_Get_Vol() * RL;
float ppm = 98.322f * pow(RS/R0, -1.458f);
return ppm;
}
测试用例
#include "stm32f4xx.h" // Device header
#include "OLED.h"
#include "systick.h"
#include "dht11.h"
#include "light_adc.h"
#include "ADC.h"
#include "MQ_adc.h"
extern uint8_t temp_2_int;
extern uint8_t temp_2_deci;
float GL5516_data = 0;
float PhotoResistor;
float Smog_ppm = 0;
void init(void)
{
OLED_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
ADC3_Config();
MQ2_Init();
}
void show_oled(void)
{
DHT11_2_Read_Data(); //获取体温
OLED_ShowString(1, 1, "average:");
OLED_ShowString(2, 1, "temp:");
OLED_ShowNum(2 , 8 ,temp_2_int , 2 );
OLED_ShowString(2, 10, ".");
OLED_ShowNum(2 , 11 ,temp_2_deci , 2 );
OLED_ShowString(2, 14, "C");
OLED_ShowString(3, 1, "HR:");
}
void light()
{
GL5516_data = Get_Conversion_value(ADC3, ADC_Channel_5, 20);
PhotoResistor = (10000*GL5516_data)/(5-GL5516_data);
LUX = GetLux(PhotoResistor);
}
int main(void)
{
init();
while(1)
{
Smog_ppm = Smog_GetPPM();
OLED_ShowNum(1,1,Smog_ppm,3);
}
}
第六步 光感模块
1.引脚部分
VCC | 3V |
AO | PF7 |
GND | GND |
该模块涉及ADC 模拟信号输入输入即可 另外需要一些参考量 如下代码
电阻与光强关系
/* GL5516光敏电阻的阻值与流明对应表 */
const PhotoRes_TypeDef GL5516[281] =
{
{40000, 1},
{26350, 2},
{20640, 3},
{17360, 4},
{15170, 5},
{13590, 6},
{12390, 7},
{11430, 8},
{10650, 9},
{9990, 10},
{9440, 11},
{8950, 12},
{8530, 13},
{8160, 14},
{7830, 15},
{7530, 16},
{7260, 17},
{7010, 18},
{6790, 19},
{6580, 20},
{6390, 21},
{6210, 22},
{6050, 23},
{5900, 24},
{5750, 25},
{5620, 26},
{5490, 27},
{5370, 28},
{5260, 29},
{5160, 30},
{5050, 31},
{4960, 32},
{4870, 33},
{4780, 34},
{4700, 35},
{4620, 36},
{4540, 37},
{4470, 38},
{4400, 39},
{4330, 40},
{4270, 41},
{4210, 42},
{4150, 43},
{4090, 44},
{4040, 45},
{3980, 46},
{3930, 47},
{3880, 48},
{3840, 49},
{3790, 50},
{3740, 51},
{3700, 52},
{3660, 53},
{3620, 54},
{3580, 55},
{3540, 56},
{3500, 57},
{3460, 58},
{3430, 59},
{3390, 60},
{3360, 61},
{3330, 62},
{3300, 63},
{3270, 64},
{3230, 65},
{3210, 66},
{3180, 67},
{3150, 68},
{3120, 69},
{3090, 70},
{3070, 71},
{3040, 72},
{3020, 73},
{2990, 74},
{2970, 75},
{2940, 76},
{2920, 77},
{2900, 78},
{2880, 79},
{2850, 80},
{2830, 81},
{2810, 82},
{2790, 83},
{2770, 84},
{2750, 85},
{2730, 86},
{2710, 87},
{2690, 88},
{2680, 89},
{2660, 90},
{2640, 91},
{2620, 92},
{2610, 93},
{2590, 94},
{2570, 95},
{2560, 96},
{2540, 97},
{2530, 98},
{2510, 99},
{2490, 100},
{2480, 101},
{2460, 102},
{2450, 103},
{2440, 104},
{2420, 105},
{2410, 106},
{2390, 107},
{2380, 108},
{2370, 109},
{2360, 110},
{2340, 111},
{2330, 112},
{2320, 113},
{2300, 114},
{2290, 115},
{2280, 116},
{2270, 117},
{2260, 118},
{2250, 119},
{2230, 120},
{2220, 121},
{2210, 122},
{2200, 123},
{2190, 124},
{2180, 125},
{2170, 126},
{2160, 127},
{2150, 128},
{2140, 129},
{2130, 130},
{2120, 131},
{2110, 132},
{2100, 133},
{2090, 134},
{2080, 135},
{2070, 136},
{2060, 137},
{2050, 138},
{2040, 139},
{2030, 141},
{2020, 142},
{2010, 143},
{2000, 144},
{1990, 145},
{1980, 147},
{1970, 148},
{1960, 149},
{1950, 150},
{1940, 152},
{1930, 153},
{1920, 154},
{1910, 155},
{1900, 157},
{1890, 158},
{1880, 160},
{1870, 161},
{1860, 162},
{1850, 164},
{1840, 165},
{1830, 167},
{1820, 168},
{1810, 170},
{1800, 171},
{1790, 173},
{1780, 175},
{1770, 176},
{1760, 178},
{1750, 180},
{1740, 181},
{1730, 183},
{1720, 185},
{1710, 187},
{1700, 188},
{1690, 190},
{1680, 192},
{1670, 194},
{1660, 196},
{1650, 198},
{1640, 200},
{1630, 202},
{1620, 204},
{1610, 206},
{1600, 208},
{1590, 210},
{1580, 212},
{1570, 215},
{1560, 217},
{1550, 219},
{1540, 222},
{1530, 224},
{1520, 226},
{1510, 229},
{1500, 231},
{1490, 234},
{1480, 237},
{1470, 239},
{1460, 242},
{1450, 245},
{1440, 248},
{1430, 250},
{1420, 253},
{1410, 256},
{1400, 259},
{1390, 262},
{1380, 266},
{1370, 269},
{1360, 272},
{1350, 275},
{1340, 279},
{1330, 282},
{1320, 286},
{1310, 289},
{1300, 293},
{1290, 297},
{1280, 300},
{1270, 304},
{1260, 308},
{1250, 312},
{1240, 317},
{1230, 321},
{1220, 325},
{1210, 330},
{1200, 334},
{1190, 339},
{1180, 344},
{1170, 348},
{1160, 353},
{1150, 358},
{1140, 364},
{1130, 369},
{1120, 374},
{1110, 380},
{1100, 386},
{1090, 391},
{1080, 397},
{1070, 403},
{1060, 410},
{1050, 416},
{1040, 423},
{1030, 430},
{1020, 436},
{1010, 444},
{1000, 451},
{990, 458},
{980, 466},
{970, 474},
{960, 482},
{950, 491},
{940, 499},
{930, 508},
{920, 517},
{910, 526},
{900, 536},
{890, 546},
{880, 556},
{870, 567},
{860, 578},
{850, 589},
{840, 600},
{830, 612},
{820, 625},
{810, 637},
{800, 650},
{790, 664},
{780, 678},
{770, 692},
{760, 707},
{750, 723},
{740, 739},
{730, 756},
{720, 773},
{710, 791},
{700, 809},
{690, 829},
{680, 849},
{670, 869},
{660, 891},
{650, 914},
{640, 937},
{630, 961},
{620, 987},
};
2.代码部分
ADC.h
#ifndef __ADC_H__
#define __ADC_H__
/* 包含官方头文件 */
#include "stm32f4xx.h"
/* 包含其它相关头文件 */
#include "light_adc.h"
#include "systick.h"
/* 外部接口函数声明 */
void ADC3_Config(void);
uint16_t Get_Adc(ADC_TypeDef *ADCx, uint8_t ADC_Channel);
uint16_t Get_Adc_Average(ADC_TypeDef *ADCx, uint8_t ADC_Channel, uint8_t times);
float Get_Conversion_value(ADC_TypeDef *ADCx, uint8_t ADC_Channel, uint8_t times);
#endif
adc.c
#include "adc.h"
void ADC3_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOF, &GPIO_InitStructure);
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADC3, &ADC_InitStructure);
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_Cmd(ADC3, ENABLE);
}
uint16_t Get_Adc(ADC_TypeDef *ADCx, uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADCx, ADC_Channel, 1, ADC_SampleTime_480Cycles);
ADC_DiscModeChannelCountConfig(ADC3, 1);
ADC_DiscModeCmd(ADC3,ENABLE);
ADC_SoftwareStartConv(ADCx);
while (!ADC_GetFlagStatus(ADCx, ADC_FLAG_EOC))
;
return ADC_GetConversionValue(ADCx);
}
uint16_t Get_Adc_Average(ADC_TypeDef *ADCx, uint8_t ADC_Channel, uint8_t times)
{
uint8_t i;
uint32_t temp_val = 0;
for (i = 0; i < times; i++)
{
temp_val += Get_Adc(ADCx, ADC_Channel);
delay_ms(5);
}
return temp_val / times;
}
float Get_Conversion_value(ADC_TypeDef *ADCx, uint8_t ADC_Channel, uint8_t times)
{
uint16_t adc_value;
float adc_vol;
adc_value = Get_Adc_Average(ADCx, ADC_Channel, times);
adc_vol = (float)adc_value * (3.3 / 4096);
return adc_vol;
}
这样我们便获取到了模拟电压值 通过模块给的公式或自己电路的特性计算光强即可
GL5516_data = Get_Conversion_value(ADC3, ADC_Channel_5, 20); //获取电压值
PhotoResistor = (10000*GL5516_data)/(5-GL5516_data); //获取电阻值
接下来查表即可
light_adc.h
#ifndef __LIGHT_ADC_H__
#define __LIGHT_ADC_H__
/* 包含官方头文件 */
#include "stm32f4xx.h"
typedef struct
{
unsigned short ohm;
unsigned short lux;
} PhotoRes_TypeDef;
extern uint16_t LUX;
uint16_t GetLux(float PhotoResistor);
#endif
light_adc.c
#include "light_adc.h"
/* 光照度 */
uint16_t LUX;
/* GL5516光敏电阻的阻值与流明对应表 */
const PhotoRes_TypeDef GL5516[281] =
{
{40000, 1},
{26350, 2},
{20640, 3},
{17360, 4},
{15170, 5},
{13590, 6},
{12390, 7},
{11430, 8},
{10650, 9},
{9990, 10},
{9440, 11},
{8950, 12},
{8530, 13},
{8160, 14},
{7830, 15},
{7530, 16},
{7260, 17},
{7010, 18},
{6790, 19},
{6580, 20},
{6390, 21},
{6210, 22},
{6050, 23},
{5900, 24},
{5750, 25},
{5620, 26},
{5490, 27},
{5370, 28},
{5260, 29},
{5160, 30},
{5050, 31},
{4960, 32},
{4870, 33},
{4780, 34},
{4700, 35},
{4620, 36},
{4540, 37},
{4470, 38},
{4400, 39},
{4330, 40},
{4270, 41},
{4210, 42},
{4150, 43},
{4090, 44},
{4040, 45},
{3980, 46},
{3930, 47},
{3880, 48},
{3840, 49},
{3790, 50},
{3740, 51},
{3700, 52},
{3660, 53},
{3620, 54},
{3580, 55},
{3540, 56},
{3500, 57},
{3460, 58},
{3430, 59},
{3390, 60},
{3360, 61},
{3330, 62},
{3300, 63},
{3270, 64},
{3230, 65},
{3210, 66},
{3180, 67},
{3150, 68},
{3120, 69},
{3090, 70},
{3070, 71},
{3040, 72},
{3020, 73},
{2990, 74},
{2970, 75},
{2940, 76},
{2920, 77},
{2900, 78},
{2880, 79},
{2850, 80},
{2830, 81},
{2810, 82},
{2790, 83},
{2770, 84},
{2750, 85},
{2730, 86},
{2710, 87},
{2690, 88},
{2680, 89},
{2660, 90},
{2640, 91},
{2620, 92},
{2610, 93},
{2590, 94},
{2570, 95},
{2560, 96},
{2540, 97},
{2530, 98},
{2510, 99},
{2490, 100},
{2480, 101},
{2460, 102},
{2450, 103},
{2440, 104},
{2420, 105},
{2410, 106},
{2390, 107},
{2380, 108},
{2370, 109},
{2360, 110},
{2340, 111},
{2330, 112},
{2320, 113},
{2300, 114},
{2290, 115},
{2280, 116},
{2270, 117},
{2260, 118},
{2250, 119},
{2230, 120},
{2220, 121},
{2210, 122},
{2200, 123},
{2190, 124},
{2180, 125},
{2170, 126},
{2160, 127},
{2150, 128},
{2140, 129},
{2130, 130},
{2120, 131},
{2110, 132},
{2100, 133},
{2090, 134},
{2080, 135},
{2070, 136},
{2060, 137},
{2050, 138},
{2040, 139},
{2030, 141},
{2020, 142},
{2010, 143},
{2000, 144},
{1990, 145},
{1980, 147},
{1970, 148},
{1960, 149},
{1950, 150},
{1940, 152},
{1930, 153},
{1920, 154},
{1910, 155},
{1900, 157},
{1890, 158},
{1880, 160},
{1870, 161},
{1860, 162},
{1850, 164},
{1840, 165},
{1830, 167},
{1820, 168},
{1810, 170},
{1800, 171},
{1790, 173},
{1780, 175},
{1770, 176},
{1760, 178},
{1750, 180},
{1740, 181},
{1730, 183},
{1720, 185},
{1710, 187},
{1700, 188},
{1690, 190},
{1680, 192},
{1670, 194},
{1660, 196},
{1650, 198},
{1640, 200},
{1630, 202},
{1620, 204},
{1610, 206},
{1600, 208},
{1590, 210},
{1580, 212},
{1570, 215},
{1560, 217},
{1550, 219},
{1540, 222},
{1530, 224},
{1520, 226},
{1510, 229},
{1500, 231},
{1490, 234},
{1480, 237},
{1470, 239},
{1460, 242},
{1450, 245},
{1440, 248},
{1430, 250},
{1420, 253},
{1410, 256},
{1400, 259},
{1390, 262},
{1380, 266},
{1370, 269},
{1360, 272},
{1350, 275},
{1340, 279},
{1330, 282},
{1320, 286},
{1310, 289},
{1300, 293},
{1290, 297},
{1280, 300},
{1270, 304},
{1260, 308},
{1250, 312},
{1240, 317},
{1230, 321},
{1220, 325},
{1210, 330},
{1200, 334},
{1190, 339},
{1180, 344},
{1170, 348},
{1160, 353},
{1150, 358},
{1140, 364},
{1130, 369},
{1120, 374},
{1110, 380},
{1100, 386},
{1090, 391},
{1080, 397},
{1070, 403},
{1060, 410},
{1050, 416},
{1040, 423},
{1030, 430},
{1020, 436},
{1010, 444},
{1000, 451},
{990, 458},
{980, 466},
{970, 474},
{960, 482},
{950, 491},
{940, 499},
{930, 508},
{920, 517},
{910, 526},
{900, 536},
{890, 546},
{880, 556},
{870, 567},
{860, 578},
{850, 589},
{840, 600},
{830, 612},
{820, 625},
{810, 637},
{800, 650},
{790, 664},
{780, 678},
{770, 692},
{760, 707},
{750, 723},
{740, 739},
{730, 756},
{720, 773},
{710, 791},
{700, 809},
{690, 829},
{680, 849},
{670, 869},
{660, 891},
{650, 914},
{640, 937},
{630, 961},
{620, 987},
};
/**
* @brief 获取光照度函数
* @param PhotoResistor:光敏电阻电阻值
* @retval 获取到的光照度
*/
uint16_t GetLux(float PhotoResistor)
{
int i = 0;
uint16_t lux = 0;
for (i = 0; i < 281; i++)
{
if (PhotoResistor < 620)
{
lux = 1000;
break;
}
else if (PhotoResistor > GL5516[i].ohm)
{
lux = GL5516[i].lux;
break;
}
}
return lux;
}
下面是我的测试用例
main.c
#include "stm32f4xx.h" // Device header
#include "OLED.h"
#include "systick.h"
#include "dht11.h"
#include "light_adc.h"
#include "ADC.h"
extern uint8_t temp_2_int;
extern uint8_t temp_2_deci;
float GL5516_data = 0;
float PhotoResistor;
void init(void)
{
OLED_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
ADC3_Config();
}
void show_oled(void)
{
DHT11_2_Read_Data(); //获取体温
OLED_ShowString(1, 1, "average:");
OLED_ShowString(2, 1, "temp:");
OLED_ShowNum(2 , 8 ,temp_2_int , 2 );
OLED_ShowString(2, 10, ".");
OLED_ShowNum(2 , 11 ,temp_2_deci , 2 );
OLED_ShowString(2, 14, "C");
OLED_ShowString(3, 1, "HR:");
}
int main(void)
{
init();
while(1)
{
GL5516_data = Get_Conversion_value(ADC3, ADC_Channel_5, 20);
PhotoResistor = (10000*GL5516_data)/(5-GL5516_data);
LUX = GetLux(PhotoResistor);
OLED_ShowNum(1,1,LUX,3);
delay_s(1);
}
}
第七步 血氧模块
1.引脚部分
这里面涉及很多寄存器操作 用的是iic的协议 可以自己去学习一下
vin | 3.3v |
scl | PB8 |
sda | PB9 |
GND | GND |
INT | PE15 |
与oled一个引脚 设备地址不同 可以挂载多个设备
该代码涉及iic协议 通过iic协议控制内部寄存器 调节模式及其读取数据 iic代码和上面OLED是一个代码无改动 控制寄存器规则及其读取到的数据处理参考如下链接
STM32通过IIC驱动MAX30102心率血氧传感器_srm32驱动max30102传感器与-CSDN博客
MAX30102脉搏血氧仪和心率传感器(含寄存器介绍)_max30102心率算法详解-CSDN博客
建议自己看手册配置寄存器锻炼动手和理解能力 (我的老师告诉我不要抄代码 0.0)
2.代码部分
max30102.h
#ifndef __MYIIC_H
#define __MYIIC_H
#include "systick.h"
#include "stm32f4xx.h" // Device header
#include "i2c.h"
#include "stdbool.h"
#define max30102_WR_address_w 0xAE //写
#define max30102_WR_address_r 0xAF //读
#define INT_pin GPIO_Pin_15
//register addresses
#define REG_INTR_STATUS_1 0x00
#define REG_INTR_STATUS_2 0x01
#define REG_INTR_ENABLE_1 0x02
#define REG_INTR_ENABLE_2 0x03
#define REG_FIFO_WR_PTR 0x04
#define REG_OVF_COUNTER 0x05
#define REG_FIFO_RD_PTR 0x06
#define REG_FIFO_DATA 0x07
#define REG_FIFO_CONFIG 0x08
#define REG_MODE_CONFIG 0x09
#define REG_SPO2_CONFIG 0x0A
#define REG_LED1_PA 0x0C
#define REG_LED2_PA 0x0D
#define REG_PILOT_PA 0x10
#define REG_MULTI_LED_CTRL1 0x11
#define REG_MULTI_LED_CTRL2 0x12
#define REG_TEMP_INTR 0x1F
#define REG_TEMP_FRAC 0x20
#define REG_TEMP_CONFIG 0x21
#define REG_PROX_INT_THRESH 0x30
#define REG_REV_ID 0xFE
#define REG_PART_ID 0xFF
bool max30102_write_reg(uint8_t uch_addr, uint8_t uch_data);
bool max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data);
bool maxim_max30102_init(void);
bool maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led);
bool max30102_reset();
#endif
max30102.c
#include "max30102.h"
bool max30102_write_reg(uint8_t uch_addr, uint8_t uch_data)
{
IIC_Start();
IIC_SendByte(max30102_WR_address_w);
if (IIC_WaitSlaveAck() != 0)
{
goto cmd_fail;
}
IIC_SendByte(uch_addr);
if (IIC_WaitSlaveAck() != 0)
{
goto cmd_fail;
}
IIC_SendByte(uch_data);
if (IIC_WaitSlaveAck() != 0)
{
goto cmd_fail;
}
IIC_Stop();
return true;
cmd_fail:
IIC_Stop();
return false;
}
bool max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data)
{
IIC_Start();
IIC_SendByte(max30102_WR_address_w);
if (IIC_WaitSlaveAck() != 0)
{
goto cmd_fail;
}
IIC_SendByte((uint8_t)uch_addr);
if (IIC_WaitSlaveAck() != 0)
{
goto cmd_fail;
}
IIC_Start();
IIC_SendByte(max30102_WR_address_r);
if (IIC_WaitSlaveAck() != 0)
{
goto cmd_fail;
}
{
*puch_data = IIC_ReadByte();
IIC_MasterAck(1);
}
IIC_Stop();
return true;
cmd_fail:
IIC_Stop();
return false;
}
bool maxim_max30102_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = INT_pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOE, &GPIO_InitStructure);
if(!max30102_write_reg(REG_INTR_ENABLE_1, 0xc0)) // INTR setting
return false;
if(!max30102_write_reg(REG_INTR_ENABLE_2, 0x00))
return false;
if(!max30102_write_reg(REG_FIFO_WR_PTR, 0x00))
return false;
if(!max30102_write_reg(REG_OVF_COUNTER, 0x00))
return false;
if(!max30102_write_reg(REG_FIFO_RD_PTR, 0x00))
return false;
if(!max30102_write_reg(REG_FIFO_CONFIG, 0x6f))
return false;
if(!max30102_write_reg(REG_MODE_CONFIG, 0x03))
return false;
if(!max30102_write_reg(REG_SPO2_CONFIG, 0x2F))
return false;
if(!max30102_write_reg(REG_LED1_PA, 0x17))
return false;
if(!max30102_write_reg(REG_LED2_PA, 0x17))
return false;
if(!max30102_write_reg(REG_PILOT_PA, 0x7f))
return false;
return true;
}
bool maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)
{
uint32_t un_temp;
uint8_t uch_temp;
*pun_ir_led = 0;
*pun_red_led = 0;
max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);
max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);
IIC_Start();
IIC_SendByte(max30102_WR_address_w);
if (IIC_WaitSlaveAck() != 0)
{
goto cmd_fail;
}
IIC_SendByte((uint8_t)REG_FIFO_DATA);
if (IIC_WaitSlaveAck() != 0)
{
goto cmd_fail;
}
IIC_Start();
IIC_SendByte(max30102_WR_address_r);
if (IIC_WaitSlaveAck() != 0)
{
goto cmd_fail;
}
un_temp = IIC_ReadByte();
IIC_MasterAck(0);
un_temp <<= 16;
*pun_red_led += un_temp;
un_temp = IIC_ReadByte();
IIC_MasterAck(0);
un_temp <<= 8;
*pun_red_led += un_temp;
un_temp = IIC_ReadByte();
IIC_MasterAck(0);
*pun_red_led += un_temp;
un_temp = IIC_ReadByte();
IIC_MasterAck(0);
un_temp <<= 16;
*pun_ir_led += un_temp;
un_temp = IIC_ReadByte();
IIC_MasterAck(0);
un_temp <<= 8;
*pun_ir_led += un_temp;
un_temp = IIC_ReadByte();
IIC_MasterAck(0);
*pun_ir_led += un_temp;
*pun_red_led &= 0x03FFFF;
*pun_ir_led &= 0x03FFFF;
IIC_Stop();
return true;
cmd_fail:
IIC_Stop();
return false;
}
bool max30102_reset()
{
if(!max30102_write_reg(REG_MODE_CONFIG, 0x40))
return false;
else
return true;
}
hr.h 数据处理 有个参照
#include "hr.h"
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;
}
}
hr.h
#ifndef HR_H_
#define HR_H_
#include "stm32f4xx.h" // Device header
#include "stdbool.h"
#include "max30102.h"
#include "i2c.h"
#define true 1
#define false 0
#define FS 50
#define BUFFER_SIZE (FS* 3)
#define MA4_SIZE 4
#define min(x,y) ((x) < (y) ? (x) : (y))
#define MAX_BRIGHTNESS 255
#define START 100
#define DATA_LENGTH 500
static int32_t an_x[ BUFFER_SIZE];
static int32_t an_y[ BUFFER_SIZE];
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);
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);
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);
void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance);
void maxim_sort_ascend(int32_t *pn_x, int32_t n_size);
void maxim_sort_indices_descend(int32_t *pn_x, int32_t *pn_indx, int32_t n_size);
#endif
接口都准备好了 接下来在main.c添加获取数值的函数 下面是我的用例
#include "stm32f4xx.h" // Device header
#include "OLED.h"
#include "systick.h"
#include "dht11.h"
#include "light_adc.h"
#include "ADC.h"
#include "MQ_adc.h"
#include "i2c.h"
#include "hr.h"
#include "max30102.h"
extern uint8_t temp_2_int;
extern uint8_t temp_2_deci;
float GL5516_data = 0;
float PhotoResistor;
float Smog_ppm = 0;
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;
void get_hr(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
I2C_SIM_Config();
max30102_reset(); //resets the MAX30102
// initialize serial communication at 115200 bits per second:
//read and clear status register
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(GPIO_ReadInputDataBit(GPIOE , INT_pin)==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
}
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(GPIO_ReadInputDataBit(GPIOE , INT_pin)== 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;
}
}
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);
OLED_ShowNum(1 , 1 ,n_heart_rate , 2 );
OLED_ShowNum(2 , 1 ,ch_hr_valid , 2 );
OLED_ShowNum(3 , 1 ,n_sp02 , 2 );
OLED_ShowNum(4 , 1 ,ch_spo2_valid , 2 );
delay_ms(10);
}
}
void init(void)
{
OLED_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
ADC3_Config();
MQ2_Init();
}
void show_oled(void)
{
DHT11_2_Read_Data(); //获取体温
OLED_ShowString(1, 1, "average:");
OLED_ShowString(2, 1, "temp:");
OLED_ShowNum(2 , 8 ,temp_2_int , 2 );
OLED_ShowString(2, 10, ".");
OLED_ShowNum(2 , 11 ,temp_2_deci , 2 );
OLED_ShowString(2, 14, "C");
OLED_ShowString(3, 1, "HR:");
}
void light()
{
GL5516_data = Get_Conversion_value(ADC3, ADC_Channel_5, 20);
PhotoResistor = (10000*GL5516_data)/(5-GL5516_data);
LUX = GetLux(PhotoResistor);
}
int main(void)
{
init();
while(1)
{
get_hr();
}
}
第八步 调节ESP8266模块上阿里云
1.步骤
原理:说白了就是通过串口发送at命令连接到阿里云的接口上去
1.首先就是烧录固件教程如下链接
乐鑫ESP8266烧录固件、升级最新固件、刷MQTT固件_at version:1.7.4.0-CSDN博客
烧录固件完成后 去阿里云注册账号 和创建设备 入口在下面
物联网平台_设备接入_设备管理_监控运维_数据服务-阿里云 (aliyun.com)
注册就不多说了 点击“管理控制台” 开通一下公共实例