前情提要:
DHT11是一款单总线温湿度传感器,外部接线非常简单,只需要三根线(Vcc,Gnd,Data)。
要驱动这个传感器,首先要了解传输时序,和数据格式。
-
DHT11数据格式
一次完整的数据传输为40bit,高位先出。数据分小数部分和整数部分,数据格式:
- 8bit湿度整数数据
- 8bit湿度小数数据
- 8bit温度整数数据
- 8bit温度小数数据
- 8bit校验和
若数据传送正确,则校验和数据 = “8bit 湿度整数数据 +8bit 湿度小数数据+8bit温度整数数据 +8bit 温度小数数据”所得结果的末8位。
以下是几个函数的详细讲解,每个函数名下面都有此函数的代码,和代码实现出的效果(逻辑分析仪截图),完整代码在文末。
主要由以下几个函数组成:
- 主机发送开始信号 DHT11_Start();
//*************************************************
//主机发送开始信号,开始采集
void DHT11_Start(void)
{
DH11_GPIO_Init_OUT(); //输出模式
dht11_low; //拉低总线20ms
delay_ms(20);
dht11_high; //释放总线20~40us
delay_us(30);
DH11_GPIO_Init_IN(); //切输入模式
}
逻辑分析仪画面:
- DHT11响应信号 是在采集过程中的一部分,体现在DHT11_Read_Data();函数里面
DHT11_Read_Data();内部主要是是读取5个字节,组成一个完整的数据
*读取一个字节,后面有讲(讲解的顺序是根据dht11时许来讲的)
//*************************************************
//获取数据(获取5个字节)
void DHT11_Read_Data(void)
{
unsigned int R_H,R_L,T_H,T_L;
unsigned char RH,RL,TH,TL,CHECK;
DHT11_Start(); //主机发送信号
dht11_high; //拉高电平
if( Read_Data == 0 ) //判断DHT11是否响应
{
while( Read_Data == 0); //低电平变高电平,等待低电平结束
while( Read_Data == 1); //高电平变低电平,等待高电平结束
R_H = DHT11_Read_Byte();
R_L = DHT11_Read_Byte();
T_H = DHT11_Read_Byte();
T_L = DHT11_Read_Byte();
CHECK = DHT11_Read_Byte(); //接收5个数据
dht11_low; //当最后一bit数据传送完毕后,DHT11拉低总线 50us
delay_us(55); //这里延时55us
dht11_high; //随后总线由上拉电阻拉高进入空闲状态。
if(R_H + R_L + T_H + T_L == CHECK) //和检验位对比,判断校验接收到的数据是否正确
{
RH = R_H;
RL = R_L;
TH = T_H;
TL = T_L;
}
}
rec_data[0] = RH;
rec_data[1] = RL;
rec_data[2] = TH;
rec_data[3] = TL;
}
if( Read_Data == 0 ) //判断DHT11是否响应
{
while( Read_Data == 0); //低电平变高电平,等待低电平结束
while( Read_Data == 1); //高电平变低电平,等待高电平结束
这几行是判断DHT11响应信号
响应信号对应的是“ while( Read_Data == 0); //低电平变高电平,等待低电平结束”
释放总线对应的是“ while( Read_Data == 1); //高电平变低电平,等待高电平结束”
*这里的讲解可能不是很明白,欢迎提出意见和一起讨论
- 读取一个字节 DHT11_Read_Byte();
//*************************************************
//获取一个字节
//函数返回值:data
unsigned char DHT11_Read_Byte(void)
{
unsigned char i = 0;
unsigned char data;
for(i=0;i<8;i++) //1个数据就是1个字节byte,1个字节byte有8位bit
{
while( Read_Data == 0); //从1bit开始,低电平变高电平,等待低电平结束
delay_us(30); //延迟30us是为了区别数据0和数据1,0只有26~28us
data <<= 1; //左移1位
if( Read_Data == 1 ) //如果过了30us还是高电平的话就是数据1
{
data |= 1; //数据+1
}
while( Read_Data == 1 ); //高电平变低电平,等待高电平结束
}
return data;
逻辑分析仪画面:
此段借鉴csdn博主“安赫'”的内容:
还是像检测响应时间那样计算高电平持续时间那就太麻烦了!!!
数据“0”的高电平持续 26~28us,数据“1”的高电平持续70us,每一位数据前都有 50us 的起始时隙。如果我们取一个中间值 40us 来区分数据“0”和数据“1”的时隙。
当数据位之前的 50us 低电平时隙过后,总线肯定会拉高,此时延时 40us 后检测总线状态,如果为高,说明此时处于 70us 的时隙,则数据为“1”;如果为低,说明此时处于下一位数据 50us 的开始时隙,那么上一位数据肯定是“0”。
为什么延时 40us?
由于误差的原因,数据“0”时隙并不是准确 26~28us,可能比这短,也可能比这长。
当数据“0”时隙大于 26~28us 时,
如果延时太短,无法判断当前处于数据“0”的时隙还是数据“1”的时隙;
如果延时太长,则会错过下一位数据前的开始时隙,导致检测不到后面的数据
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/m0_55849362/article/details/126426768
while( Read_Data == 0); //从1bit开始,低电平变高电平,等待低电平结束
delay_us(30); //延迟30us是为了区别数据0和数据1,0只有26~28us
这两条就实现了判断1和0
在54us左右的低电平时隙结束后,延时30us
此时,如果数据是0,那么此时读到的是低电平(由于0的高电平时间为20us左右,延时30us采样正好超过了20us的高电平)看图中我画的1那里。
如果数据是1,那么此时读到的是高电平(由于1的高电平时间为70us左右,延时30us采样正好在高电平里面)看图中我画的2那里。
以此类推,for循环采样8次就是一个字节了。
//******************************************************************************************************
源码部分
dht11.c
#include "stm32f10x.h"
#include "DHT11.h"
#include "delay.h"
//*************************************************
//数据变量数组
unsigned int rec_data[4];// 占用2个字节
//*************************************************
//一个io口分别收和发,所以要对应输入/输出两个状态
//输出
void DH11_GPIO_Init_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//输入
void DH11_GPIO_Init_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//*************************************************
//主机发送开始信号,开始采集
void DHT11_Start(void)
{
DH11_GPIO_Init_OUT(); //输出模式
dht11_low; //拉低总线20ms
delay_ms(20);
dht11_high; //释放总线20~40us
delay_us(30);
DH11_GPIO_Init_IN(); //切输入模式
}
//*************************************************
//获取一个字节
//函数返回值:data
unsigned char DHT11_Read_Byte(void)
{
unsigned char i = 0;
unsigned char data;
for(i=0;i<8;i++) //1个数据就是1个字节byte,1个字节byte有8位bit
{
while( Read_Data == 0); //从1bit开始,低电平变高电平,等待低电平结束
delay_us(30); //延迟30us是为了区别数据0和数据1,0只有26~28us
data <<= 1; //左移1位
if( Read_Data == 1 ) //如果过了30us还是高电平的话就是数据1
{
data |= 1; //数据+1
}
while( Read_Data == 1 ); //高电平变低电平,等待高电平结束
}
return data;
}
//*************************************************
//获取数据(获取5个字节)
void DHT11_Read_Data(void)
{
unsigned int R_H,R_L,T_H,T_L;
unsigned char RH,RL,TH,TL,CHECK;
DHT11_Start(); //主机发送信号
dht11_high; //拉高电平
if( Read_Data == 0 ) //判断DHT11是否响应
{
while( Read_Data == 0); //低电平变高电平,等待低电平结束
while( Read_Data == 1); //高电平变低电平,等待高电平结束
R_H = DHT11_Read_Byte();
R_L = DHT11_Read_Byte();
T_H = DHT11_Read_Byte();
T_L = DHT11_Read_Byte();
CHECK = DHT11_Read_Byte(); //接收5个数据
dht11_low; //当最后一bit数据传送完毕后,DHT11拉低总线 50us
delay_us(55); //这里延时55us
dht11_high; //随后总线由上拉电阻拉高进入空闲状态。
if(R_H + R_L + T_H + T_L == CHECK) //和检验位对比,判断校验接收到的数据是否正确
{
RH = R_H;
RL = R_L;
TH = T_H;
TL = T_L;
}
}
rec_data[0] = RH;
rec_data[1] = RL;
rec_data[2] = TH;
rec_data[3] = TL;
}
//*************************************************
dht11.h
#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f10x.h" // Device header
//void DHT11_init(void);//初始化
//****************************************************************
//define
//****************************************************************
#define dht11_high GPIO_SetBits(GPIOB, GPIO_Pin_12)
#define dht11_low GPIO_ResetBits(GPIOB, GPIO_Pin_12)
#define Read_Data GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)
//****************************************************************
//以下是内部调用部分
//****************************************************************
void DHT11_GPIO_Init_OUT(void);
void DHT11_GPIO_Init_IN(void);
void DHT11_Start(void);
unsigned char DHT11_Read_Byte(void);//获取一个字节
//****************************************************************
//以下是用户调用部分
//****************************************************************
void DHT11_Read_Data(void);//获取数据
#endif