第三十章 DHT11数字温湿度传感器 本章,我们将介绍数字温湿度传感器DHT11的使用,与前一章的温度传感器相比,该传感器不但能测温度,还能测湿度。我们将学习如何获取DHT11传感器的温湿度数据,并把数据显示在LCD上。 本章分为如下几个小节: 30.1 DHT11及工作时序简介 30.2 硬件设计 30.3 程序设计 30.4 下载验证 30.1 DHT11及工作时序简介 30.1.1 DHT11简介 DHT11是一款温湿度一体化的数字传感器。该传感器包括一个电容式测湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。DHT11与单片机之间能采用简单的单总线进行通信,仅仅需要一个I/O口。传感器内部湿度和温度数据40Bit的数据一次性传给单片机,数据采用校验和方式进行校验,有效的保证数据传输的准确性。DHT11功耗很低,5V电源电压下,工作平均最大电流0.5mA。 DHT11的技术参数如下: l 工作电压范围:3.3V ~ 5.5V l 工作电流:平均0.5mA l 输出:单总线数字信号 l 测量范围:湿度5 ~ 95%RH,温度-20 ~ 60℃ l 精度:湿度±5%,温度±2℃ l 分辨率:湿度1%,温度0.1℃ DHT11的管脚排列如图30.1.1所示: 图30.1.1.1 DHT11管脚排列图 30.1.2 DHT11工作时序简介 虽然DHT11与DS18B20类似,都是单总线访问,但是DHT11的访问,相对DS18B20来说简单很多。下面我们先来看看DHT11的数据结构。 DHT11数字温湿度传感器采用单总线数据格式。即,单个数据引脚端口完成输入输出双向传输。其数据包由5byte(40bit)组成。数据分小数部分和整数部分,一次完整的数据传输为40bit,高位先处。DHT11的数据格式为:8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数部分+8bit校验和。其中校验和数据为前面四个字节相加。 传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。例如,某次从DHT11读到的数据如图30.1.2.1所示: 图30.1.2.1 某次读取到DHT11数据 由以上数据就可得到湿度和温度的值,计算方法: 湿度 = byte4 . byte3 = 45.0(%RH) 温度 = byte2 . byte1 = 28.0(℃) 校验 = byte4 + byte3 + byte2 + byte1 = 73 (= 湿度 + 温度) (校验正确) 可以看出,DHT11的数据格式十分简单的,DHT11和MCU的一次通信最大为34ms左右,建议主机连续读取时间间隔不要小于2s。 下面,我们介绍一下DHT11的传输时序。DHT11的数据发送流程如图30.1.2.2所示: 图30.1.2.2 DHT11数据发送流程图 首先主机发送开始信号,即:拉低数据线,保持t1(至少18ms)时间,然后拉高数据线t2(10~35us)时间,然后读取DHT11的响应,正常的话,DHT11会拉低数据线,保持t3(78~88us)时间,作为响应信号,然后DHT11拉高数据线,保持t4(80~92us)时间后,开始输出数据。 DHT11输出数字‘0’时序如图30.1.2.3所示: 图30.1.2.3 DHT11数字‘0’时序图 DHT11输出数字‘1’的时序如图30.1.2.4所示: 图30.1.2.4 DHT11输出数字‘1’时序图 DHT11输出数字‘0’和‘1’时序,一开始都是DHT11拉低数据线54us,后面拉高数据线保持的时间就不一样,数字‘0’就是23~27us,而数字‘1’就是68~74us。 通过以上了解,我们就可以通过ESP32F103来实现对DHT11的读取了。DHT11的介绍就到这里,更详细的介绍,请参考DHT11数据手册。 30.2 硬件设计 30.2.1 例程功能 DHT11每隔100ms左右读取一次数据,并把温度显示在LCD上。LED闪烁用于提示程序正在运行。 30.2.2 硬件资源 1. XL9555 IIC_INT-IO0(需在P5连接IO0) IIC_SDA-IO41 IIC_SCL-IO42 2. SPILCD CS-IO21 SCK-IO12 SDA-IO11 DC-IO40(在P5端口,使用跳线帽将IO_SET和LCD_DC相连) PWR- IO1_3(XL9555) RST- IO1_2(XL9555) 3. DHT11 1WIRE_DQ-IO0 30.2.3 原理图 DHT11接口与ESP32的连接关系跟上一章节中DS18B20和ESP32的关系是一样的,使用到的GPIO口是IO0。这里原理图就不列出来了,可以翻看上一章节原理图。 DHT11和DS18B20的接口是共用一个的,不过DHT11有4条腿,需要把U6的4个接口都用上,将DHT11传感器插入到这个上面就可以通过ESP32来读取温湿度值了。连接示意图如图30.2.3.1所示: 图30.2.3.1 DHT11连接示意图 这里要注意,将DHT11贴有字的一面朝内,而有很多孔的一面(网面)朝外,然后插入如图所示的四个孔内就可以了。 30.3 程序设计 30.3.1 程序流程图 程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图: 图30.3.1.1 DHT11实验程序流程图 30.3.2 DHT11函数解析 这一章节除了涉及到GPIO的API函数,便没有再涉及到其他API函数。因此,有关GPIO的API函数介绍,请读者回顾此前的第十章的内容。接下来,笔者将直接介绍DHT11的驱动代码。 30.3.3 DHT11驱动解析 在IDF版20_dht11例程中,作者在20_dht11\components\BSP路径下新增了一个DHT11文件夹,分别用于存放dht11.c、dht11.h这两个文件。其中,dht11.h文件负责声明DHT11相关的函数和变量,而dht11.c文件则实现了DHT11的驱动代码。下面,我们将详细解析这两个文件的实现内容。 1,dht11.h文件 /* 引脚定义 */ #define DHT11_DQ_GPIO_PIN GPIO_NUM_0 /* DHT11引脚高低电平枚举 */ typedef enum { DHT11_PIN_RESET = 0u, DHT11_PIN_SET }DHT11_GPIO_PinState; /* IO操作 */ #define DHT11_DQ_IN gpio_get_level(DHT11_DQ_GPIO_PIN) /* 数据端口输入 */ /* DHT11端口定义 */ #define DHT11_DQ_OUT(x) do{ x ? \ gpio_set_level(DHT11_DQ_GPIO_PIN, DHT11_PIN_SET) : \ gpio_set_level(DHT11_DQ_GPIO_PIN, DHT11_PIN_RESET); \ }while(0) 对DHT11的相关引脚以及IO操作进行宏定义,方便程序中调用。 2,dht11.c文件 /** * @param 无 * @retval 0, 正常 * 1, 不存在/不正常 */ uint8_t dht11_init(void) { gpio_config_t gpio_init_struct; gpio_init_struct.intr_type = GPIO_INTR_DISABLE; /* 失能引脚中断 */ /* 开漏模式的输入和输出 */ gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT_OD; gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; /* 使能上拉 */ gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 失能下拉 */ /* 设置的引脚的位掩码 */ gpio_init_struct.pin_bit_mask = 1ull << DHT11_DQ_GPIO_PIN; gpio_config(&gpio_init_struct); /* 配置DHT11引脚 */ dht11_reset(); return dht11_check(); } 在DHT11的初始化函数中,主要对用到的GPIO口进行初始化,同时在函数最后调用复位函数和自检函数,这两个函数在后面会解释到。 下面介绍的是复位DHT11函数和等待DHT11的回应函数,它们的定义如下: /** * @brief 复位DHT11 * @param data: 要写入的数据 * @retval 无 */ static void dht11_reset(void) { DHT11_DQ_OUT(0); /* 拉低DQ */ vTaskDelay(25); /* 拉低至少18ms */ DHT11_DQ_OUT(1); /* DQ=1 */ esp_rom_delay_us(30); /* 主机拉高10~35us */ } /** * @brief 等待DHT11的回应 * @param 无 * @retval 0, DHT11正常 * 1, DHT11异常/不存在 */ uint8_t dht11_check(void) { uint8_t retry = 0; uint8_t rval = 0; while (DHT11_DQ_IN && retry < 100) /* DHT11会拉低约83us */ { retry++; esp_rom_delay_us (1); } if (retry >= 100) { rval = 1; } else { retry = 0; while (!DHT11_DQ_IN && retry < 100) /* DHT11拉低后会再次拉高87us */ { retry++; esp_rom_delay_us (1); } if (retry >= 100) rval = 1; } return rval; } 以上两个函数分别代表着前面所说的复位脉冲与应答信号,大家可以对比前面的时序图进行理解。那么在上一章DS18B20的实验中,也对复位脉冲以及应答信号进行了详细的解释,大家也可以对比理解。 DHT11与DS18B20有所不同,DHT11是不需要写函数,只需要读函数即可,下面我们看一下读函数: /** * @brief 从DHT11读取一个位 * @param 无 * @retval 读取到的位值: 0 / 1 */ uint8_t dht11_read_bit(void) { uint8_t retry = 0; while (DHT11_DQ_IN && retry < 100) /* 等待变为低电平 */ { retry++; esp_rom_delay_us (1); } retry = 0; while (!DHT11_DQ_IN && retry < 100) /* 等待变高电平 */ { retry++; esp_rom_delay_us (1); } esp_rom_delay_us (40); /* 等待40us */ if (DHT11_DQ_IN) /* 根据引脚状态返回 bit */ { return 1; } else { return 0; } } /** * @brief 从DHT11读取一个字节 * @param 无 * @retval 读到的数据 */ static uint8_t dht11_read_byte(void) { uint8_t i, data = 0; for (i = 0; i < 8; i++) /* 循环读取8位数据 */ { data <<= 1; /* 高位数据先输出, 先左移一位 */ data |= dht11_read_bit(); /* 读取1bit数据 */ } return data; } 在这里dht11_read_bit函数从DHT11处读取1位数据,大家可以对照前面的读时序图进行分析,读数字0和1的不同,在于高电平的持续时间,所以这个作为判断的依据。dht11_read_byte函数就是调用一字节读取函数进行实现。 下面介绍读取温湿度函数,其定义如下: /** * @brief 从DHT11读取一次数据 * @param temp: 温度值(范围:-20~60°) * @param humi: 湿度值(范围:5%~95%) * @retval 0, 正常. * 1, 失败 */ uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi) { uint8_t buf[5]; uint8_t i; dht11_reset(); if (dht11_check() == 0) { for (i = 0; i < 5; i++) /* 读取40位数据 */ { buf = dht11_read_byte(); } if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) { *humi = buf[0]; *temp = buf[2]; } } else { return 1; } return 0; } 读取温湿度函数也是根据时序图进行实现的,在发送复位信号以及应答信号产生后,即可以读取5Byte数据进行处理,校验成功即读取数据有效成功。 30.3.4 CMakeLists.txt文件 打开本实验BSP下的CMakeLists.txt文件,其内容如下所示: set(src_dirs DHT11 IIC LCD LED SPI XL9555) set(include_dirs DHT11 IIC LCD LED SPI XL9555) set(requires driver) idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires}) component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format) 上述的红色DHT11驱动需要由开发者自行添加,以确保DHT11驱动能够顺利集成到构建系统中。这一步骤是必不可少的,它确保了DHT11驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。 30.3.5 实验应用代码 打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。 i2c_obj_t i2c0_master; /** * @brief 程序入口 * @param 无 * @retval 无 */ void app_main(void){ uint8_t err; uint8_t t = 0; uint8_t temperature; uint8_t humidity; esp_err_t ret; /* 初始化NVS */ ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } /* 初始化LED */ led_init(); /* 初始化IIC0 */ i2c0_master = iic_init(I2C_NUM_0); /* 初始化SPI2 */ spi2_init(); /* 初始化XL9555 */ xl9555_init(i2c0_master); /* 初始化LCD */ lcd_init(); lcd_show_string(30, 50, 200, 16, 16, "ESP32", RED); lcd_show_string(30, 70, 200, 16, 16, "DHT11 TEST", RED); lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED); /* 初始化DHT11数字温湿度传感器 */ err = dht11_init(); if (err != 0) { while (1) { lcd_show_string(30, 110, 200, 16, 16, "DHT11 Error", RED); vTaskDelay(200); lcd_fill(30, 110, 239, 130 + 16, WHITE); vTaskDelay(200); } } lcd_show_string(30, 110, 200, 16, 16, "DHT11 OK", RED); lcd_show_string(30, 130, 200, 16, 16, "Temp: C", BLUE); lcd_show_string(30, 150, 200, 16, 16, "Humi: %", BLUE); while (1) { /* 每100ms读取一次 */ if (t % 10 == 0) { /* 读取温湿度值 */ dht11_read_data(&temperature, &humidity); /* 显示温度 */ lcd_show_num(30 + 40, 130, temperature, 2, 16, BLUE); /* 显示湿度 */ lcd_show_num(30 + 40, 150, humidity, 2, 16, BLUE); } vTaskDelay(10); t++; if (t == 20) { t = 0; LED_TOGGLE();/* LED闪烁 */ } } } 主函数代码比较简单,一系列硬件初始化后,如果DHT11初始化成功,那么在循环中调用dht11_get_temperature函数获取温湿度值,每隔100ms读取数据并显示在LCD上。 30.4 下载验证 假定DHT11传感器已经接上去正确的位置,将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示当前的温度值的内容如图30.4.1所示: 图30.4.1 程序运行效果图 至此,本章实验结束。大家可以将本章通过DHT11读取到的温度值,和前一章的通过DS18B20读取到的温度值对比一下,看看哪个更准确?
|