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 编译与烧录
- 工具 → 开发板 → **ESP32 Dev Module**
- 端口选择对应的 COM 口
- 上传速度建议 921600
- 点击"上传",等待烧录完成
- 打开串口监视器(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 三个独立任务,让系统更健壮。祝你玩得开心!