Let me plan the content:

Let me write this out in detail, aiming for ~1500 words.

ESP32 实战教程:智能环境监测站(温湿度 + OLED + MQTT)

难度:⭐⭐☆☆☆ | 阅读时间:8 分钟 | 实战时间:2 小时

一、项目背景

在物联网应用场景中,环境监测是最常见的入门级项目之一。本次我们将基于 ESP32 构建一个**智能环境监测站**,实时采集温湿度数据并通过 OLED 本地显示,同时利用 MQTT 协议上报至云端(如 EMQX、Home Assistant、ThingsBoard),实现"端—边—云"全链路打通。

相较于 Arduino Uno,ESP32 优势明显:

**项目核心指标:**

---

二、硬件清单

序号 元器件 型号 数量 参考价格
1 主控板 ESP32-DevKitC V4 1 ¥28
2 温湿度传感器 DHT22 (AM2302) 1 ¥12
3 OLED 显示屏 SSD1306 0.96" 128×64 I2C 1 ¥15
4 面包板 830 孔 1 ¥5
5 杜邦线 公-公 / 公-母 若干 ¥3
6 4.7kΩ 电阻 上拉用 1 ¥0.1
💡 **选购建议**:建议购买 Type-C 接口的 ESP32-DevKitC V4(带 CH340 串口芯片),兼容性更好。OLED 务必选 I2C 接口(4 针版本),避免与 SPI 版本混淆。

---

三、接线图说明

引脚分配表

模块 模块引脚 ESP32 GPIO 备注
DHT22 VCC 3V3 3.3V 供电
DHT22 DATA **GPIO 4** 需 4.7kΩ 上拉至 VCC
DHT22 GND GND 共地
OLED VCC 3V3 3.3V 供电
OLED GND GND 共地
OLED SCL **GPIO 22** I2C 时钟
OLED SDA **GPIO 21** I2C 数据

实物接线示意


        ┌─────────────────┐
        │   ESP32-DevKitC │
        │                 │
   3V3 ─┤ 3V3         D2 ├─ GPIO 4 ──┐
   GND ─┤ GND         D1 ├─ GPIO 22 ─┼──┐
        │                 │          │  │
        │              21 ├─ GPIO 21 ┤  │  (I2C 总线)
        │                 │          │  │
        └─────────────────┘          │  │
                                      │  │
        ┌──────────┐                  │  │
        │  DHT22   │                  │  │
        │ ┌──┐     │  DATA ───────────┘  │
        │ │  │     │                     │
        │ └──┘  VCC├──── 3V3            │
        │      GND├──── GND            │
        └──────────┘                    │
                                      │  │
        ┌──────────┐                  │  │
        │ SSD1306  │                  │  │
        │ OLED     │  SCL ────────────┘  │
        │  ┌──┐    │  SDA ───────────────┘
        │  │  │ VCC│──── 3V3
        │  └──┘ GND│──── GND
        └──────────┘

关键注意事项

  1. **DHT22 上拉电阻**:DATA 线空闲时为高电平,4.7kΩ 上拉电阻必须接在 DATA 与 VCC 之间(部分模块已板载)。
  2. **I2C 总线长度**:面包板跳线建议 < 20cm,过长会因上拉不足导致显示花屏。
  3. **电源去耦**:DHT22 启动瞬间电流可达 2.5mA,建议在 VCC/GND 间并联 100nF 陶瓷电容。

---

四、完整代码(Arduino 框架)

4.1 安装依赖库

在 Arduino IDE 中安装以下库:

  • `DHT sensor library` (Adafruit)
  • `Adafruit Unified Sensor`
  • `Adafruit SSD1306`
  • `PubSubClient` (Nick O'Leary)
  • `ArduinoJson` (Benoit Blanchon)

4.2 主程序


/*************************************************************
 * 智能环境监测站 - ESP32 + DHT22 + SSD1306 + MQTT
 * 作者: AI Know Lab
 * 日期: 2026-01
 *************************************************************/
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_SSD1306.h>
#include <ArduinoJson.h>

// ============ 用户配置 ============
const char* WIFI_SSID     = "your_wifi_ssid";
const char* WIFI_PASSWORD = "your_wifi_password";

const char* MQTT_SERVER   = "broker.emqx.io";
const int   MQTT_PORT     = 1883;
const char* MQTT_USER     = "env_station_01";
const char* MQTT_PASS     = "public";
const char* MQTT_TOPIC    = "home/env/station01/data";

// ============ 硬件引脚 ============
#define DHT_PIN    4
#define DHT_TYPE   DHT22
#define I2C_SDA    21
#define I2C_SCL    22
#define OLED_ADDR  0x3C
#define OLED_RST   -1
#define SCREEN_W   128
#define SCREEN_H   64

// ============ 运行时对象 ============
DHT dht(DHT_PIN, DHT_TYPE);
Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, OLED_RST);
WiFiClient espClient;
PubSubClient mqtt(espClient);

