ESP32 实战 · 2026-06-07 配图
AI Know 自有配图 · 嵌入式 · IoT · 硬件编程

ESP32 智能环境监测站实战教程:温湿度采集 + OLED 显示 + MQTT 上云

一、项目背景

在物联网(IoT)应用场景中,环境监测是最常见的需求之一。无论是家庭温室、智能家居、机房温控,还是工业仓储的温湿度报警系统,都离不开一套稳定、低成本、易扩展的数据采集终端。

本项目基于 ESP32 主控,搭配 DHT22 温湿度传感器0.96 寸 SSD1306 OLED 显示屏,实现本地实时显示 + 远程 MQTT 上传。ESP32 凭借双核 240MHz 主频、内置 Wi-Fi/BLE、丰富 GPIO 以及超低功耗模式,成为此类监测节点的理想选择。

通过本教程,你将学会:

整个项目硬件成本约 30 元,代码可编译可直接烧录,零基础也能跑通。

---

二、硬件清单

序号 元件 型号 数量 说明
1 主控板 ESP32-DevKitC(30P) 1 推荐 NodeMCU-32S 兼容款
2 温湿度传感器 DHT22(AM2302) 1 比 DHT11 精度更高,量程 -40~80℃
3 OLED 屏 0.96 寸 SSD1306(I2C,128×64) 1 蓝色或白色任选
4 面包板 830 孔 1 免焊接,方便调试
5 杜邦线 公-公 / 公-母 若干 建议备 20 根
6 电阻 10KΩ 1 DHT22 上拉用
7 电源 USB 5V/1A 1 或 3.7V 锂电池
小贴士:如果手头只有 DHT11,把代码中 `DHT22` 改成 `DHT11` 即可,其余代码完全兼容。

---

三、接线图说明

ESP32 DevKitC 引脚分布丰富,我们使用以下默认分配,方便后期外接更多传感器。

3.1 引脚分配表

模块 引脚 ESP32 GPIO 说明
DHT22 DATA 信号线 GPIO 4 需 10KΩ 上拉到 3.3V
DHT22 VCC 电源 3V3 切忌接 5V,会烧坏传感器
DHT22 GND GND
OLED SDA 数据线 GPIO 21 I2C 默认 SDA
OLED SCL 时钟线 GPIO 22 I2C 默认 SCL
OLED VCC 电源 3V3 或 5V SSD1306 通常兼容两者
OLED GND GND

3.2 实物连接描述

想象你手中的面包板横向长、纵向短:

  1. 左侧电源轨:红色线接入 ESP32 的 3V3 引脚,蓝色线接入 GND。整条电源轨即 3.3V 公共端。
  2. DHT22 放置:将 DHT22 插在面包板中间区域,三个引脚朝下。从左到右依次为 `VCC`、`DATA`、`NC`、`GND`(DHT22 通常四脚,第二个脚为数据,第三个悬空)。
  3. 上拉电阻:10KΩ 电阻一端接 DHT22 的 DATA 脚,另一端接到 3.3V 电源轨。电阻紧贴传感器焊接或直接插在同列插孔。
  4. 杜邦线连接:用公-母线从 DHT22 的 DATA 脚接到 ESP32 的 GPIO 4
  5. OLED 屏:四个排针依次插入面包板,GND 接蓝线,VCC 接红线,SDAGPIO 21SCLGPIO 22
  6. 整体布局:ESP32 放在面包板上方,传感器与屏幕分列左右,避免相互遮挡。
接线完成后,检查是否有短路。建议先用万用表通断档确认 3V3 与 GND 之间电阻不为零。

---

四、完整代码(Arduino IDE)

4.1 准备工作

  1. Arduino IDE 安装 ESP32 核心(开发板管理器搜索 `esp32`,安装 2.x 版本)。
  2. 库管理器依次安装:

- `DHT sensor library`(Adafruit)

- `Adafruit Unified Sensor`

- `Adafruit SSD1306`

