ESP32-DHT11温湿度传感器

DHT11 模块介绍

DHT11 模块:一款结合了温湿度检测以及信号处理 IC 的传感器模块,通信方式采用单总线串行通信

  • 信号类型:数字信号
  • 精度:温度精度 ±2°C,湿度 ±5%
  • 取样周期:2S
  • 工作电压:3-5.5V
  • 工作电流:2.5mA(测量时),150μA(待机)
  • 温度范围:0-50℃
  • 湿度范围:20-90%
  • 电路复杂度:直接连接,无需额外电路
  • 代码复杂度:直接调用库函数读取温度

接线

DHT11 模块已集成上拉电阻,无需额外添加(如果 DHT11 模块无内置上拉电阻,需在 DATA 引脚和 5V 之间外接一个 4.7kΩ 电阻)

image.png

程序

MicroPython 内置的 DHT11 程序库(dht),通过 dht 程序库获取温湿度数据,需要如下四个步骤

  • 导入 dht 库
  • 创建 DHT11 对象
  • 执行 measure 方法
    • 注意读取频率:DHT11 的采样间隔需 ≥1 秒,建议设为 2 秒,否则可能读取失败
  • 通过两个方法分别获取温度和湿度
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from machine import Pin, Timer
import dht
from time import sleep_ms

d = dht.DHT11(Pin(4)) # 创建一个 DHT11 实例,连接到 GPIO 4

while True:
    d.measure()  # 触发测量
    temp = d.temperature()  # 获取温度值
    hum = d.humidity()  # 获取湿度值
    print("temperature:"+str(temp)+"℃")
    print("humidity:"+str(hum)+"%")
    sleep_ms(2000)  # 每 2 秒测量一次

返回结果

1
2
3
4
5
temperature:22
humidity:66%
temperature:22
humidity:68%
......

DHT11 的局限性

  • 精度较低:温度误差 ±2°C,湿度 ±5%,适用于一般场景(如室内环境监测),不适用于高精度实验。
  • 响应速度慢:每次读取需等待 1~2 秒,不适合高速采样场景。
  • 单总线协议:同一引脚只能连接一个 DHT11,无法并联多个传感器。

通讯方式

DHT11 通过单总线(1-Wire)协议与外部设备(如 Arduino、ESP32)通信:

  1. 主机(Arduino)发送启动信号:拉低总线至少 18ms,然后释放。
  2. DHT11 响应:拉低总线 80μs,再拉高 80μs,表示准备发送数据。
  3. 数据传输:DHT11 发送 40 位数据(16 位湿度 + 16 位温度 + 8 位校验和),每位通过高低电平的持续时间表示 0 或 1。

image.png

1
2
3
湿度整数部分 | 湿度小数部分 | 温度整数部分 | 温度小数部分 | 校验和
0001 1001    | 0000 0000    | 0001 1000    | 0000 0000    | 0011 0001
(25% RH)    | (0% RH)     | (24°C)      | (0°C)       | 校验和=49

拓展

为什么 DHT11 模块直接返回数字信号

DHT11 内部集成了一个专用集成电路(ASIC),负责以下任务:

  • 模拟信号采集:
    • 读取热敏电阻的电阻值(温度)
    • 读取电容式元件的电容值(湿度)
  • 模拟信号调理:
    • 对原始信号进行放大、滤波,消除噪声
  • 模数转换(ADC)
    • 将调理后的模拟信号转换为数字信号(例如,温度值用 8 位二进制表示,湿度值用 8 位二进制表示)
  • 校准与补偿
    • 根据预存的校准参数(如非线性补偿、温度漂移补偿)修正数字信号
  • 数字编码:
    • 将温度和湿度数据打包成固定格式的二进制数据

能否查看或自定义修改 ASIC 的底层代码

不能,原因如下:

  • 硬件固化设计:ASIC 的逻辑功能是通过物理电路实现的(如晶体管布局),而非运行在通用处理器上的软件代码。它的“逻辑”被固化在硅片中,无法通过软件更新或调试接口修改。
  • 封闭式协议与知识产权:DHT11 的通信协议和数据格式是厂商定义的,其内部校准参数、ADC 算法等属于商业机密,未开放给用户访问。
  • 缺乏调试接口:DHT11 没有提供 JTAG、SWD 等硬件调试接口,无法通过常规手段读取内部逻辑。

通过上位机读取 DHT11 模块数据

