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

一、项目背景

随着物联网技术在智能家居、智慧农业、机房监控等领域的深入应用,环境参数的实时监测已成为最基础也最关键的需求。温湿度数据看似简单,却是判断环境舒适度、预防设备故障、调节通风系统的核心依据。

本项目将带大家从零搭建一个**智能环境监测站**,使用 ESP32 作为主控,搭配 DHT22 温湿度传感器采集环境数据,通过 SSD1306 OLED 屏幕本地实时显示,并利用 MQTT 协议将数据推送至云端 Broker(如 EMQX、Mosquitto、阿里云 IoT 平台),最终在手机或电脑上查看历史曲线。整个项目涉及嵌入式开发中最常见的三大技术栈:**外设驱动**、**显示交互**、**网络通信**,非常适合作为 ESP32 入门到进阶的练手项目。

二、硬件清单

序号 元件名称 型号 数量 备注
1 主控板 ESP32-DevKitC V4 1 双核 240MHz,内置 WiFi/BT
2 温湿度传感器 DHT22 (AM2302) 1 精度 ±0.5℃ / ±2%RH
3 OLED 屏 0.96 寸 SSD1306 I2C 1 128×64 分辨率
4 杜邦线 母对母 / 公对母 若干 建议 20cm
5 面包板 400 孔 1 方便调试
6 USB 数据线 Micro-USB 1 供电 + 烧录
7 电阻 10KΩ 1 DHT22 上拉电阻(可选)
**扩展备选**:可加入 BH1750 光照传感器、MQ-2 气体传感器、SDS011 PM2.5 传感器,构建综合环境站。

三、接线图说明

ESP32 的 GPIO 资源丰富,本项目接线遵循"功能分离、避免冲突"原则。

3.1 硬件连接表

元件引脚 ESP32 GPIO 物理丝印 说明
DHT22 VCC 3V3 3.3V 供电 3.3V
DHT22 DATA GPIO4 IO4 数据线,需 10K 上拉至 VCC
DHT22 GND GND GND 共地
OLED VCC 3V3 3.3V 供电
OLED GND GND GND 共地
OLED SCL GPIO22 IO22 I2C 时钟线,默认 I2C0
OLED SDA GPIO21 IO21 I2C 数据线,默认 I2C0

3.2 实物连接示意


        ┌─────────────────────┐
        │     ESP32-DevKitC   │
        │  ┌───────────────┐  │
        │  │  ESP-WROOM-32 │  │
        │  └───────────────┘  │
        │ 3V3 ●──────────● 3V3 │── DHT22 VCC、OLED VCC
        │ GND ●──────────● GND │── DHT22 GND、OLED GND
        │ IO4 ●──────────● DATA│── DHT22 数据
        │IO21 ●──────────● SDA │── OLED SDA
        │IO22 ●──────────● SCL │── OLED SCL
        └─────────────────────┘
               │
        ┌──────┴──────┐
        │   DHT22     │   GPIO4 ──[10K]── 3V3
        │  VCC DATA   │
        │      GND    │
        └─────────────┘

        ┌─────────────┐
        │ SSD1306     │
        │ OLED 0.96"  │   VCC ── 3.3V
        │ VCC GND SCL │   GND ── GND
        │      SDA    │   SCL ── GPIO22
        └─────────────┘   SDA ── GPIO21

3.3 注意事项

  • DHT22 数据线务必加上拉电阻,10KΩ 接 DATA 与 VCC 之间,否则长距离通信易出错。
  • ESP32 GPIO 在启动时不要接高电平输入,IO4/IO21/IO22 均为安全引脚。
  • OLED 屏幕 I2C 地址一般为 **0x3C**,若不显示可用 I2C Scanner 扫描确认。

四、完整代码(Arduino 框架)

4.1 安装依赖库