- `PubSubClient`(Nick O'Leary)

  1. 修改代码顶部的 `WIFI_SSID`、`WIFI_PASS`、`MQTT_SERVER` 三个宏定义。

4.2 完整源码


/* ===========================================================
 *  ESP32 智能环境监测站
 *  功能:DHT22 温湿度采集 + OLED 显示 + MQTT 上传
 *  平台:Arduino-ESP32 2.x
 *  作者:AI Know
 * =========================================================== */

#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <Adafruit_SSD1306.h>

/* -------------------- 用户配置区 -------------------- */
#define WIFI_SSID     "你的WiFi名称"
#define WIFI_PASS     "你的WiFi密码"

#define MQTT_SERVER   "broker.emqx.io"   // 公共测试 Broker
#define MQTT_PORT     1883
#define MQTT_USER     ""                 // 若 Broker 无认证可留空
#define MQTT_PASS     ""
#define MQTT_CLIENT_ID  "esp32_env_01"
#define MQTT_TOPIC_T  "home/room/temp"
#define MQTT_TOPIC_H  "home/room/humi"

/* -------------------- 硬件引脚 -------------------- */
#define DHT_PIN   4
#define DHT_TYPE  DHT22

#define OLED_SDA  21
#define OLED_SCL  22
#define SCREEN_W  128
#define SCREEN_H  64

/* -------------------- 对象初始化 -------------------- */
DHT dht(DHT_PIN, DHT_TYPE);
Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, -1);
WiFiClient espClient;
PubSubClient mqtt(espClient);

/* -------------------- 函数声明 -------------------- */
void wifiInit();
void mqttInit();

/* ============================================================ */
void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\n[BOOT] ESP32 环境监测站启动");

  // I2C 与 OLED 初始化
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("[ERR] OLED 初始化失败,请检查 I2C 地址(0x3C/0x3D)");
    while (true);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("Booting...");
  display.display();

  // DHT 初始化
  dht.begin();

  // 网络
  wifiInit();
  mqttInit();
}

/* -------------------- 主循环 -------------------- */
void loop() {
  // 1. 保持 MQTT 连接
  if (!mqtt.connected()) mqttInit();
  mqtt.loop();

  // 2. 读取传感器(每 2 秒一次)
  static unsigned long lastRead = 0;
  if (millis() - lastRead > 2000) {
    lastRead = millis();

    float t = dht.readTemperature();
    float h = dht.readHumidity();

    if (isnan(t) || isnan(h)) {
      Serial.println("[WARN] DHT 读取失败");
      return;
    }

    Serial.printf("[DATA] T=%.1f C  H=%.1f %%\n", t, h);
    publishData(t, h);
  }
}

/* -------------------- WiFi 连接 -------------------- */
void wifiInit() {
  WiFi.mode(WIFI_STA);
  Serial.printf("[WIFI] 连接中: %s", WIFI_SSID);

  display.clearDisplay();
  display.setCursor(0, 0);
  display.print("WiFi:");
  display.println(WIFI_SSID);
  display.display();

  int tryCnt = 0;
  while (WiFi.status() != WL_CONNECTED && tryCnt < 30) {
    delay(500);
    Serial.print('.');
    tryCnt++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("\n[WIFI] 已连接,IP: %s\n", WiFi.localIP().toString().c_str());
  } else {
    Serial.println("\n[WIFI] 连接失败,将继续尝试");
  }
}

/* -------------------- MQTT 连接 -------------------- */
void mqttInit() {
  mqtt.setKeepAlive(60);

  while (!mqtt.connected()) {
    Serial.print("[MQTT] 连接 Broker...");
    if (mqtt.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS)) {
      Serial.println(" OK");
    } else {
      Serial.printf(" 失败 rc=%d,5 秒后重试\n", mqtt.state());
      delay(5000);
    }
  }
}

