
ESP32 智能环境监测站实战教程:温湿度采集 + OLED 显示 + MQTT 上云
一、项目背景
在物联网(IoT)应用场景中,环境监测是最常见的需求之一。无论是家庭温室、智能家居、机房温控,还是工业仓储的温湿度报警系统,都离不开一套稳定、低成本、易扩展的数据采集终端。
本项目基于 ESP32 主控,搭配 DHT22 温湿度传感器 和 0.96 寸 SSD1306 OLED 显示屏,实现本地实时显示 + 远程 MQTT 上传。ESP32 凭借双核 240MHz 主频、内置 Wi-Fi/BLE、丰富 GPIO 以及超低功耗模式,成为此类监测节点的理想选择。
通过本教程,你将学会:
- ESP32 与 DHT22 的单总线通信
- I2C 总线驱动 OLED 屏
- Wi-Fi 连接与 MQTT 协议发布
- 多任务协作(FreeRTOS 思路)
整个项目硬件成本约 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 实物连接描述
想象你手中的面包板横向长、纵向短:
- 左侧电源轨:红色线接入 ESP32 的 3V3 引脚,蓝色线接入 GND。整条电源轨即 3.3V 公共端。
- DHT22 放置:将 DHT22 插在面包板中间区域,三个引脚朝下。从左到右依次为 `VCC`、`DATA`、`NC`、`GND`(DHT22 通常四脚,第二个脚为数据,第三个悬空)。
- 上拉电阻:10KΩ 电阻一端接 DHT22 的 DATA 脚,另一端接到 3.3V 电源轨。电阻紧贴传感器焊接或直接插在同列插孔。
- 杜邦线连接:用公-母线从 DHT22 的 DATA 脚接到 ESP32 的 GPIO 4。
- OLED 屏:四个排针依次插入面包板,GND 接蓝线,VCC 接红线,SDA 接 GPIO 21,SCL 接 GPIO 22。
- 整体布局:ESP32 放在面包板上方,传感器与屏幕分列左右,避免相互遮挡。
接线完成后,检查是否有短路。建议先用万用表通断档确认 3V3 与 GND 之间电阻不为零。
---
四、完整代码(Arduino IDE)
4.1 准备工作
- Arduino IDE 安装 ESP32 核心(开发板管理器搜索 `esp32`,安装 2.x 版本)。
- 库管理器依次安装:
- `DHT sensor library`(Adafruit)
- `Adafruit Unified Sensor`
- `Adafruit SSD1306`
- `PubSubClient`(Nick O'Leary)
- 修改代码顶部的 `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 扫描工具确认。
---
五、调试步骤
- 串口监视器:打开 Arduino IDE 串口监视器,波特率 115200,观察 `[WIFI]`、`[MQTT]` 日志。
- MQTT 客户端验证:用电脑或手机安装 MQTTX / MQTTool,连接同一 Broker,订阅主题 `home/room/#`,即可看到实时数据。
- OLED 无显示:
- 确认 I2C 地址(I2C Scanner 扫描 0x3C 或 0x3D)。
- 检查 SDA/SCL 是否接反。
- 测量 3.3V 电源是否正常。
- DHT22 一直返回 NaN:
- 确认上拉电阻已焊接。
- 数据线过长(>20cm)建议加屏蔽或换更短接线。
- 连续读取至少间隔 2 秒,否则传感器会响应失败。
- MQTT 连接失败 `rc=-2`:网络不通,检查 Wi-Fi 与外网访问;`rc=5` 通常是 Broker 鉴权问题。
- 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 双核任务调度与底层驱动模型,这会让你对嵌入式开发有更深的认知。祝玩得开心!