unsigned long lastReport = 0;
const unsigned long REPORT_INTERVAL = 5000UL;  // 5 秒

// ============ 函数声明 ============
void connectWiFi();
void connectMQTT();
void readSensor(float &t, float &h, bool &ok);
void showOLED(float t, float h, bool online);
void publishMQTT(float t, float h);
void drawProgressBar(uint8_t x, uint8_t y, int value);

// ============ 初始化 ============
void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\n[BOOT] Smart Environment Station v1.0");

  // 初始化 I2C
  Wire.begin(I2C_SDA, I2C_SCL);

  // 初始化 OLED
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("[ERR] OLED init failed");
    while (true) delay(1000);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("Smart Env Station");
  display.println("Initializing...");
  display.display();

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

  // 连接网络
  connectWiFi();
  mqtt.setServer(MQTT_SERVER, MQTT_PORT);
  mqtt.setKeepAlive(30);
  connectMQTT();

  Serial.println("[BOOT] System ready");
}

// ============ 主循环 ============
void loop() {
  if (!mqtt.connected()) {
    connectMQTT();
  }
  mqtt.loop();

  if (millis() - lastReport >= REPORT_INTERVAL) {
    lastReport = millis();

    float temperature, humidity;
    bool ok;
    readSensor(temperature, humidity, ok);

    showOLED(temperature, humidity, mqtt.connected());

    if (ok) {
      publishMQTT(temperature, humidity);
    } else {
      Serial.println("[WARN] DHT22 read failed");
    }
  }
}