需要考虑的问题:

  1. 通信方式选择:ESP32 和笔记本电脑之间可以通过哪些方式通信?常见的有串口、Wi-Fi、蓝牙等。用户需要选择一种适合他们应用场景的方式。
  2. 数据传输协议:确定使用哪种协议来发送数据。比如,通过串口发送简单的文本格式,或者使用 HTTP、MQTT 等更复杂的协议。
  3. 上位机程序开发:用户需要在笔记本电脑上编写程序来接收和显示数据。这可能涉及到 Python、Java、C#或其他语言,取决于用户的熟悉程度和需求。
  4. 代码实现细节:如何在 ESP32 上编写代码,将传感器数据发送到上位机?需要确保数据格式正确,传输稳定。
  5. 错误处理和稳定性:考虑通信过程中可能出现的错误,如连接中断、数据丢失等,如何进行处理以保证系统的稳定性。
  6. 用户友好性:上位机程序是否易于使用,是否需要图形界面,还是简单的命令行输出即可。

实现步骤大致如下:

  1. 配置 ESP32 连接 Wi-Fi。
  2. 在 ESP32 上创建 TCP 服务器或 HTTP 服务器。
  3. 读取传感器数据并发送到连接的客户端。
  4. 在上位机编写程序连接 ESP32,接收并显示数据

使用 WiFi(Socket)通信

ESP32 若连接了 WiFi,可以在 ESP32 上开启一个 Socket 服务器,让上位机通过 TCP 访问它

1、ESP32 代码(作为服务器);可将代码运行在一个 Thonny 实例上

 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
import network
import socket
from machine import Pin
import dht
import time

SSID = "coffeelize" #你的WiFi名称
PASSWORD = "xxxxxxxx" #你的WiFi密码

# 连接WiFi
wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(SSID, PASSWORD)

while not wifi.isconnected():
    pass  # 等待WiFi连接

# 打印IP地址
print("Connected, IP:", wifi.ifconfig()[0])

# 初始化DHT11
d = dht.DHT11(Pin(4))

# 创建TCP服务器
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 8080)) # 监听端口8080
s.listen(1)
print("等待客户端连接...")

conn, addr = s.accept()
print("客户端连接:", addr)

while True:
    d.measure()
    temp = d.temperature()
    hum = d.humidity()
    data = f"{temp},{hum}\n"
    conn.send(data.encode())  # 发送数据
    time.sleep(2)
1
2
Connected, IP: 192.168.31.35
等待客户端连接...

2、在 Windows 上运行另一个 Thonny 实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import socket

ESP32_IP = "192.168.31.35"  # ESP32 IP
PORT = 8080

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ESP32_IP, PORT))

while True:
    data = s.recv(1024).decode().strip()
    if data:
        temp, hum = data.split(',')
        print(f"温度: {temp}℃, 湿度: {hum}%")

image.png

image.png

3、两边都运行起来后:

image.png

不同方式的比较

方法 适用场景 传输方式 优点 缺点
串口通信(UART) 直接连接笔记本 USB 串口 简单,适合调试 需 USB 线,距离受限
WiFi Socket 同一局域网内 TCP/IP 可无线读取,速度快 需 WiFi 网络
MQTT 远程监控 MQTT 协议 适合云端,支持多个设备 需 MQTT 服务器

拓展 2

如何理解 Wifi Socket

可以把 Socket 理解成两台设备(比如 ESP32 和笔记本)之间的“电话线”。这条“电话线”允许两台设备 通过 WiFi 进行数据传输,就像两个人在打电话一样,一个人说,另一个人听。

  • 服务器(ESP32):负责监听和等待连接,它是数据的“提供者”
  • 客户端(笔记本):主动连接服务器,它是数据的“使用者”
  • 服务器 不会主动给客户端发消息,它只是等着客户端来“点单”,然后才会提供数据
需求 服务器(ESP32) 客户端(笔记本)
网络要求 连接同一 WiFi,获取 IP 连接同一 WiFi,知道 ESP32 的 IP
通信端口 监听特定端口(如 8080) 连接到 ESP32 的 IP 和端口
数据格式 按一定格式发送数据 按格式解析数据
可能问题 WiFi 连接失败、IP 变动、端口被占用 连接不上 ESP32、数据丢失
解决方案 确保 WiFi 稳定、检查 IP、换端口 确保同一 WiFi、确认 IP、用 ping 测试

拓展 3

图表动态展示数据

客户端通过图像形式动态展示数据,这里切换成 Pycharm 作为 IDE 了,因为涉及到 matplotlib 模块

 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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import socket  
import matplotlib.pyplot as plt  
import matplotlib.animation as animation  
from collections import deque  
  
# ESP32的IP和端口  
ESP32_IP = "192.168.31.35"  # 替换为 ESP32 IP 地址  
PORT = 8080  # 端口号  
  
