在正式开篇叙述前,这里建议大家可以回顾之前的内容,避免对本文的部分内容理解不清问题,查看整个教程:【Freertos实战】零基础制作基于stm32的物联网温湿度检测(教程非常简易)
本章开始讲解温湿度模块,并利用上章讲的ESP8266模块,将采集得到的温湿度信息上传到云端,并在云端上显示信息。
一、DHT11模块介绍
DHT11的供电电压为3~5.5 V。传感器上电后,要等待 1s 以越过不稳定状态,在此期间无需发送任何指令。我自己的话是用的3.3v电压,与stm32主控电压一致。DATA引脚接IO口,大家可以与我一致采用PA11。
模块的工作温度可在0°到50°之间,我们DIY作品一般都在室温下,工作环境是搓搓有余的了。
型号 | 测量范围 | 测湿精度 | 测温精度 |
DHT11 | 20-90%RH 0-50摄氏度°C | 士5%RH | 士2℃ |
二、通信过程
1.数据帧
DHT11通过单根数据线实现双向通信,所以整个通信的状态属于你发我收,你收我发的状态。当我需要获取温湿度的信息时,DHT11会发送5字节的数据,这5字节就组成了一个数据帧,内容为:
我们依次按位进行接收,每8位数据存入一个字节中,为了避免信号干扰的情况,导致数据出现错误,在结尾处多了一段校验码。在接收完所有的字节后,我们需要将前四个字节相加,若前四个字节全等于校验码,则这段温湿度的数据是正确的。我们后续就可以上传至云端,反之,则可以丢弃了这个数据。
2.时序图
下面的通信均以PA11口为IO口与DATA进行通信。先贴出硬件初始化部分。
#ifndef __DHT11_H
#define __DHT11_H
#include "sys.h"
//IO方向设置
#define DHT11_IO_IN() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=8<<12;}
#define DHT11_IO_OUT() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=3<<12;}
#define DHT11_DQ_OUT PAout(11) //数据端口 PA0
#define DHT11_DQ_IN PAin(11) //数据端口 PA0
u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11
#endif
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PG端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PG11端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化IO口
GPIO_SetBits(GPIOA,GPIO_Pin_11); //PG11 输出高
DHT11_Rst(); //复位DHT11
return DHT11_Check();//等待DHT11的回应
}
数据帧是站在字节的角度上进行考虑,同时还要知道每个字节中的0和1在时序中的状态是怎么样的,什么条件下DHT11知道它该发数据帧给我,什么时候它处于接收主机控制的状态。这里对照数据手册,罗列出数据时序图 整体过程分为3个步骤:
①主机先发送开始信号,主机信号线拉高准备接收数据。
②从机会返回一个响应信号进行应答,并将信号拉高准备输出
③开始接收数据(一次接收40位)
步骤1
//复位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
}
步骤2
//等待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的0、1时序
在开始步骤三前,我们先讲一下对于DHT11模块,它的0和1分别是什么样的形式。
与我们常接触的vcc和gnd代表0和1不同,DHT11发出的0、1时序是以延时的保持时间长短来代表的。我们可以把这一段的时序理解为,DHT11会先把数据线拉低50us,然后延时等待40us,然后再去读取信号线的电平,如果为低电平,则为位“0”;如果为高电平,则为位“1”。
//从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;
}
步骤3
//从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;
}
三、创建Freertos任务
在了解完硬件知识和编写完驱动文件后,这里就可以正式创建Freertos任务,将DHT11的温湿度获取放在Freertos上运行,并通过esp8266将数据上传云端。
引入头文件,创建OLED任务句柄及上传温湿度任务句柄,OLED任务实现及上传温湿度任务实现,设置全局变量,接收DHT11模块的温湿度信息,实现温湿度采集在OLED下位机显示和信息上报云端。
* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "Task.h"
#include "queue.h"
/* 开发板硬件bsp头文件 */
#include "delay.h"//延时函数
#include "led.h"//LED灯
#include "OLED.h"//显示屏
#include "esp8266.h"//物联网模块
#include "dht11.h"//温湿度模块
/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t Send_Data_Task_Handle = NULL;/* 上传温湿度任务句柄 */
static TaskHandle_t OLED_Task_Handle = NULL;/* OLED任务句柄 */
/*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */
static void Send_Data_Task(void* pvParameters);/* 上传温湿度任务实现 */
static void OLED_Task(void* pvParameters);/* OLED任务实现 */
static void BSP_Init(void);/* 用于初始化板载相关资源 */
/*
/*
*************************************************************************
* 全局变量
*************************************************************************
*/
u8 humidity = 0;//湿度
u8 temp = 0;//温度
/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* OLED 初始化 */
OLED_Init();
/* 温湿度 初始化 */
DHT11_Init();
/* esp8266 初始化 */
uart_init(115200);
}
/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
**********************************************************************/
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建Send_Data_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )Send_Data_Task, /* 任务入口函数 */
(const char* )"Send_Data_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&Send_Data_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
//printf("创建Send_Data_Task任务成功!\r\n");
/* 创建OLED_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )OLED_Task, /* 任务入口函数 */
(const char* )"OLED_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&OLED_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
//printf("创建OLED_Task任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
/**********************************************************************
* @ 函数名 : Send_Data_Task
* @ 功能说明: Send_Data_Task
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void Send_Data_Task(void* parameter)
{
esp8266_Init();
u8 Humi = 0;//湿度
u8 Temp = 0;//温度
while (1)
{
if(Temp!=temp && Recv_LED_Flag == 0)
{
esp8266_mqtt_send_int("Temp", temp); // 上传温度值
Temp = temp;
OLED_Clear();
}
if(Humi!=humidity && Recv_LED_Flag == 0)
{
esp8266_mqtt_send_int("Humi", humidity); // 上传湿度值
Humi = humidity;
OLED_Clear();
}
vTaskDelay(1000); /* 延时100个tick */
//esp8266_mqtt_send_bool("LED", false);
}
}
/**********************************************************************
* @ 函数名 : OLED_Task
* @ 功能说明: OLED_Task
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void OLED_Task(void* parameter)
{
while (1)
{
DHT11_Read_Data(&temp,&humidity); // 读取湿度值
OLED_ShowString(1,1,"Temp:");
OLED_ShowString(2,1,"Humi:");
OLED_ShowString(3,1,"id:");
OLED_ShowNum(1,6,temp,2);
OLED_ShowNum(2,6,humidity,2);
OLED_ShowNum(3,4,id,2);
vTaskDelay(300); /* 延时1000个tick */
}
}