先来认识一下这个传感器
DHT11温湿度传感器图片
以下是他的封装尺寸以及管脚介绍
1、VDD供电 3.3~5.5V DC
2、DATA 串行数据,单总线
3、NC 空脚
4、GND接地,电源负极
DHT11和单片机之间的通信时通过io口来通信的,以下是它的通信介绍
DHT11 器件采用简化的单总线通信。单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。设备(主机或从机)通过一个漏枀开路或三态端口连至该数据线,以允许设备在不发送数据时能够释放总线,而让其它设备使用总线;单总线通常要求外接一个约 4.7kΩ 的上拉电阻,这样,当总线闲置时,其状态为高电平。由于它们是主从结极,只有主机呼叫从机时,从机才能应答,因此主机访问器件都必须严格遵循单总线序列,如果出现序列混乱,器件将不响应主机。
单总线传送数据位定义DATA 用于微处理器与 DHT11 之间的通讯和同步,采用单总线数据格式,一次传送 40 位数据,高位先出。
上面一段话中所说的主机就是Stm32单片机,从机就是传感器内的一个8位单片机,传输模式是主机给从机发送一个信号,然后从机应答后返还给主机数据。主机和从机依靠一个IO口的高低电平进行通信。既然用到IO口那么我们就必须找到并配置这个IO口。
简单认识之后看开发板原理图DTH11是连接在哪个IO口
这个传感器连接在PG11 io口上,那么我们需要配置这个端口
之前配置io口,需要开启GPIOx的时钟,创建一个GPIO_initTypeDef结构体,选择管脚、输出速率、IO口输入输出模式,最后调用函数初始化IO
PG11在GPIOG上,那初始化GPIO函数的第一个参数选择GPIOG,管脚和速率我们选择GPIO_Pin_11、GPIO_Speed_50MHz,只剩下IO口输入输出模式这个参数通过DTH11数据手册的时序图介绍可知
时序图有实线和虚线、实线代表主机信号(单片机信号)、虚线代表从机信号(DTH11响应的信号).
(1)主机通过拉低IO口之后至少延迟18ms,然后拉高IO口与从机通信,这个过程我们站在32芯片单片机的角度来看,io口当然要配置成输出模式。(2)那么从机响应信号后给主机返回信号,信号到达主机,主机通过读取IO口高低电平状态来读取数据,这个过程当然IO口要配置成输入模式。
这里主机既要求IO口配置成输出模式,从机又要求IO口配置成输入模式,我们怎么解决这个矛盾?
查询GPIO口输出模式得到将IO口配置成开漏模式可以解决这个矛盾,至此配置IO口的所有参数确定
dht11.h头文件代码:
#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f10x.h"
#define dht11_High() GPIO_SetBits(GPIOG,GPIO_Pin_11)
#define dht11_low() GPIO_ResetBits(GPIOG,GPIO_Pin_11)
#define dht11_readG11() GPIO_ReadInputDataBit(GPIOG, GPIO_Pin_11)
struct dht11_data
{
int8_t tem;
uint8_t humidity;
};
extern struct dht11_data sj;
extern uint8_t shuju[5];
void dht11_config(void);
uint8_t dht11_readdata(void);
#endif
DTH11配置函数:
void dht11_config(void)
{
//初始化io口
//开启APB2时钟树上的GPIOG的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);
//配置PG11管脚的输出模式为开漏输出,输出速率为50MHz
GPIO_InitTypeDef inittypedef={0};
inittypedef.GPIO_Pin=GPIO_Pin_11;
inittypedef.GPIO_Speed=GPIO_Speed_50MHz;
inittypedef.GPIO_Mode=GPIO_Mode_Out_OD;
//初始化GPIO函数
GPIO_Init(GPIOG,&inittypedef);
//将PG11管脚设为高电平
dht11_High();
}
配置完之后就需要考虑获取数据的流程了
首先了解需要获取的数据格式
DHT11传输的数据格式:
8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验位。
可以看到他传输的数据有温度、湿度、一字节的校验位。那么校验位的依据是什么?来看一个例子
我们可以通过数据传输的时序图来编程,以下是时序图
传输数据步骤:
/------------------------第一个信号----------------------------/
我们当前是站在主机角度编程,那么第一个时序应该拉低总线、延迟18ms、拉高总线。这样从机就会感应到这个信号
/------------------------第二个信号----------------------------/
之后的信号都是主机感应从机输入到IO口的高低电平
第二个信号等待一个低电平,如果等待时间内低电平未到达就传输错误。
再等待一个高电平,如果等待时间内低电平未到达就传输错误。之后开始接受数据
/-----------------------接收数据信号-------------------------/
开始接受数据“0”和“1”,这里的0和1数据是这样的0数据:
*****先等待一个将总线拉低54us的信号,然后等待一个将总线拉高23us~27us的信号
1数据:
*****先等待一个将总线拉低54us的信号,然后等待一个将总线拉高68us~74us的信号
那么如何判断信号是0或1呢?我们可以先等待总线拉低然后利用延时函数延时54us,然后延时30us,此时读取总线的状态,如果是高电平那么就是数据“1”,否则为“0”.
最后加上结束信号
上代码
uint8_t dht11_readdata(void)
{
uint8_t waittime=0;
//第一个信号
dht11_High();
dht11_low();
Delay_ms(20);
dht11_High();
//第二个信号
while(dht11_readG11()==1)//等待一个低信号
{
Delay_us(1);
waittime++;
if(waittime>=100)
{
return 1;
}
}
waittime=0;
while(dht11_readG11()==0)//等待一个高信号
{
Delay_us(1);
waittime++;
if(waittime>=100)
{
return 1;
}
}
//开始读取数据
for(int i=0;i<40;i++)
{
waittime=0;
//等待一个低信号
while(dht11_readG11()==1)
{
Delay_us(1);
waittime++;
if(waittime>=100)
{
return 1;
}
}
waittime=0;
//等待一个高信号
while(dht11_readG11()==0)
{
Delay_us(1);
waittime++;
if(waittime>=100)
{
return 1;
}
}
Delay_us(30);
if(dht11_readG11()==0)
{
//数据为0
shuju[i/8]&=1<<(7-i%8);
}else{
//数据为1
shuju[i/8]|=~(1<<(7-i%8));
}
}
//结束信号
Delay_us(54);
dht11_High();
//验证数据是否正确
if(shuju[0]+shuju[1]+shuju[2]+shuju[3]==shuju[4])
{
//数据正确
sj.humidity=shuju[0];
sj.tem=shuju[2];
if((shuju[3]&1<<7)!=0)//如果温度最高位为1,是负数
{
sj.tem=-shuju[2];
}
return 0;
}
else{
printf("数据校验失败\r\n");
}
return 2;
}