DHT11
- 前面一直在讲串口类设备,今天讲IO类设备,不同的接口方式
目录
- tip:可以适当的总结keil的错误,因为在keil出现的错误来来回回就只有几种错误,总结好,以后遇到错误,就可以更快的解决。
查资料
- 上网查找官方文档,先看引脚
- DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合 传感器。
-- 测量的范围
-- DHT11硬件层
-- DHT11 协议层
DATA 用于微处理器与 DHT11 之间的通讯和同步,采用单总线数据格式,一 次通讯时间 4ms 左右,数据分小数部分和整数部分
一次完整的数据传输为 40bit,高位先出。
数据格式:8bit 湿度整数数据+8bit 湿度小数数据 +8bi 温度整数数据+8bit 温度小数数据 +8bit 校验和校验:
校验和 = 8bit 湿度整数数据+8bit 湿度小数数据+8bi 温度整数数据+8bit 温度小数数据的后8 位
-- 通讯时序:
输出:
空闲状态下:主机发送高电平
主机起始信号:发送至少 18ms 低电平
发送 20-40us 的高电平
输入:
DHT 响应信号:发送 80us 的低电平
发送 80us 的高电平
DHT 数据:信号 0/1 判断 保存 校验
DHT 结束信号:从机发送 50us 的低电平 主机拉高进入空闲状态
信号 0 判断:
发送 50us 的低电平 发送 26-28us 高电平
信号 1 判断 :
发送 50us 的低电平 发送 70us 高电平
文档的梳理
- 根据时序图和流程图可知DHT11的传输数据的流程
-- 重点(代码就是根据时序图来编写的)
代码编写
初始化(时钟、IO、其他)
-- 这里采用宏定义的方式将电平拉高拉低,使代码更加简洁明了,
#define DHT_OUT_L GPIO_ResetBits(GPIOG,GPIO_Pin_11)
#define DHT_OUT_H GPIO_SetBits(GPIOG, GPIO_Pin_11)
#define DHT_IN GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_11)//读取输入的数据
void dht11_init(void)
{
//时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);
//IO PG11
GPIO_InitTypeDef GPIO_InitStructure = {0}; //定义结构体变量,并且将结构体变量赋初值
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //复用推挽
GPIO_Init(GPIOG, &GPIO_InitStructure);
//其他 -- 根据官方文档可知,在DHT11上电后,需要等待至少2s才完成传感器的初始化。初始化期间,传感器接入单总线的微处理器I/O应配置为开漏模式并输出高电平,以保证单总线处于空闲状态(高电平)。
DHT_OUT_H;
Delay_nms(20);(2000);
}
- 在dht11.h里定义结构体
typedef struct {
float tem;//温度
float hum;//湿度
}DHT;
单总线的读取流程
1、主机发送复位信号
DHT11 的初始化过程同样分为复位信号和响应信号。 首先主机拉低总线至少18ms,然后再拉高总线,延时 20~40us,取中间值 30us,此时复位信号发送完毕
从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。
//1、起始信号
DHT_OUT_L;
Delay_nms(20);
DHT_OUT_H;
2、DHT11响应信号
DHT11 检测到复位信号后,触发一次采样,并拉低总线 80us 表示响应信号,告诉主机数据已经准备好了;
然后 DHT11 拉高总线 80us,之后开始传输数据。如果检测到响应信号为高电平,则 DHT11 初始化失败,请检查线路是否连接正常。
- 这里不用if来判断的原因是,说是延时80us,但实际的代码执行会有误差,程序也有被打断的情况,所以用while循环来延时,如果DHT11向单片机发送高电平,那将会一直在while循环中等待低电平,如果超过1000us,就不再等了,此为超时检测
//2、响应信号
num = 0 ;
//不用if,用while(时间范围判断)
while(DHT_IN == 1)//主机等待从机发来低电平
{
//做一个超时判断
num++;
Delay_nus(1);
if(num>1000) //>83,时间要比它大
return;
}
num = 0 ;
while(DHT_IN == 0)//主机等待从机发来高电平
{
num++;
Delay_nus(1);
if(num>1000)
return;
}
3、数据传输
数据“0”的高电平持续 26~28us,数据“1”的高电平持续70us,每一位数据前都有 50us 的起始时隙。如果我们取一个中间值 40us 来区分数据“0”和数据“1”的时隙。
当数据位之前的 50us 低电平时隙过后,总线肯定会拉高,此时延时 40us 后检测总线状态,如果为高,说明此时处于 70us 的时隙,则数据为“1”;如果为低,说明此时处于下一位数据 50us 的开始时隙,那么上一位数据肯定是“0”。
为什么延时 40us?
由于误差的原因,数据“0”时隙并不是准确 26~28us,可能比这短,也可能比这长。
当数据“0”时隙大于 26~28us 时,
如果延时太短,无法判断当前处于数据“0”的时隙还是数据“1”的时隙;
如果延时太长,则会错过下一位数据前的开始时隙,导致检测不到后面的数据
//3、数据接收
for(uint8_t i=0;i<5;i++)//循环40次,接收40位数据
{
for(uint8_t j=0;j<8;j++)
{
num = 0 ;
while(DHT_IN == 1)//等待低电平到来
{
num++;
Delay_nus(1);
if(num>1000)
return;
}
num = 0 ;
while(DHT_IN == 0)//等待高电平的到来
{
num++;
Delay_nus(1);
if(num>1000)
return;
}
Delay_nus(40); //数据0的话,延时后是低电平,如果是1的话,延时后是高电平
if(DHT_IN ==0)
{
buff[i] &= ~(0x1<<7-j);
}
else
{
buff[i] |= (0x1<<7-j);
}
}
}
- 通信完毕,释放总线
//通信完毕后,释放总线
DHT_OUT_H;
4、校验数据
//4、数据校验
uint8_t data = buff[0]+buff[1]+buff[2]+buff[3];
if(data != buff[4])
{
printf("校验错误");
return;
}
5、数据获取
- 数值转换为10进制,还包括小数和整数,正数和负数
//5、数据获取
if(buff[1]<10) //如果只有一位小数
dht.hum = buff[0]+buff[1]*0.1;
else if(buff[1]>=10)//两位
dht.hum = buff[0]+buff[1]*0.01;
//温度小数部分最高位是否为1
if(buff[3] & (0x1<<7))//负值
{
buff[3] &= ~(0x1<<7);//将最高位清0
if(buff[3]<10) //如果只有一位小数
dht.tem = (buff[2]+buff[3]*0.1)*(-1);
else if(buff[3]>=10)
dht.tem = (buff[2]+buff[3]*0.01)*(-1);
}
else//正值
{
buff[3] &= ~(0x1<<7);
if(buff[3]<10) //如果只有一位小数
dht.tem = buff[2]+buff[3]*0.1;
else if(buff[3]>=10)
dht.tem = buff[2]+buff[3]*0.01;
}
printf("tem:%.2f℃\r\n",dht.tem);
printf("hum:%.2f%%RH\r\n",dht.hum);
main.c
int main()
{
SysTick_Config(72000);
usart_init();
dht11_init();
while(1)
{
if(dhtime >=2000)
{
dhtime =0 ;
get_dht11_val();
}
}
}
补充: stlink有个调试的功能
- 注:进入调试,不能点击板子上的复位按键
- 添加断电(注意:有阴影的地方才能添加断点,因为有阴影的地方是程序运行的代码)