1.温湿度的一次完整的数据包括:
一次完整的数据传输为40bit,高位先出。
数据格式:
8bit湿度整数数据
+8bit湿度小数数据
+8bi温度整数数据
+8bit温度小数数据
+8bit校验(校验和的值是前四个字节数据的和)
用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结果,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据.如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集.采集数据后转换到低速模式。
2.温湿度的数据正确验证
8bit校验(校验和的值是前四个字节数据的和) = 其他的四个数据相加
8bit湿度整数数据
+8bit湿度小数数据
+8bi温度整数数据
+8bit温度小数数据
3. 获取一次温湿度的 步骤:
步骤一:DHT11上电后,要等待1S以越过不稳定状态在此期间不能发送任何指令,测试环境温湿度数据,并记录数据,同时DHT11的DATA 数据线由上拉电阻拉高一直保持高电平;此时DHT11的DATA引脚处于输入状态,时刻检测外部信号。
步骤二:微处理器的I/O设置为输出同时输出低电平,且低电平保持时间不能小于18ms,然后微处理器的I/O设置为输入状态,由于上拉电阻,微处理器的I/O即 DHT11的DATA数据线也随之变高,等待DHT11作出回答信号
步骤三:DHT11的DATA引脚检测到外部信号有低电平时,等待外部信号低电平结束延迟后DHT11的DATA引脚处于输出状态,输出80微秒的低电平作为应答信号,紧接着输出80微秒的高电平通知外设准备接收数据,微处理器的I/O此时处于输入状态,检测到I/О有低电平(DHT11回应信号)后,等待80微秒的高电平后的数据接收。
步骤四:由DHT11的 DATA引脚输出40位数据,微处理器根据I/O电平的变化接收40位数据,位数据“O”的格式为:50微秒的低电平和26-28微秒的高电平,位数据“1”的格式为:50微秒的低电平加70微秒的高电平。结束信号:DHT11的DATA引脚输出40位数据后,继续输出低电平50微秒后转为输入
状态,由于上拉电阻随之变为高电平。但DHT11内部重测环境温湿度数据,并记录数据,等待外部信号的到来。
4.代码:
delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include <sys.h>
void delay_init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);
#endif
delay.c
#include "delay.h"
#include "sys.h"
//
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_UCOS
#include "includes.h" //ucos 使用
#endif
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//使用SysTick的普通计数模式对延迟进行管理(支持ucosii)
//包括delay_us,delay_m
//All rights reserved
//********************************************************************************
//修改说明
//无
//
static u8 fac_us=0;//us延时倍乘数
static u16 fac_ms=0;//ms延时倍乘数,在ucos下,代表每个节拍的ms数
#ifdef OS_CRITICAL_METHOD //如果OS_CRITICAL_METHOD定义了,说明使用ucosII了.
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的时钟服务程序
OSIntExit(); //触发任务切换软中断
}
#endif
//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init(u8 SYSCLK)
{
#ifdef OS_CRITICAL_METHOD //如果OS_CRITICAL_METHOD定义了,说明使用ucosII了.
u32 reload;
#endif
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8; //不论是否使用ucos,fac_us都需要使用
#ifdef OS_CRITICAL_METHOD //如果OS_CRITICAL_METHOD定义了,说明使用ucosII了.
reload=SYSCLK/8; //每秒钟的计数次数 单位为K
reload*=1000000/OS_TICKS_PER_SEC;//根据OS_TICKS_PER_SEC设定溢出时间
//reload为24位寄存器,最大值:16777216,在168M下,约合0.7989s左右
fac_ms=1000/OS_TICKS_PER_SEC;//代表ucos可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD=reload; //每1/OS_TICKS_PER_SEC秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
fac_ms=(u16)fac_us*1000;//非ucos下,代表每个ms需要的systick时钟数
#endif
}
#ifdef OS_CRITICAL_METHOD //如果OS_CRITICAL_METHOD定义了,说明使用ucosII了.
//延时nus
//nus:要延时的us数.
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
tcnt=0;
OSSchedLock(); //阻止ucos调度,防止打断us延时
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow;//这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break;//时间超过/等于要延迟的时间,则退出.
}
};
OSSchedUnlock(); //开启ucos调度
}
//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{
if(OSRunning==OS_TRUE&&OSLockNesting==0)//如果os已经在跑了
{
if(nms>=fac_ms)//延时的时间大于ucos的最少时间周期
{
OSTimeDly(nms/fac_ms); //ucos延时
}
nms%=fac_ms; //ucos已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((u32)(nms*1000)); //普通方式延时
}
#else //不用ucos时
//延时nus
//nus为要延时的us数.
//注意:nus的值,不要大于798915us
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}
while((temp&0x01)&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对168M条件下,nms<=798ms
void delay_xms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}
while((temp&0x01)&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//延时nms
//nms:0~65535
void delay_ms(u16 nms)
{
u8 repeat=nms/540; //这里用540,是考虑到某些客户可能超频使用,
//比如超频到248M的时候,delay_xms最大只能延时541ms左右了
u16 remain=nms%540;
while(repeat)
{
delay_xms(540);
repeat--;
}
if(remain)delay_xms(remain);
}
#endif
usart1.h
void usart1init(void);
void myprintf(char str[]);
void printt(char str[],unsigned char num);
usart1.c
#include "stm32f4xx.h"
#include "usart1.h"
#include "beep.h"
#include "string.h"
#include "delay.h"
#include "usart1.h"
#include "stdio.h"
char wendu[30] = "";
void mydelay(void);
char str[128] = "\0";
char i = 0 ;
//编写一个串口调试工具
void myprintf(char str[])
{
int i = 0 ;
//str[i] = '\0';
for(i = 0 ; i < strlen(str);i++)
{
USART_SendData(USART1,str[i]);
delay_ms(1);
}
}
//串口1初始化函数
void usart1init(void)
{
//1.串口时钟问题
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//2.GPIO时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
//3.引脚复用映射:每个引脚多个功能,用哪一个呢?
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
//4.配置GPIO功能:复用功能;
GPIO_InitTypeDef USART1struct;
//填写引脚
USART1struct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10 ;
//填写速度
USART1struct.GPIO_Speed = GPIO_Speed_2MHz;
//填写电阻
USART1struct.GPIO_PuPd = GPIO_PuPd_UP ;
//填写模式复用?
USART1struct.GPIO_Mode = GPIO_Mode_AF;
GPIO_Init(GPIOA,&USART1struct);
//5.配置NVIC
NVIC_InitTypeDef nvicstruct;
nvicstruct.NVIC_IRQChannel = USART1_IRQn;
nvicstruct.NVIC_IRQChannelPreemptionPriority = 1;
nvicstruct.NVIC_IRQChannelSubPriority = 1;
nvicstruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvicstruct);
//6.串口配置
USART_InitTypeDef usart1struct;
usart1struct.USART_BaudRate = 115200 ;
usart1struct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart1struct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx ;
usart1struct.USART_Parity = USART_Parity_No;
usart1struct.USART_StopBits = USART_StopBits_1;
usart1struct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&usart1struct);
//7串口使能
USART_Cmd(USART1,ENABLE);
//8串口中断使能
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
}
//0 .判断数据是否来了?
//1.具体的数据?数值还是字符?
//2.把数据发回去?
//3.上机如果发过来一串字符串,openled1 下位机能否把led1打开
//作业2个:1openled 2closeled 3openbeep 4closebeep 5 配置串口4
//修改时钟配置 stm32f4xx.h 中搜索 HSE_VALUE 把25 改成 8
//void USART1_IRQHandler()
//{
// OpenBeep();
//
// unsigned char res = 0 ;
// res = USART_ReceiveData(USART1);
// if(res == 1)
// {
// OpenBeep();
// USART_SendData(USART1,res);
// }
//
// if(res == 'a')
// {
// CloseBeep();
// USART_SendData(USART1,res);
// }
//
// if(res == 't')
// {
// CloseBeep();
// USART_SendData(USART1,res);
// }
//
// USART_ClearITPendingBit(USART1,USART_IT_RXNE);
//}
void USART1_IRQHandler()
{
//i = 0 ;
//memset(str,0,sizeof(str));
unsigned char res = 0;
//OpenBeep();
static int i = 0 ;
str[i] = USART_ReceiveData(USART1);
delay_us(1);
i++;
str[i] = USART_ReceiveData(USART1);
delay_us(1);
str[2] = USART_ReceiveData(USART1);
delay_us(1);
str[3] = '\0';
//
// USART_SendData(USART1,str[0]);
// delay_us(1);
//
// USART_SendData(USART1,str[1]);
// delay_us(1);
//
// USART_SendData(USART1,str[2]);
// delay_us(1);
// if(!strcmp("xyz",str))
// OpenBeep();
// if(!strcmp(str,"openled"))
// {
// OpenBeep();
//
//
// }
// if(!strcmp(str,"closeled"))
// CloseBeep();
//
// //清除缓冲区
// for(i = 0 ; str[i] != '\0' ; i++ )
// {
// OpenBeep();
// str[i] = USART_ReceiveData(USART1);
// delay_us(1);
// }
}
//输出字符串和数值的组合
void printt(char str[],unsigned char num)
{
unsigned char i = 0 ;
for(i = 0 ; i < strlen(str);i++)
{
USART_SendData(USART1,str[i]);
delay_ms(5);
}
//格式化函数,把十六进制转换成字符串
sprintf(wendu,"%d",num);
//添加一个换行符
strcat(wendu,"\r\n");
//串口打印函数
for(i = 0 ; i < strlen(wendu);i++)
{
USART_SendData(USART1,wendu[i]);
delay_ms(5);
}
}
DHt11.c (main.c 在里面)
#include "stm32f4xx.h"
#include "delay.h"
//#include "beep.h"
//#include "key.h"
//#include "led.h"
#include "usart1.h"
extern char wendu;
//1.dht11初始化函数
//2.dht11输入模式函数
//3.dht11输出模式函数
//4.dht11读数据函数
//5.dht11读位函数
char buff[5] = "";
char humi = 0 ,temp = 0 ;
char flg = 100 ,flg1 = 100;
//读出来的数据是16进制也没事。把二进制发送上位机也没事。只要是一个字节就行。
//引脚:PA3
void DHT11_init()
{
//1,配置时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
//2.配置引脚
GPIO_InitTypeDef DHT11struct;
//填写引脚
DHT11struct.GPIO_Pin = GPIO_Pin_3;
//填写速度
DHT11struct.GPIO_Speed = GPIO_Speed_2MHz;
DHT11struct.GPIO_PuPd = GPIO_PuPd_NOPULL ;
//填写电阻
DHT11struct.GPIO_Mode = GPIO_Mode_OUT;
//即可以输出高电平也可以输出低电平
DHT11struct.GPIO_OType = GPIO_OType_OD;
GPIO_Init(GPIOA,&DHT11struct);
//把引脚拉高。
GPIO_SetBits(GPIOA,GPIO_Pin_3);
}
//输入模式函数
void DHT11_InMode()
{
//配置引脚
GPIO_InitTypeDef DHT11struct;
//填写引脚
DHT11struct.GPIO_Pin = GPIO_Pin_3;
//填写速度
DHT11struct.GPIO_Speed = GPIO_Speed_2MHz;
//填写电阻
DHT11struct.GPIO_Mode = GPIO_Mode_IN;
DHT11struct.GPIO_PuPd = GPIO_PuPd_NOPULL ;
//即可以输出高电平也可以输出低电平
GPIO_Init(GPIOA,&DHT11struct);
}
//输入模式函数
void DHT11_OutMode()
{
//配置引脚
GPIO_InitTypeDef DHT11struct;
//填写引脚
DHT11struct.GPIO_Pin = GPIO_Pin_3;
//填写速度
DHT11struct.GPIO_Speed = GPIO_Speed_2MHz;
//填写电阻
//即可以输出高电平也可以输出低电平
DHT11struct.GPIO_OType = GPIO_OType_OD;
DHT11struct.GPIO_Mode = GPIO_Mode_OUT;
DHT11struct.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOA,&DHT11struct);
GPIO_SetBits(GPIOA,GPIO_Pin_3);
}
unsigned char DHT11_Read_Bit()
{
unsigned char readbit = 0 ;
unsigned int i = 0 ;
for(i = 0 ; i < 8 ; i++)
{
//为了避免最高位被移出去。
readbit = readbit << 1 ;
//1在内部也要等待高电平结束。因为在循环过程中,有高电平的持续时间。
//高电平是上一个周期的数据
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3));
//2等待引脚变为高电平,如果是低电平就卡主,等低电平结束
while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3));
//如果是高电平持续30us
//这里是读位数据。
//如果高电平持续时间较长,也就是30us之后还是高电平
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3))
{
//如果读到的是高电平并且 50us以后又读一次还是高电平。
delay_us(30);
}
//又读一次还是高电平,那么久写数据1,否则就是低电平。
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3))
readbit |= 1 ;
else
readbit |= 0 ;
}
return readbit;
}
unsigned char DHT11_ReadData(char * humi,char *temp)
{
unsigned char num = 0 ;
unsigned int i = 0 ;
//1.主机发送开始信号20ms,低电平20ms。至少拉低。
GPIO_ResetBits(GPIOA,GPIO_Pin_3);
delay_ms(20);
//2.主机拉高30us
GPIO_SetBits(GPIOA,GPIO_Pin_3);
delay_us(30);
DHT11_InMode();
//如果发过来的是高电平,就等待高电平结束。
//因为外拉电阻,如果读到的是高电平,就等待高电平结束。
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3));
//3如果读到的是低电平,那么就等待低电平结束。
while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3))
{
//响应信号
flg = 0xee ;
}
//4等待高电平结束。高点平持续时间太久也不行。
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3))
{ //如果低电平持续的时间太长,那么也要退出。
delay_us(4);
num++;
if(num > 20)
return 1;
}
//高电平一旦结束,那么就是信号来了。
//开始读数据
// buff[0] = DHT11_Read_Bit();
// buff[1] = DHT11_Read_Bit();
// buff[2] = DHT11_Read_Bit();
// buff[3] = DHT11_Read_Bit();
// buff[4] = DHT11_Read_Bit();
for(i = 0 ; i < 5 ; i++)
{
//读五次数据,五个字节。
buff[i] = DHT11_Read_Bit();
}
//函数结束以后要把引脚调整为输出模式
DHT11_OutMode();
//5.校验数据
if(buff[4] == buff[0] + buff[1] + buff[2] + buff[3] )
{
*humi = buff[0];
*temp = buff[2] ;
i = 0 ;
return 0;
}
else
return 1 ;
}
int main()
{
//延时函数初始化
//看来这里面有干扰,做例程的时候,就只先看这一个引脚。
//一个引脚一个初始化,其他的都不要参与进来。
//用在线调试系统来做就行。
delay_init(168);
usart1init();
DHT11_init();
while(1)
{
//把数据读到全局变量中,然后再显示出来就行了。
if(!DHT11_ReadData(&humi,&temp))
{
delay_ms(100);
printt("温度是:",temp);
printt("湿度是:",humi);
}
else
{
delay_ms(1000);
}
}
}