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

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

一、项目背景

在物联网与边缘计算快速发展的今天,环境数据采集已成为智慧农业、机房监控、智能家居等场景的基础需求。本教程将带大家从零搭建一个基于 ESP32 的智能环境监测站,实时采集温度与湿度,本地 OLED 屏幕可视化显示,并通过 MQTT 协议将数据推送至云端 Broker,为后续接入 Home Assistant、Node-RED 或自建看板提供数据源。

整个项目兼顾入门友好工程实践:硬件成本不足 30 元,代码量约 150 行,既能跑通也能扩展。学习完成后,读者将掌握 ESP32 的 I2C、GPIO 中断、Wi-Fi 联网、MQTT 客户端以及 FreeRTOS 任务调度的核心套路。

二、硬件清单

序号 元件 型号 / 规格 数量 参考单价
1 主控板 ESP32-DevKitC V4(30 引脚版) 1 22 元
2 温湿度传感器 DHT22(AM2302) 1 6 元
3 OLED 屏 0.96 寸 SSD1306 I2C(128×64) 1 8 元
4 面包板 830 孔无焊面包板 1 4 元
5 杜邦线 公-公 / 公-母 各 10 根 20 3 元
6 4.7kΩ 电阻 上拉用(DHT22 DATA 线) 1 <1 元
7 Micro-USB 线 数据线(非纯充电线) 1
选型建议:DHT22 比 DHT11 精度更高(±0.5℃ / ±2%RH),适合对数据可靠性有要求的场景。若预算充足可替换为 SHT30/SHT35(I2C 接口,更稳定)。

三、接线图说明

引脚定义

模块引脚 ESP32 GPIO 备注
DHT22 VCC 3V3 必须 3.3V,5V 会缩短寿命
DHT22 GND GND 共地
DHT22 DATA GPIO4 需 4.7kΩ 上拉至 3V3
OLED VCC 3V3
OLED GND GND
OLED SDA GPIO21 I2C0 SDA(默认)
OLED SCL GPIO22 I2C0 SCL(默认)

实物连接示意


   ┌──────────────────────┐
   │       ESP32 板       │
   │  3V3 ●  ● GND        │
   │  GPIO21 ─┐           │    ┌──────────────┐
   │  GPIO22 ─┼──SCL──────┼────┤   SSD1306    │
   │  GPIO4  ─┼──DATA─────┼─┐  │   OLED 屏    │
   │          │           │ │  └──────────────┘
   └──────────┼───────────┼─┘
              │          │
           4.7kΩ        │
              │          │
             3V3       DHT22
                        VCC

实物摆放提示

  1. 将 ESP32 竖插在面包板中央长槽两侧;
  2. DHT22 放置在面包板左侧,栅格孔位方便上拉电阻的跨接;
  3. OLED 屏插在右侧,I2C 走线尽量短,避免长线干扰;
  4. DHT22 探头面朝外,远离 ESP32 的散热区(Wi-Fi 模组会轻微发热,影响测温)。

四、完整代码(Arduino 框架)

4.1 库依赖

在 Arduino IDE 中安装以下库:

  • DHT sensor library(Adafruit)
  • Adafruit Unified Sensor
  • Adafruit SSD1306
  • PubSubClient(Nick O'Leary)
  • WiFi(ESP32 内置)

4.2 源码


/*********************************************************************
 *  ESP32 智能环境监测站
 *  功能:DHT22 温湿度采集 + SSD1306 OLED 显示 + MQTT 上报
 *  作者:AI Know 编辑部
 *  平台:Arduino-ESP32 v2.0.14+
 *********************************************************************/

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

// ====== 用户配置区 ======
const char* WIFI_SSID     = "your_wifi_ssid";
const char* WIFI_PASS     = "your_wifi_password";
const char* MQTT_SERVER   = "broker.emqx.io";   // 公网测试 Broker
const int   MQTT_PORT     = 1883;
const char* MQTT_CLIENTID = "esp32_env_001";
const char* MQTT_TOPIC    = "home/livingroom/env";

// ====== 硬件引脚 ======
#define DHT_PIN   4
#define DHT_TYPE  DHT22

// ====== OLED 配置 ======
#define SCREEN_W 128
#define SCREEN_H 64
#define OLED_ADDR 0x3C
#define OLED_RST  -1   // 多数开发板无 RST 引脚
Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, OLED_RST);

// ====== 全局对象 ======
DHT dht(DHT_PIN, DHT_TYPE);
WiFiClient espClient;
PubSubClient mqtt(espClient);

float g_temp = 0.0, g_humi = 0.0;
unsigned long g_lastSample = 0;
const unsigned long SAMPLE_INTERVAL = 5000;   // 5 秒采集一次

// ====== 函数声明 ======
void connectWiFi();
void connectMQTT();
void readSensor();
void updateOLED();
void publishData();

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

  // OLED 初始化
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("[ERR] OLED 初始化失败");
    while (true);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.display();

  // 传感器初始化
  dht.begin();

  // 网络初始化
  connectWiFi();
  mqtt.setBufferSize(512);
}

// FreeRTOS 风格的循环调度
void loop() {
  unsigned long now = millis();

  if (now - g_lastSample >= SAMPLE_INTERVAL) {
    g_lastSample = now;
    readSensor();
    updateOLED();
  }

  if (!mqtt.connected()) {
    connectMQTT();
  }
  mqtt.loop();

  publishData();
  delay(100);
}

