4、温湿度数据采集实战:使用ESP32读取DHT11/SHT30温湿度数据

温湿度数据是环境感知项目中最基础、最常用的物理量。无论是智能家居的舒适度调节、农业大棚的种植监控,还是实验室的恒温恒湿管理,都离不开对温度和湿度的精确采集。本章将聚焦于ESP32微控制器,深入讲解如何通过GPIO(通用输入输出)接口和I2C(集成电路总线)协议,分别驱动DHT11单总线数字传感器与SHT30高精度I2C传感器,并实现定时采集与串口打印的完整工程链路。我们将从传感器选型对比、硬件接线原理、代码架构设计到实际调试排障,构建一个可复用的温湿度采集模块,为后续的TinyML边缘推理提供可靠的数据源。

ESP32 温湿度采集硬件链路与数据流架构 ESP32 主控 3.3V / GPIO / I2C 定时器触发采集 DHT11 传感器 单总线协议 (1-Wire) VCC=3.3V, DATA=GPIO4 精度: ±2°C / ±5%RH SHT30 传感器 I2C协议 (0x44) SCL=GPIO22, SDA=GPIO21 精度: ±0.2°C / ±2%RH 串口监视器 115200 baud 打印温湿度数据 TinyML 推理 本地判断环境状态 异常预警/联动控制 GPIO4 数据线 I2C总线 UART TX 数据预处理 传感器接口区域 数据处理与输出区域

上图展示了本章实战项目的整体硬件链路与数据流架构。ESP32作为主控核心,左侧连接DHT11(单总线)与SHT30(I2C)两种传感器,右侧通过UART串口将采集到的数据输出至PC监视器,并为后续TinyML推理预留数据接口。下面我们将从传感器原理、硬件接线、代码实现到调试方法,逐步拆解整个采集流程。

4.1 核心原理:DHT11与SHT30的工作机制

在编写代码之前,理解传感器的物理层协议与数据格式至关重要。DHT11和SHT30虽然都输出数字信号,但它们的通信方式、精度和内部结构截然不同。

4.1.1 DHT11单总线协议

DHT11是一款经典的温湿度复合传感器,内部包含一个电阻式感湿元件和一个NTC(负温度系数)测温元件,并连接一个8位微处理器。它采用简化的单总线(1-Wire)协议,仅需一根数据线即可完成与主控的双向通信。其数据帧格式为40位:8位湿度整数 + 8位湿度小数 + 8位温度整数 + 8位温度小数 + 8位校验和。校验和等于前四个字节之和的低8位,用于检测传输错误。

通信时序要求严格:主机发起起始信号(拉低数据线至少18ms),传感器响应后拉低80us再拉高80us,随后逐位输出数据。每一位的“0”或“1”由高电平持续时间区分——26-28us表示“0”,70us表示“1”。由于时序敏感,建议在代码中使用微秒级延时函数,并关闭中断以避免干扰。

4.1.2 SHT30 I2C协议

SHT30是Sensirion公司推出的高精度数字传感器,内部集成了CMOSens®芯片,将传感器元件、信号处理电路和I2C接口集成于一体。其默认I2C地址为0x44(ADDR引脚接低电平时),支持标准模式(100kHz)和快速模式(400kHz)。SHT30的测量命令分为单次测量和周期测量两种模式,单次测量命令如0x2C 0x06(高重复性,时钟拉伸使能),周期测量则需先发送0x20 0x00启动2Hz的连续采样。

SHT30返回的数据为6字节:2字节温度 + 1字节CRC校验 + 2字节湿度 + 1字节CRC校验。温度计算公式为:T = -45 + 175 * (rawTemp / 65535.0),湿度计算公式为:RH = 100 * (rawHumidity / 65535.0)。相比DHT11,SHT30提供了更宽的测量范围(-40~125°C,0~100%RH)和更高的精度(±0.2°C,±2%RH)。

对比维度DHT11SHT30
通信接口单总线(1-Wire)I2C(地址0x44)
温度精度±2°C±0.2°C
湿度精度±5%RH±2%RH
采样周期≥1秒≥2ms(快速模式)
数据帧格式40位原始数据6字节含CRC校验
典型应用低成本、低精度场景高精度、工业级场景