/* -------------------- OLED 显示 -------------------- */
void oledShow(float t, float h, bool online) {
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("=== Env Station ===");

  display.setTextSize(2);
  display.setCursor(0, 16);
  if (t < -50) display.print("T: --.-");
  else         display.printf("T:%2.1fC", t);

  display.setCursor(0, 40);
  if (h < -50) display.print("H: --.-");
  else         display.printf("H:%2.1f%%", h);

  display.setTextSize(1);
  display.setCursor(90, 0);
  display.print(online ? "MQTT:OK" : "MQTT:X");
  display.display();
}

/* -------------------- 发布数据 -------------------- */
void publishData(float t, float h) {
  char buf[16];
  snprintf(buf, sizeof(buf), "%.1f", t);
  mqtt.publish(MQTT_TOPIC_T, buf, true);   // retained

  snprintf(buf, sizeof(buf), "%.1f", h);
}

4.3 代码要点说明

  • OLED 初始化失败陷入死循环:这是常用技巧,让用户快速发现硬件问题。
  • `mqtt.publish(..., true)`:retained 标志设为 `true`,新订阅者能立即拿到最近一次数据。
  • 静态变量 `lastRead`:避免使用 `delay()` 阻塞主循环,保证 MQTT 心跳不被卡住。
  • I2C 地址:常见 SSD1306 地址是 `0x3C`,若屏幕无显示可用 I2C 扫描工具确认。

---

五、调试步骤

  1. 串口监视器:打开 Arduino IDE 串口监视器,波特率 115200,观察 `[WIFI]`、`[MQTT]` 日志。
  2. MQTT 客户端验证:用电脑或手机安装 MQTTX / MQTTool,连接同一 Broker,订阅主题 `home/room/#`,即可看到实时数据。
  3. OLED 无显示

- 确认 I2C 地址(I2C Scanner 扫描 0x3C 或 0x3D)。

- 检查 SDA/SCL 是否接反。

- 测量 3.3V 电源是否正常。

  1. DHT22 一直返回 NaN

- 确认上拉电阻已焊接。

- 数据线过长(>20cm)建议加屏蔽或换更短接线。

- 连续读取至少间隔 2 秒,否则传感器会响应失败。

  1. MQTT 连接失败 `rc=-2`:网络不通,检查 Wi-Fi 与外网访问;`rc=5` 通常是 Broker 鉴权问题。
  2. JSON 化扩展:若想一次性发布温湿度,可改用 `ArduinoJson` 库组装 JSON 字符串,例如:

   StaticJsonDocument<64> doc;
   doc["t"] = t; doc["h"] = h;
   serializeJson(doc, buf);
   mqtt.publish("home/room/state", buf);

---

六、进阶方向

完成基础版本后,可以从以下方向继续扩展:

6.1 接入 Home Assistant / Node-RED

将 MQTT 数据通过 Home Assistant 的 MQTT 集成接入,可自动生成温湿度卡片,并配合 `automation.yaml` 设置阈值告警(如温度 > 35℃ 推送手机通知)。

6.2 增加低功耗模式

将 DHT22 替换为 SHT30(I2C 接口,精度更高),并利用 ESP32 的 `deepSleep`:


esp_sleep_enable_timer_wakeup(60 * 1000000); // 60 秒唤醒
esp_deep_sleep_start();

配合电池供电,节点续航可达数月。

6.3 多传感器融合

在 I2C 总线上并联:

  • BMP280:气压与海拔
  • CCS811:eCO₂ 与 TVOC(空气质量)
  • BH1750:光照强度

通过唯一 I2C 地址区分设备,OLED 切换页面显示,构建一个真正的小型环境监测站。

---

七、结语

本项目从硬件到代码完整呈现了一个最小可运行的 ESP32 物联网节点。核心要点是 Wi-Fi 通信 + I2C 总线 + 单总线传感器 的协同工作。掌握这一范式后,你可以将 `DHT22` 替换成任何传感器,将 `MQTT` 替换成 `HTTP` 或 `Modbus`,快速复用到工业、农业、智能家居等各种场景。

下一步,建议你尝试用 ESP-IDF 框架 重写该工程,理解 FreeRTOS 双核任务调度与底层驱动模型,这会让你对嵌入式开发有更深的认知。祝玩得开心!