// =================== 子函数 ===================
void connectWiFi() {
  Serial.printf("[WiFi] 连接中: %s", WIFI_SSID);
  WiFi.mode(WIFI_STA);
  int retry = 0;
  while (WiFi.status() != WL_CONNECTED && retry < 30) {
    delay(500);
    Serial.print(".");
    retry++;
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("\n[WiFi] 已连接, IP: %s\n", WiFi.localIP().toString().c_str());
  } else {
    Serial.println("\n[WiFi] 连接失败,请检查 SSID / 密码");
  }
}

void connectMQTT() {
  while (!mqtt.connected()) {
    Serial.print("[MQTT] 连接 Broker...");
    if (mqtt.connect(MQTT_CLIENTID)) {
      Serial.println("OK");
      mqtt.publish("home/livingroom/status", "online");
    } else {
      Serial.printf("失败 rc=%d, 5 秒后重试\n", mqtt.state());
      delay(5000);
    }
  }
}

void readSensor() {
  float t = dht.readTemperature();
  float h = dht.readHumidity();
  if (isnan(t) || isnan(h)) {
    Serial.println("[WARN] DHT22 读取失败,使用上一次值");
    return;
  }
  g_temp = t;
  g_humi = h;
  Serial.printf("[SENSOR] T=%.1f C, H=%.1f %%\n", g_temp, g_humi);
}

void updateOLED() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("=== Env Station ===");
  display.printf("Temp : %.1f C\n", g_temp);
  display.printf("Humi : %.1f %%\n", g_humi);

  // 简易温湿度条形图
  display.setCursor(0, 32);
  display.print("T: ");
  display.println(String((int)(g_temp + 40) / 1).c_str());  // 0~80℃ 映射
  display.setCursor(0, 44);
  display.print("H: ");
  int bar = (int)(g_humi / 100.0 * 80);
  for (int i = 0; i < bar; i++) display.print((char)0xFF);
  display.display();
}

void publishData() {
  static unsigned long lastPub = 0;
  if (millis() - lastPub < 10000) return;   // 10 秒上报一次
  lastPub = millis();

  if (!mqtt.connected()) return;

  char payload[160];
           "{\"device\":\"%s\",\"temp\":%.1f,\"humi\":%.1f,"
           "\"rssi\":%d,\"uptime\":%lu}",
           MQTT_CLIENTID, g_temp, g_humi,
           WiFi.RSSI(), millis() / 1000);

  if (mqtt.publish(MQTT_TOPIC, payload)) {
    Serial.printf("[MQTT] 上报成功: %s\n", payload);
  } else {
    Serial.println("[MQTT] 上报失败");
  }
}

4.3 烧录步骤

  1. Arduino IDE → 工具 → 开发板选 ESP32 Dev Module
  2. 端口选择对应的 COM 口;
  3. 波特率 115200,点击上传;
  4. 打开串口监视器,观察 [SENSOR][MQTT] 日志。

五、调试与排错

5.1 常见问题清单

现象 可能原因 解决方案
串口一直输出 .. Wi-Fi SSID / 密码错误 改用手机热点验证
OLED 全黑 I2C 地址不对 用 I2C Scanner 扫描,确认是 0x3C 还是 0x3D
OLED 显示花屏 SDA / SCL 接反 调换两根线
DHT 一直 NaN 缺少 4.7kΩ 上拉 DATA 与 3V3 之间加电阻
MQTT 连接失败 rc=-2 Broker 不可达 检查路由器能否访问外网
MQTT rc=5 鉴权失败 确认 Broker 无需账号或填对

5.2 推荐调试工具

  • MQTTBoxMQTTX:桌面端订阅调试;
  • Home Assistant MQTT 集成:直接生成传感器卡片;
  • Wireshark:抓取 1883 端口的 MQTT 包。

5.3 关键日志解读


[BOOT] ESP32 环境监测站启动       ← 启动正常
[WiFi] 已连接, IP: 192.168.1.42   ← 网络 OK
[MQTT] 连接 Broker...OK            ← Broker OK
[SENSOR] T=25.3 C, H=58.2 %        ← 传感器正常
[MQTT] 上报成功: {"device":...}    ← 链路打通

六、进阶方向

方向 1:接入 Home Assistant 打造智能联动

configuration.yaml 中加入:


mqtt:
  sensor:
    - name: "客厅温度"
      state_topic: "home/livingroom/env"
      value_template: "{{ value_json.temp }}"
      unit_of_measurement: "°C"
    - name: "客厅湿度"
      state_topic: "home/livingroom/env"
      value_template: "{{ value_json.humi }}"
      unit_of_measurement: "%"

即可在 HA 中实现「温度 > 28℃ 自动开空调」等自动化。

方向 2:引入 Deep Sleep 实现电池供电

将采集周期改为 5 分钟,并通过 esp_deep_sleep_start() 进入深度睡眠,实测电池可续航 6 个月以上


esp_sleep_enable_timer_wakeup(300 * 1000000ULL); // 5 分钟
esp_deep_sleep_start();

配合 BME280 替换 DHT22,还能顺便采集大气压力。

方向 3:升级为 LoRa Mesh 多节点监测

若监测面积大、Wi-Fi 覆盖不到,可加一块 SX1278 LoRa 模组,多块 ESP32 自组网,将数据汇聚到一台带 4G 上网的网关,最终实现温室大棚、智慧工地的远距离监测。

---

小结:本项目用最小成本打通「感知 → 边缘 → 云」全链路,是 ESP32 嵌入式学习的最佳练手案例之一。掌握了它,再去做 BLE 信标、LoRa 网关、语音控制等进阶项目都会事半功倍。建议读者在吃透代码后,尝试将传感器换成 SHT30 / BME680 / PMS5003 等,探索更多元的物联网场景。