4.2 硬件接线与工程准备

正确的硬件连接是数据采集成功的前提。ESP32开发板通常提供多个GPIO引脚,但并非所有引脚都适合连接传感器。对于DHT11,我们选择GPIO4作为数据引脚;对于SHT30,使用默认的I2C引脚GPIO21(SDA)和GPIO22(SCL)。

⚠️ 引脚选择注意事项: 避免使用GPIO0、GPIO2、GPIO12等特殊引脚。GPIO0在启动时需保持高电平,GPIO12在启动时决定VDD_SDIO电压,GPIO2在烧录时需悬空。这些引脚在传感器连接时可能引发启动异常或烧录失败。

接线清单如下:

  • DHT11:VCC接3.3V,GND接GND,DATA接GPIO4(需外接10kΩ上拉电阻至3.3V,部分模块已内置)。
  • SHT30:VCC接3.3V,GND接GND,SCL接GPIO22,SDA接GPIO21。若使用多个I2C设备,注意地址冲突。
  • 串口:ESP32的UART0(TX=GPIO1,RX=GPIO3)通过USB转串口芯片连接PC,无需额外接线。

在Arduino IDE中,需要安装ESP32开发板支持包。打开“文件”->“首选项”,在“附加开发板管理器网址”中添加:https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json,然后在“工具”->“开发板”->“开发板管理器”中搜索“esp32”并安装。对于SHT30,还需要安装Adafruit_SHT31库(通过库管理器搜索“Adafruit SHT31”)。

4.3 代码实现:定时采集与串口打印

我们将编写一个完整的Arduino程序,实现以下功能:每2秒采集一次DHT11和SHT30的数据,通过串口以格式化文本输出,并包含错误处理机制。代码采用模块化设计,便于后续扩展。

#include <Wire.h>
#include <Adafruit_SHT31.h>
#include <DHT.h>

// DHT11配置
#define DHT_PIN 4
#define DHT_TYPE DHT11
DHT dht(DHT_PIN, DHT_TYPE);

// SHT30配置
Adafruit_SHT31 sht30 = Adafruit_SHT31();

// 定时器变量
unsigned long previousMillis = 0;
const long interval = 2000; // 2秒采集间隔

void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println(F("ESP32 温湿度采集系统启动"));

  // 初始化DHT11
  dht.begin();
  Serial.println(F("DHT11 初始化完成"));

  // 初始化SHT30
  if (!sht30.begin(0x44)) {
    Serial.println(F("SHT30 初始化失败,请检查接线!"));
    while (1) delay(10);
  }
  Serial.println(F("SHT30 初始化成功"));
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    readAndPrintSensors();
  }
}

void readAndPrintSensors() {
  // 读取DHT11
  float dhtTemp = dht.readTemperature();
  float dhtHum = dht.readHumidity();

  // 读取SHT30
  float shtTemp = sht30.readTemperature();
  float shtHum = sht30.readHumidity();

  // 打印分隔线
  Serial.println(F("-----------------------------------"));

  // 打印DHT11数据
  if (isnan(dhtTemp) || isnan(dhtHum)) {
    Serial.println(F("[DHT11] 读取失败,请检查连接"));
  } else {
    Serial.print(F("[DHT11] 温度: "));
    Serial.print(dhtTemp);
    Serial.print(F(" °C, 湿度: "));
    Serial.print(dhtHum);
    Serial.println(F(" %RH"));
  }

  // 打印SHT30数据
  if (isnan(shtTemp) || isnan(shtHum)) {
    Serial.println(F("[SHT30] 读取失败,请检查连接"));
  } else {
    Serial.print(F("[SHT30] 温度: "));
    Serial.print(shtTemp);
    Serial.print(F(" °C, 湿度: "));
    Serial.print(shtHum);
    Serial.println(F(" %RH"));
  }

  // 数据对比提示
  if (!isnan(dhtTemp) && !isnan(shtTemp)) {
    float diff = abs(dhtTemp - shtTemp);
    if (diff > 2.0) {
      Serial.print(F("[警告] 两传感器温差超过2°C,差值: "));
      Serial.println(diff);
    }
  }
}