// ============ WiFi 连接 ============
void connectWiFi() {
  Serial.printf("[WIFI] Connecting to %s", WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  uint8_t 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 connectMQTT() {
  while (!mqtt.connected()) {
    Serial.print("[MQTT] Connecting...");
    if (mqtt.connect(MQTT_USER, MQTT_USER, MQTT_PASS)) {
      Serial.printf(" OK, clientId=%s\n", MQTT_USER);
      // 订阅控制主题(用于远程重启/校准)
      mqtt.subscribe("home/env/station01/cmd");
    } else {
      Serial.printf(" FAIL, rc=%d, retry in 3s\n", mqtt.state());
      delay(3000);
    }
  }
}

// ============ 读取传感器 ============
void readSensor(float &t, float &h, bool &ok) {
  h = dht.readHumidity();
  t = dht.readTemperature();
  ok = !isnan(t) && !isnan(h);
}

// ============ OLED 显示 ============
void showOLED(float t, float h, bool online) {
  display.clearDisplay();

  // 标题
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("ENV STATION  ");
  display.print(online ? F("[MQTT]") : F("[OFF]"));

  // 分隔线
  display.drawLine(0, 10, 127, 10, SSD1306_WHITE);

  // 温度
  display.setTextSize(2);
  display.setCursor(0, 16);
  display.print(t, 1);
  display.setTextSize(1);
  display.print(" C");

  // 湿度
  display.setCursor(0, 38);
  display.setTextSize(2);
  display.print(h, 1);
  display.setTextSize(1);
  display.print(" %");

  // 状态栏
  display.setTextSize(1);
  display.setCursor(0, 56);
  display.print(online ? "Up:5s  IP:" : "WiFi down");
  if (online) display.print(WiFi.localIP());

  display.display();
}

// ============ MQTT 发布 ============
void publishMQTT(float t, float h) {
  StaticJsonDocument<192> doc;
  doc["device"]  = "esp32_env01";
  doc["temp"]    = t;
  doc["humi"]    = h;
  doc["rssi"]    = WiFi.RSSI();
  doc["uptime"]  = millis() / 1000;

  char payload[192];
  size_t n = serializeJson(doc, payload);
  bool ok = mqtt.publish(MQTT_TOPIC, (uint8_t*)payload, n, true);  // retained

  Serial.printf("[MQTT] %s | %s\n", ok ? "OK" : "FAIL", payload);
}

// ============ MQTT 回调(可选) ============
void mqttCallback(char* topic, byte* payload, unsigned int len) {
  Serial.printf("[MQTT] CMD recv: %s\n", (char*)payload);
  if (strncmp((char*)payload, "reboot", 6) == 0) {
    ESP.restart();
  }
}

4.3 PlatformIO 用户替代方案

若使用 PlatformIO,可在 `platformio.ini` 中配置:


[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
    adafruit/DHT sensor library@^1.4.6
    adafruit/Adafruit Unified Sensor@^1.1.9
    adafruit/Adafruit SSD1306@^2.5.7
    knolleary/PubSubClient@^2.8
    bblanchon/ArduinoJson@^7.0.0
monitor_speed = 115200

---

五、调试指南

5.1 常见问题排查表

现象 可能原因 解决方案
OLED 全黑不亮 I2C 地址错误 / 接触不良 用 I2C Scanner 扫描 0x3C 或 0x3D
OLED 显示花屏 接触不良 / 供电不足 更换杜邦线,检查 3V3 电压
DHT22 一直返回 NaN 接线错误 / 上拉缺失 确认 DATA 接 GPIO4,添加 4.7kΩ 上拉
WiFi 连接超时 2.4GHz 信道问题 路由器改固定信道 1/6/11
MQTT 频繁断连 keepalive 过短 / Broker 不稳定 调大 keepalive 至 60s
串口乱码 波特率不匹配 确认 115200 8N1

5.2 I2C Scanner 小工具


#include <Wire.h>
void setup() {
  Wire.begin(21, 22);
  Serial.begin(115200);
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.printf("Found 0x%02X\n", addr);
    }
  }
}
void loop() {}

5.3 串口日志解读

正常启动后串口应输出:


[BOOT] Smart Environment Station v1.0
[WIFI] Connecting to your_wifi_ssid.......... OK, IP: 192.168.1.42
[MQTT] Connecting... OK, clientId=env_station_01
[BOOT] System ready
[MQTT] OK | {"device":"esp32_env01","temp":23.4,"humi":58.2,"rssi":-52,"uptime":5}

---

六、进阶方向

🚀 方向一:低功耗改造(电池供电数月)

接入 18650 锂电池 + TP4056 充电模块,并启用 **深度睡眠**:


#include <esp_sleep.h>

void enterDeepSleep(uint64_t seconds) {
  esp_sleep_enable_timer_wakeup(seconds * 1000000ULL);
  Serial.println("Sleep...");
  esp_deep_sleep_start();
}

配合 `MQTT Last Will` 与 `retain` 标志,云端始终保留最后一条数据。唤醒功耗可降至 **10μA 以下**,配合 3000mAh 电池可达 3-6 个月续航。

🚀 方向二:多传感器融合

扩展为完整环境监测:

  • **BMP280**:气压 + 海拔
  • **MQ-135**:空气质量(CO₂、NH₃、苯)
  • **BH1750**:光照强度
  • **SGP30**:VOC 与 eCO₂

使用 I2C 总线多机挂载,注意统一供电与去耦。

🚀 方向三:本地 Web 备援通道

当 MQTT Broker 不可达时,启用 **mDNS + 简易 Web Server**:


#include <WebServer.h>
#include <ESPmDNS.h>

WebServer server(80);

void setupWeb() {
  MDNS.begin("env-station");
  server.on("/", []() {
    server.send(200, "application/json", "{\"temp\":23.4,\"humi\":58.2}");
  });
  server.begin();
}

手机/PC 通过 `http://env-station.local` 即可访问降级数据,提升系统鲁棒性。

---

七、写在最后

本项目作为 ESP32 物联网开发的"Hello World",涵盖了**传感器采集 + 本地显示 + 无线上报**三大核心能力。建议读者在此基础上完成至少一个进阶方向后,再去挑战更复杂的项目(如 LoRa Mesh、BLE 定位、语音控制等)。完整的代码与原理图可在 GitHub 仓库 `ai-know-lab/esp32-env-station` 找到,欢迎 Star 与 PR。

📌 **下月预告**:我们将带来《ESP32 + SX1276 搭建 5km 远距离 LoRa 传输链路》,敬请期待。