ESP32 使用ESP-IDF驱动DHT11温湿度计源码分享
- 一、源码分享
-
- 1、效果展示
- 2、开发环境搭建
- 3、源码分享
-
- 3.1、dht11.h
- [3.2、 dht11.c](#3.2、 dht11.c)
- [3.3、 main.c](#3.3、 main.c)
- 二、dht11温湿度计介绍
-
- [1、 概述](#1、 概述)
- 2、主要特性
- 3、引脚定义
- 4、工作原理与通信协议
- 5、数据格式
- 6、使用注意事项
- 三、ESP-IDF详解
-
- [1、ESP32 概述](#1、ESP32 概述)
- [2、 ESP-IDF 详解](#2、 ESP-IDF 详解)
-
- [2.1 、ESP-IDF 的核心组件与架构](#2.1 、ESP-IDF 的核心组件与架构)
- [2.2 、ESP-IDF 开发环境与工具链](#2.2 、ESP-IDF 开发环境与工具链)
- [2.3 、ESP-IDF 开发流程简述](#2.3 、ESP-IDF 开发流程简述)
- [2.4、 示例代码结构 (最简单的 Hello World)](#2.4、 示例代码结构 (最简单的 Hello World))
- [2.5、 优势与特点](#2.5、 优势与特点)
- [2.6 、适用场景](#2.6 、适用场景)
- 3、总结

一、源码分享
1、效果展示

2、开发环境搭建
参考我这篇博文:VS Code 在线安装ESP-IDF,ESP32开发环境搭建详细教程
3、源码分享
3.1、dht11.h
c
#ifndef __DHT11_H
#define __DHT11_H
#include "driver/gpio.h"
#include "esp_log.h"
/* 引脚定义 */
#define DHT11_DQ_GPIO_PIN GPIO_NUM_0
/* 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, 1): \
gpio_set_level(DHT11_DQ_GPIO_PIN, 0); \
} while(0)
/* 函数声明 */
void dht11_reset(void); /* 复位DHT11 */
uint8_t dht11_init(void); /* 初始化DHT11 */
uint8_t dht11_check(void); /* 等待DHT11的回应 */
uint8_t dht11_read_data(short *temp, short *humi); /* 读取温湿度 */
#endif
3.2、 dht11.c
c
#include "dht11.h"
/**
* @brief 复位DHT11
* @param 无
* @retval 无
*/
void dht11_reset(void)
{
DHT11_DQ_OUT(0); /* 拉低DQ */
esp_rom_delay_us(25000); /* 拉低至少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会拉低40~80us */
{
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;
}
/**
* @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 data:读到的数据
*/
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;
}
/**
* @brief 从DHT11读取一次数据(温度和湿度的整数部分)
* @param temp: 温度值(范围:-20~60°)(放大10倍)
* @param humi: 湿度值(范围:5%~95%)(放大10倍)
* @retval 0, 正常; 1, 失败
*/
uint8_t dht11_read_data(short *temp, short *humi)
{
uint8_t buf[5];
uint8_t i;
short raw_temp = 0;
short raw_humi = 0;
dht11_reset();
if (dht11_check() == 0)
{
for (i = 0; i < 5; i++) /* 读取40位数据 buf[0]湿度高8位 buf[1]湿度低8位 buf[2]温度高8位 buf[3]温度低8位 */
{
buf[i] = dht11_read_byte();
}
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) /* 数据校验正确 */
{
raw_humi = buf[0] * 10 + buf[1]; /* 获取湿度数据 */
if (buf[3] & 0x80) /* 温度为负值 */
{
raw_temp = buf[2] * 10 + (buf[3] & 0x7F);
raw_temp = -raw_temp;
}
else
{
raw_temp = buf[2] * 10 + buf[3]; /* 温度数据 */
}
*humi = raw_humi;
*temp = raw_temp;
}
}
else
{
return 1;
}
return 0;
}
/**
* @brief 初始化DHT11
* @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();
}
3.3、 main.c
cpp
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "led.h"
#include "dht11.h"
#include <stdio.h>
void app_main(void)
{
esp_err_t ret;
uint8_t t = 0;
short temperature = 0;
short humidity = 0;
ret = nvs_flash_init(); /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
led_init(); /* LED初始化 */
while (dht11_init()) /* 初始化DHT11数字温湿度传感器 */
{
printf("DHT11 Error\r\n");
vTaskDelay(pdMS_TO_TICKS(200));
}
while (1)
{
if (t % 10 == 0) /* 每100ms读取一次 */
{
dht11_read_data(&temperature, &humidity); /* 读取温湿度值 */
printf("temp = %d,humidity = %d\r\n",temperature /10,humidity/10);
}
vTaskDelay(pdMS_TO_TICKS(10));
t++;
if (t == 20)
{
t = 0;
LED0_TOGGLE();
}
}
}
二、dht11温湿度计介绍
1、 概述
DHT11 是一款低成本、低功耗的数字式温湿度复合传感器。它通过单总线协议与微控制器(如 Arduino、Raspberry Pi、STM32 等)进行通信,输出经过校准的数字信号。其核心部分是一个电阻式湿度感测元件和一个热敏电阻(NTC)温度感测元件,内部集成了一个 8 位微控制器进行信号处理。
2、主要特性
- 测量范围:
- 湿度: 5 % ∼ 90 % R H 5\% \sim 90\% RH 5%∼90%RH(相对湿度)
- 温度: − 20 ∘ C ∼ 95 ∘ C -20^{\circ}C \sim 95^{\circ}C −20∘C∼95∘C
- 精度:
- 湿度: ± 5 % R H \pm 5\% RH ±5%RH(典型值)
- 温度: ± 2 ∘ C \pm 2^{\circ}C ±2∘C(典型值)
- 注意:精度相对较低,适用于一般性监测,不适用于高精度应用。
- 分辨率:
- 湿度: 1 % R H 1\% RH 1%RH
- 温度: 1 ∘ C 1^{\circ}C 1∘C
- 响应时间: 约 1 ∼ 2 1 \sim 2 1∼2 秒
- 工作电压: 3.3 V ∼ 5.5 V 3.3V \sim 5.5V 3.3V∼5.5V DC
- 接口: 单总线数字信号输出
- 尺寸: 小型化封装
- 功耗: 测量时约 2.5 m A 2.5mA 2.5mA,待机时很低
3、引脚定义
DHT11 模块通常有 3 个或 4 个引脚:
- VCC (或 +): 电源正极( 3.3 V ∼ 5 V 3.3V \sim 5V 3.3V∼5V)
- GND (或 -): 电源负极/地
- DATA (或 OUT): 数据输入/输出引脚(单总线)
- NC (可选): 空引脚(不连接)
4、工作原理与通信协议
DHT11 使用单总线协议进行通信,这意味着数据线同时用于发送和接收数据。通信过程由微控制器(主机)发起和协调:
- 主机发起通信:
- 主机将 DATA 线拉低至少 18 m s 18ms 18ms(起始信号)。
- 然后主机释放总线(将 DATA 线置高),等待 DHT11 响应。
- DHT11 响应:
- DHT11 检测到起始信号后,会先将 DATA 线拉低约 80 μ s 80\mu s 80μs(应答信号)。
- 接着 DHT11 再将 DATA 线拉高约 80 μ s 80\mu s 80μs,表示准备发送数据。
- 数据传输:
- DHT11 开始发送 40 位数据。数据由 5 个字节组成:
- 湿度整数部分(8 位)
- 湿度小数部分(8 位)(DHT11 此部分恒为 0)
- 温度整数部分(8 位)
- 温度小数部分(8 位)(DHT11 此部分恒为 0)
- 校验和(8 位,等于前四个字节之和的低 8 位)
- 每一位数据由一个低电平起始位(约 50 μ s 50\mu s 50μs)开始。
- 紧接着是一个高电平脉冲:
- 高电平持续约 26 μ s ∼ 28 μ s 26\mu s \sim 28\mu s 26μs∼28μs 表示逻辑 '0'
- 高电平持续约 70 μ s 70\mu s 70μs 表示逻辑 '1'
- 传输完 40 位后,DHT11 拉低 DATA 线约 50 μ s 50\mu s 50μs 后释放总线,进入空闲状态。
- DHT11 开始发送 40 位数据。数据由 5 个字节组成:

5、数据格式
如前所述,传输的 40 位数据(5 字节)含义如下:
Byte 0: 湿度整数 (Humidity High Byte)Byte 1: 湿度小数 (Humidity Low Byte) (DHT11 固定为 0)Byte 2: 温度整数 (Temperature High Byte)Byte 3: 温度小数 (Temperature Low Byte) (DHT11 固定为 0)Byte 4: 校验和 (Checksum) =(Byte0 + Byte1 + Byte2 + Byte3) & 0xFF
计算示例:
假设接收到的 5 个字节是:45, 00, 28, 00, 6D
- 湿度 =
45= 69 % R H 69\% RH 69%RH - 温度 =
28= 40 ∘ C 40^{\circ}C 40∘C - 校验和计算:
45 + 00 + 28 + 00 = 109( 6 D h e x 6D_{hex} 6Dhex) 匹配,数据有效。
6、使用注意事项
- 电压匹配: 确保供电电压在 3.3 V ∼ 5 V 3.3V \sim 5V 3.3V∼5V 范围内。如果主机是 3.3 V 3.3V 3.3V 逻辑电平(如 Raspberry Pi),建议给 DHT11 也供 3.3 V 3.3V 3.3V,或在 DATA 线上加电平转换器,避免损坏主机 GPIO。
- 上拉电阻: DATA 线在空闲状态时由 DHT11 内部上拉到 VCC。对于长导线或强干扰环境,可在 DATA 线与 VCC 之间外接一个 4.7 K Ω ∼ 10 K Ω 4.7K\Omega \sim 10K\Omega 4.7KΩ∼10KΩ 的上拉电阻。
- 读取间隔: DHT11 功耗低,但其感测元件需要时间稳定。两次读取操作之间应间隔至少 1 1 1 秒。
- 时序要求: 通信协议对时序要求严格。在资源受限的单片机(如 Arduino)上,读取时可能需要暂时关闭中断以保证时序准确。
- 校验和: 务必检查校验和,确保数据传输无误。
- 环境: 避免将传感器置于结露、腐蚀性气体或过高温度的环境中。避免阳光直射。
三、ESP-IDF详解
1、ESP32 概述
ESP32 是由乐鑫科技(Espressif Systems)推出的一款高性能、低功耗、高集成度的 Wi-Fi & 蓝牙双模系统级芯片(SoC)。它广泛应用于物联网(IoT)、智能家居、工业控制等领域。其关键特性包括:
- 双核处理器(通常为 Xtensa LX6,某些型号为 RISC-V)
- 集成 Wi-Fi (802.11 b/g/n) 和蓝牙 (包括经典蓝牙和低功耗蓝牙 BLE)
- 丰富的外设接口: S P I SPI SPI, I 2 C I2C I2C, I 2 S I2S I2S, U A R T UART UART, A D C ADC ADC, D A C DAC DAC, P W M PWM PWM, 触摸传感器等
- 充足的内存(RAM 和 Flash 选项多样)
- 强大的安全特性(如加密加速器)
- 超低功耗设计,支持多种休眠模式
2、 ESP-IDF 详解
ESP-IDF (Espressif IoT Development Framework) 是乐鑫官方为 ESP32、ESP32-S 系列、ESP32-C 系列等芯片提供的开发框架和 SDK(软件开发工具包)。它是开发基于 ESP32 芯片应用程序的首选和官方推荐工具。
2.1 、ESP-IDF 的核心组件与架构
ESP-IDF 是一个分层、模块化的框架:
- 硬件抽象层 (HAL): 提供对芯片硬件资源(如 GPIO、UART、SPI、I2C、定时器、ADC、DAC、RTC、Wi-Fi、蓝牙等)进行操作的统一接口,屏蔽底层硬件细节。
- 驱动 (Drivers): 在 HAL 之上,提供更易用、功能更丰富的设备驱动(如 SPI Flash 驱动、SD 卡驱动、以太网驱动等)。
- FreeRTOS: ESP-IDF 深度集成了开源的 FreeRTOS 实时操作系统内核。它提供了任务管理、队列、信号量、软件定时器、中断管理等机制,充分利用 ESP32 的多核特性(任务可以在不同核心上运行)。
- 中间件:
- Wi-Fi 协议栈: 实现 Wi-Fi 的 Station(客户端)、SoftAP(接入点)、Promiscuous(监听)模式。
- 蓝牙协议栈: 实现经典蓝牙和低功耗蓝牙 (BLE) 的各种角色(如 GATT Client/Server, GAP Central/Peripheral)。
- TCP/IP 协议栈 (lwIP): 轻量级的 TCP/IP 协议栈,支持 IPv4/IPv6 (部分型号)、DHCP、DNS、Socket API 等。
- 文件系统 (FATFS/VFS): 支持在 SPI Flash 或外部存储设备上使用 FAT 文件系统。
- 加密库: 提供 AES、SHA、RSA 等加密算法的软件和硬件加速实现。
- HTTP/WebSocket/MQTT 等协议库: 方便构建网络应用。
- 应用程序: 用户编写的业务逻辑代码,运行在 FreeRTOS 的任务中,通过调用下层 API 实现功能。
2.2 、ESP-IDF 开发环境与工具链
- 工具链: 基于 GNU GCC 编译器(针对 Xtensa 或 RISC-V 架构)。
- 构建系统: 使用 CMake (早期版本使用 GNU Make)。开发者编写
CMakeLists.txt文件来定义项目结构、源文件、依赖组件等。 - 配置工具:
menuconfig。这是一个基于文本的图形化配置工具(类似于 Linux Kernel 的make menuconfig),用于配置 ESP-IDF 的众多选项,如:- 选择目标芯片型号
- 配置 Wi-Fi/BT 参数
- 调整 FreeRTOS 设置(任务栈大小、优先级等)
- 配置日志输出级别和方式
- 配置内存布局
- 启用/禁用特定功能和外设驱动
- 核心工具 -
idf.py: 这是 ESP-IDF 提供的命令行工具,用于执行构建、烧录、调试、监视串口输出等几乎所有开发任务。常用命令如:idf.py set-target esp32(设置目标芯片)idf.py menuconfig(启动配置工具)idf.py build(编译项目)idf.py -p PORT flash(烧录固件到设备,PORT 为串口,如 COM3 或 /dev/ttyUSB0)idf.py -p PORT monitor(启动串口监视器,查看设备日志输出)idf.py fullclean(彻底清理构建目录)
2.3 、ESP-IDF 开发流程简述
- 环境搭建: 在 Windows、Linux 或 macOS 上安装 ESP-IDF 开发环境(包括工具链、Python、Git 等)。
- 创建项目: 使用
idf.py create-project或复制示例项目模板。 - 编写代码: 在
main目录下编写应用程序代码 (通常是main.c或app_main()函数)。 - 配置项目: 运行
idf.py menuconfig根据需求进行配置。 - 编译项目: 运行
idf.py build生成可执行固件 (.bin 文件)。 - 烧录固件: 将 ESP32 开发板连接到电脑,运行
idf.py -p PORT flash将固件烧录到设备 Flash 中。 - 监视输出: 运行
idf.py -p PORT monitor查看设备运行日志,进行调试。可以使用ESP_LOGx(如ESP_LOGI,ESP_LOGE) 函数打印不同级别的日志。
2.4、 示例代码结构 (最简单的 Hello World)
c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
static const char *TAG = "MAIN";
void app_main(void)
{
while (1) {
ESP_LOGI(TAG, "Hello World!"); // 打印 Info 级别日志
vTaskDelay(pdMS_TO_TICKS(1000)); // 延时 1000 毫秒 (FreeRTOS 延时)
}
}
2.5、 优势与特点
- 官方支持: 由芯片原厂维护,更新及时,与新芯片特性同步快。
- 功能全面: 提供对 ESP32 所有硬件特性和外设的底层访问。
- 性能优化: 针对 ESP32 硬件进行了深度优化(如 Wi-Fi/BT 共存)。
- 稳定性高: 经过大量商业产品验证。
- 社区活跃: 用户多,社区支持好,问题容易找到解决方案。
- 文档完善: 官方提供详尽的 API 参考指南、编程指南和示例代码。
- 模块化: 易于扩展和复用代码,方便管理大型项目。
- 开源免费: 基于 Apache 2.0 许可证。
2.6 、适用场景
ESP-IDF 适用于需要深度控制硬件、追求高性能、高稳定性或需要使用 ESP32 特有功能(如超低功耗协处理器 ULP)的应用开发。对于快速原型开发,也可以考虑基于 ESP-IDF 构建的更高级框架(如 Arduino for ESP32、MicroPython),但这些框架最终都依赖于 ESP-IDF 的底层功能。
3、总结
ESP-IDF 是开发 ESP32 系列芯片的强大、灵活且功能完备的官方框架。它提供了从硬件操作到网络协议栈的全套解决方案,结合 FreeRTOS 实现了高效的多任务处理。虽然其学习曲线相对陡峭,尤其是对于不熟悉嵌入式开发和 RTOS 的开发者,但它提供了最直接、最强大的方式来充分利用 ESP32 的潜力,是开发复杂或高性能 ESP32 应用的基石。掌握 idf.py 工具链和 menuconfig 配置是使用 ESP-IDF 的关键。