代码逻辑说明:setup()中初始化串口、DHT11和SHT30,若SHT30初始化失败则进入死循环提示。主循环使用millis()实现非阻塞定时,避免delay()阻塞其他任务。读取函数中,isnan()用于检测传感器返回的非法浮点值(NaN),这是传感器通信异常时的典型表现。打印格式采用[传感器名] 数据的格式,便于串口监视器过滤查看。

4.4 案例拆解:从原始数据到可靠工程实践

为了更深入地理解实际工程中的挑战,我们模拟一个智能花盆项目场景。项目要求:实时监测土壤环境温度与空气湿度,当温度超过35°C或湿度低于20%时,触发浇水继电器。

初始现象与目标:使用DHT11采集数据,发现温度读数在30°C左右波动,但湿度读数经常跳变到99.9%或0%,导致误触发浇水。目标是将湿度读数稳定在±5%误差范围内。

关键数据与日志分析:通过串口打印原始数据,发现DHT11的湿度值在短时间内从45%跳变到99.9%,且伴随校验错误。进一步检查发现,传感器数据线过长(超过1米),且未使用屏蔽线,导致信号完整性受损。

分析判断过程:DHT11的单总线协议对时序要求极高,长线缆会增加信号上升沿时间,导致主控采样点偏移。此外,电源纹波也可能干扰传感器内部ADC转换。

实际操作步骤:

  1. 将DHT11数据线缩短至20cm以内,并采用双绞线结构。
  2. 在DHT11的VCC和GND之间并联一个100nF去耦电容,滤除高频噪声。
  3. 将采集间隔从1秒调整为2秒,给传感器足够的稳定时间。
  4. 在代码中增加软件滤波:连续读取3次,取中位数作为有效值。

结果对比:优化后,湿度读数稳定在42%~48%之间,不再出现跳变。温度读数波动从±1.5°C降低到±0.5°C。

风险与代价:缩短线缆限制了传感器安装位置,软件滤波增加了约10ms的处理时间,但换来了数据可靠性的大幅提升。

复盘经验:对于单总线传感器,硬件布局和电源完整性比代码逻辑更重要。在项目初期就应规划好传感器位置,避免后期改动。

4.5 常见误区与调试技巧

在实际开发中,以下问题出现频率最高,需要特别注意:

🔍 误区一:DHT11读取频率过高
DHT11的数据手册明确标注“采样周期不得低于1秒”。如果主循环中无延时地连续读取,传感器将无法正确响应,导致返回0或NaN。解决方案是使用定时器或millis()控制读取间隔。
🔍 误区二:忽略I2C上拉电阻
ESP32的GPIO21和GPIO22内部有弱上拉,但驱动能力有限。当I2C总线长度超过10cm或挂载多个设备时,应外接2.2kΩ~4.7kΩ的上拉电阻至3.3V,否则通信可能间歇性失败。
🔍 误区三:串口波特率不匹配
代码中设置Serial.begin(115200),但串口监视器窗口未选择相同波特率,导致输出乱码。务必在监视器右下角选择“115200 baud”。

调试技巧:使用逻辑分析仪或示波器观察DHT11数据线的波形,确认时序是否符合规格书。对于I2C设备,可以使用I2C扫描程序(如Wire.begin(); for(address=1; address<127; address++))快速检测设备地址是否正确。

总结复盘:本章通过DHT11和SHT30的对比实战,完成了从传感器原理、硬件接线、代码编写到调试优化的完整闭环。DHT11适合成本敏感、精度要求不高的场景,而SHT30则适用于需要高可靠性和精度的工业级应用。在实际项目中,应根据环境约束(如线缆长度、电源质量、采样频率)选择合适的传感器,并做好硬件层面的抗干扰设计。下一阶段,我们将把采集到的温湿度数据作为特征输入,结合人体存在传感器,构建TinyML本地推理模型,实现环境状态的智能判断。