参考文章:《DHT11详细介绍》:http://t.csdn.cn/L0M3r
本文实验平台:MM32F527x平台验证通过 开源器件这个系列将记录一系列我接触到的各种器件驱动模块,现在开源的资料众多,编程已经是基于模块化的思想,尤其是Arduino、Micropython等语法简单的开发语言的入局,大大降低了开发者开发难度。对于最经典的32位产品STM32而言,意法半导体公司也停止了标准库的更新,转而投入HAL库开发。以上无一例外是隐藏了部分底层原理结构换取调用的便捷性,这种对用户友好的做法很大程度上会带来系统性能的牺牲。接下来的这款DHT11的开发虽然简单,但是debug过程也遇到了一些与性能有关系的问题值得记录,与其让它烂在网盘里,不如发出来可能会对别人有所启发。

DHT11工作时序

DHT11采用单总线通信方式,过程为: 1 主从机引脚初始化,主机拉高总线至VCC,从机高电平下无应答; 2 主机发出通信请求,拉低总线电平至少18ms,再拉高总线电平20-40us,切换至浮空输入监听从机响应; 3 从机识别到主机信号,发起响应:从机拉低总线电平80us,再拉高电平80us,然后传输数据; 4 数据的组成为40位:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit校验和,每个字节数据高位在前,低位在后。 5 发送完成从机会拉低总线电平,主机应切换到推挽输出,重新拉高总线电平。

几点说明: 1 浮空输入:因为是需要监听从机低电平响应,主机不应该主动拉高或者拉低电平,因此采取浮空输入; 2 数据格式:传输每一位数据,都是以50us的低电平开始,后面如果接26-28us表示数据“0”,如果是70us 则表示数据“1”;

代码 dht11.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef __DHT11_H
#define __DHT11_H

#define DATA B4 //数据通信引脚

typedef struct //结构体存数据
{
uint8_t humi_int;
uint8_t humi_deci;
uint8_t temp_int;
uint8_t temp_deci;
uint8_t check_sum;

}DHT11_Data_TypeDef;

void DHT11_GPIO_Config(void);
uint8_t Read_DHT11(DHT11_Data_TypeDef *DHT11_Data);
static uint8_t Read_Byte(void);
#endif

代码 dht11.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include "dht11.h"
#include "headfile.h"
void DHT11_GPIO_Config(void) //通信引脚初始化--高电平
{
gpio_init(DATA, GPO, GPIO_HIGH, GPO_PUSH_PULL);
gpio_set_level(DATA,1);
}

static uint8_t Read_Byte(void) //读取一个字节函数
{
uint8_t i, temp=0;

for(i=0;i<8;i++) //8位数据
{
while(gpio_get_level(DATA)==0); //每位数据有50us低电平,略过
system_delay_us(40); //电平0有26-28us高电平,若是0延时过后会检测到0,1则检测1
if(gpio_get_level(DATA)==1) //发送的是高电平
{
while(gpio_get_level(DATA)==1); //等发完
temp|=(uint8_t)(0x01<<(7-i)); //将高电平1移到对应高位
}
else
{
temp&=(uint8_t)~(0x01<<(7-i)); //将低电平0移到对应高位
}

}
return temp;
}

uint8_t Read_DHT11(DHT11_Data_TypeDef *DHT11_Data)
{
gpio_set_dir(DATA,GPO,GPO_PUSH_PULL);
gpio_set_level(DATA,0); //拉低总线至少18ms
system_delay_ms(20);
gpio_set_level(DATA,1); //拉高20-40us
system_delay_us(30);
gpio_set_dir(DATA, GPI, GPI_FLOATING_IN); //浮空输入
if(gpio_get_level(DATA)==0) //从机响应
{
while(gpio_get_level(DATA)==0);
while(gpio_get_level(DATA)==1); //跳过两个响应电平
DHT11_Data->humi_int= Read_Byte(); //读8位数据湿度整数

DHT11_Data->humi_deci= Read_Byte(); //读8位数据湿度小数

DHT11_Data->temp_int= Read_Byte(); //读8位数据温度整数

DHT11_Data->temp_deci= Read_Byte(); //读8位数据温度小数

DHT11_Data->check_sum= Read_Byte(); //读8位校验和

gpio_set_dir(DATA, GPO,GPO_PUSH_PULL); //读完拉高电平
gpio_set_level(DATA,1);

if(DHT11_Data->check_sum == (uint8_t)(DHT11_Data->humi_int + DHT11_Data->humi_deci + DHT11_Data->temp_int+ DHT11_Data->temp_deci))
{return 1;} //校验和正确,数据正确
else return 0; //校验和错误
}
else
return 0; //从机保持高电平无响应
}

代码 main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "headfile.h"
#include "oled.h"
#include "dht11.h"
int main (void)
{
clock_init(SYSTEM_CLOCK_120M); // 初始化芯片时钟 工作频率为 120MHz
debug_init();
OLED_Init();
DHT11_Data_TypeDef DHT11_Data;
DHT11_GPIO_Config();
while(1)
{
while(!Read_DHT11(&DHT11_Data)); //跳过读取失败
OLED_ShowString(4,1,"success");
OLED_ShowString(1,1,"Tem:");
OLED_ShowNum(1,5,DHT11_Data.temp_int,2);
OLED_ShowString(1,8,".");
OLED_ShowNum(1,9,DHT11_Data.temp_deci,1);
OLED_ShowString(1,11,"C");
OLED_ShowString(2,1,"Hum:");
OLED_ShowNum(2,5,DHT11_Data.humi_int,2);
OLED_ShowString(2,8,".");
OLED_ShowNum(2,9,DHT11_Data.humi_deci,2);
OLED_ShowString(2,12,"%");
system_delay_ms(500);
}
}

debug遇到的问题

源程序是基于mm32寄存器库开发的,原来习惯在stm32标准库中大量使用了头文件调用和函数调用语句能正常实现,移植在mm32平台下载发现一直无法正常显示;进入debug模式发现是由于DHT11一直无法正常响应造成的,反复卡在while循环中,反复检查时序无明显问题,测量查看引脚电平无误,考虑到是延时出现问题;由于没有示波器难以精细确认完整时序,查看系统延迟函数,发现us延时函数注释“程序跳转会使实际延时比设定延时高一些”,考虑到有可能是上述大量头文件和函数调用带来us级的延时,而DHT11对时序的把控相当严格,造成这个错误;遂把头文件调用函数全部用显式加入C文件中,成功解决,以上均是排查的猜想,由于身边没有精确仪器,不能百分百确定是跳转产生时延的问题,这一点尚需商榷。