打开 Arduino IDE → 库管理器,搜索并安装以下库:

  • **DHT sensor library** (Adafruit)
  • **Adafruit Unified Sensor**
  • **Adafruit SSD1306**
  • **Adafruit GFX Library**
  • **PubSubClient** (Nick O'Leary)

4.2 完整源码


/*********************************************************************
 *  ESP32 智能环境监测站
 *  功能:DHT22 温湿度采集 + OLED 显示 + MQTT 上云
 *  作者:AI Know
 *  平台:Arduino-ESP32 (esp32 by Espressif Systems 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_PASSWORD = "your_wifi_password";

const char* MQTT_SERVER   = "broker.emqx.io";   // 公共测试 Broker
const int   MQTT_PORT     = 1883;
const char* MQTT_CLIENT   = "esp32_env_station_01";
const char* MQTT_TOPIC    = "home/room1/env";

#define DHTPIN    4
#define DHTTYPE   DHT22

#define SDA_PIN   21
#define SCL_PIN   22
#define SCREEN_W  128
#define SCREEN_H  64

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

unsigned long lastSample = 0;
const unsigned long SAMPLE_INTERVAL = 5000;  // 5 秒采集一次

// ============ 函数声明 ============
void setupWifi();
void setupMqtt();
void callback(char* topic, byte* payload, unsigned int length);
void reconnect();
void readSensor(float &t, float &h, bool &ok);

// =================================================================
void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\n[BOOT] ESP32 Env Station Starting...");

  // I2C 初始化
  Wire.begin(SDA_PIN, SCL_PIN);

  // OLED 初始化
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    while (true);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.display();

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

  // 网络
  setupWifi();
  setupMqtt();
}

// =================================================================
void loop() {
  if (!mqtt.connected()) reconnect();
  mqtt.loop();

  unsigned long now = millis();
  if (now - lastSample >= SAMPLE_INTERVAL) {
    lastSample = now;
    float temp, humi;
    bool ok;
    readSensor(temp, humi, ok);

    if (ok) {
      Serial.printf("[DATA] T=%.2f C  H=%.2f %%\n", temp, humi);
      publishData(temp, humi);
    } else {
    }
  }
}

// ============ WiFi 连接 ============
void setupWifi() {
  Serial.printf("[WIFI] Connecting to %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] OK, IP: %s\n", WiFi.localIP().toString().c_str());
  } else {
    Serial.println("\n[WIFI] FAIL, will retry in loop");
  }
}

// ============ MQTT 初始化 ============
void setupMqtt() {
  mqtt.setCallback(callback);
}

// ============ MQTT 回调(订阅消息处理)============
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.printf("[MQTT] Recv [%s]: ", topic);
  for (unsigned int i = 0; i < length; i++) {
  }
  Serial.println();
  // 示例:根据指令切换本地 LED
  // if (String(topic) == "home/room1/cmd" && payload[0] == '1') { ... }
}

// ============ MQTT 重连 ============
void reconnect() {
  static unsigned long lastTry = 0;
  if (millis() - lastTry < 5000) return;
  lastTry = millis();

  if (mqtt.connected()) return;
  if (mqtt.connect(MQTT_CLIENT)) {
    Serial.println(" OK");
    mqtt.publish("home/room1/status", "online");
    mqtt.subscribe("home/room1/cmd");
  } else {
    Serial.printf(" FAIL rc=%d\n", mqtt.state());
  }
}

// ============ 传感器读取 ============
void readSensor(float &t, float &h, bool &ok) {
  float temp = dht.readTemperature();
  float humi = dht.readHumidity();
  if (isnan(temp) || isnan(humi)) {
    ok = false;
    t = 0; h = 0;
  } else {
    ok = true;
    t = temp; h = humi;
  }
}

// ============ OLED 显示 ============
void updateOled(float t, float h, bool wifiOk, bool mqttOk) {
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("== Env Station ==");
  display.printf("Temp : %.1f C\n", t);
  display.printf("Humi : %.1f %%\n", h);
  display.setCursor(0, 34);
  display.printf("WiFi: %s\n", wifiOk ? "OK" : "NO");
  display.printf("MQTT: %s\n", mqttOk ? "ON" : "OFF");
  display.printf("RSSI: %d dBm", WiFi.RSSI());
  display.display();
}

// ============ 发布数据 ============
void publishData(float t, float h) {
  char payload[160];
  // 使用 JSON 格式,便于后端解析
           "{\"device\":\"%s\",\"temp\":%.2f,\"humi\":%.2f,\"rssi\":%d}",
  Serial.printf("[MQTT] Pub [%s]: %s\n", MQTT_TOPIC, payload);
}

4.3 编译与烧录

  1. 工具 → 开发板 → **ESP32 Dev Module**
  2. 端口选择对应的 COM 口
  3. 上传速度建议 921600
  4. 点击"上传",等待烧录完成
  5. 打开串口监视器(115200 波特率)查看日志

五、调试与排错

5.1 常见问题

**Q1:OLED 无显示?**

  • 检查 I2C 地址,SSD1306 通常为 0x3C,部分模块为 0x3D。
  • 用 I2C Scanner 例程扫描确认地址。
  • 确认 SDA/SCL 是否接反。

**Q2:DHT22 读数始终为 NaN?**

  • 10K 上拉电阻是否焊接/接好。
  • 采集间隔不要小于 2 秒(DHT22 时序要求)。
  • 确认数据线接的是 IO4 而非其他引脚。

**Q3:MQTT 连接失败?**

  • 路由器是否能访问外网(EMQX 公共 Broker)。
  • 防火墙是否屏蔽 1883 端口。
  • 改用局域网 Mosquitto 验证。

**Q4:WiFi 经常断连?**

  • 检查电源是否稳定(建议 5V/1A 供电)。
  • 加入 WiFi 重连机制:

if (WiFi.status() != WL_CONNECTED) {
  WiFi.reconnect();
}

5.2 调试技巧

  • 串口分级输出:使用 `[INFO]`、`[WARN]`、`[ERR]` 前缀便于过滤。
  • 心跳机制:定时发布 `heartbeat` 消息,监测设备在线率。
  • 离线缓存:网络异常时将数据存入 SPIFFS/LittleFS,恢复后批量上报。

六、进阶方向

完成基础功能后,可以从以下方向继续深入:

6.1 接入 Home Assistant / Node-RED

通过 MQTT 自动发现协议(Discovery)将本设备接入 Home Assistant,配合 Lovelace 卡片可以绘制温湿度历史曲线,并设置自动化联动(如温度 > 30℃ 自动开空调)。

6.2 加入低功耗策略

使用 ESP32 的 **Deep Sleep** 模式,结合 **ULP 协处理器** 定时唤醒采集,电池供电下续航可达数月。唤醒后快速上报后再次休眠,整体平均功耗可压到 80μA 以下。

6.3 OTA 远程升级

借助 ArduinoOTA 或 `Update` 库实现**空中固件升级**。配合 MQTT 下发升级指令,设备重启后从 HTTP 服务器拉取新固件,告别拆机烧录的烦恼,适合规模化部署。

6.4 多传感器融合扩展

  • **BH1750**:光照强度,自动化控制窗帘
  • **MQ-2 / MQ-135**:烟雾、有害气体监测
  • **PMS5003**:PM2.5 浓度
  • **SGP30**:TVOC、eCO₂ 空气质量
  • **土壤湿度传感器**:扩展至智慧农业场景

---

通过本项目,你已经掌握了 ESP32 嵌入式开发的核心链路:**硬件接线 → 驱动开发 → 显示输出 → 网络通信**。这套模板可以平移到智能家居、智慧农业、工业监控等众多场景。下一步可以尝试把代码重构为 PlatformIO 多文件工程,并使用 FreeRTOS 任务拆分采集、显示、MQTT 三个独立任务,让系统更健壮。祝你玩得开心!