# 创建Socket连接  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
s.connect((ESP32_IP, PORT))  # 连接ESP32  
  
# 维护最近 30 个数据点(最多 1 分钟)  
MAX_POINTS = 30  
times = deque(maxlen=MAX_POINTS)  # 时间数据  
temps = deque(maxlen=MAX_POINTS)  # 温度数据  
hums = deque(maxlen=MAX_POINTS)   # 湿度数据  
  
# 创建图形窗口  
fig, ax = plt.subplots(2, 1, figsize=(10, 8))  
  
# 温度曲线  
ax[0].set_title('Temperature (°C)')  
ax[0].set_xlabel('Time (s)')  
ax[0].set_ylabel('Temperature (°C)')  
ax[0].grid(True)  # 添加网格  
line_temp, = ax[0].plot([], [], label='Temperature', color='r')  
  
# 湿度曲线  
ax[1].set_title('Humidity (%)')  
ax[1].set_xlabel('Time (s)')  
ax[1].set_ylabel('Humidity (%)')  
ax[1].grid(True)  # 添加网格  
line_hum, = ax[1].plot([], [], label='Humidity', color='b')  
  
# 设定初始 X 轴范围  
ax[0].set_xlim(0, 60)  
ax[1].set_xlim(0, 60)  
  
# **更新数据并绘制**  
def update_data(frame):  
    try:  
        # 从ESP32接收数据  
        data = s.recv(1024).decode().strip()  
        if data:  
            # 解析温度和湿度  
            temp, hum = data.split(',')  
            temp = float(temp)  
            hum = float(hum)  
  
            # 计算当前时间戳(假设每次更新间隔 2s)  
            if times:  
                new_time = times[-1] + 2  # 继续增加 2s 时间  
            else:  
                new_time = 0  # 第一帧从 0 开始  
  
            # 更新数据  
            times.append(new_time)  
            temps.append(temp)  
            hums.append(hum)  
  
            # 更新曲线数据  
            line_temp.set_data(times, temps)  
            line_hum.set_data(times, hums)  
  
            # **更新 X 轴范围(防止 `min == max` 报错)**  
            if len(times) > 1:  
                ax[0].set_xlim(min(times), max(times))  
                ax[1].set_xlim(min(times), max(times))  
            else:  
                ax[0].set_xlim(new_time, new_time + 1)  
                ax[1].set_xlim(new_time, new_time + 1)  
  
            # **动态更新 Y 轴范围**  
            if temps:  
                ymin_t, ymax_t = min(temps), max(temps)  
                ax[0].set_ylim(ymin_t - 2, ymax_t + 2)  # 温度 Y 轴增加 2°C 缓冲区  
  
            if hums:  
                ymin_h, ymax_h = min(hums), max(hums)  
                ax[1].set_ylim(ymin_h - 5, ymax_h + 5)  # 湿度 Y 轴增加 5% 缓冲区  
  
        return line_temp, line_hum  
  
    except Exception as e:  
        print(f"Error: {e}")  
        return line_temp, line_hum  
  
# 动画更新,避免缓存问题  
ani = animation.FuncAnimation(fig, update_data, interval=2000, cache_frame_data=False)  
  
# 显示图表  
plt.tight_layout()  
plt.show()

image.png

  • 自动调整 Y 轴范围
    • 温度 Y 轴范围:min(temps) - 2max(temps) + 2
    • 湿度 Y 轴范围:min(hums) - 5max(hums) + 5
  • 添加网格线 grid(True),让纵坐标数据更清晰。
  • X 轴范围固定为最近 60 秒
    • times 只存储最近 30 个数据点(每 2s 采样一次,60s = 30 点)

进一步优化程序

  • 提高性能,减少不必要的 set_xlim/set_ylim 计算
    • set_xlim()set_ylim() 只有在数据更新时才调用,而不是每帧都执行。
  • 让温湿度数据绘制更平滑
    • 可以使用 Line2D.set_xdata()Line2D.set_ydata() 来更新数据,而不是 set_data(),减少不必要的计算
  • 让 X 轴时间显示更加友好
    • datetime.now() 记录真实时间,而不是 times[-1] + 2 这样模拟的时间,避免时间误差累积
  • 增加异常处理,避免程序崩溃
    • 如果 ESP32 断开连接,程序不会直接崩溃,而是尝试重连。
    • recv() 读取数据时,添加 try-except 捕获 socket.error 以防止网络问题导致程序崩溃。

image.png

Licensed under CC BY-NC-SA 4.0