
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
实物摆放提示:
- 将 ESP32 竖插在面包板中央长槽两侧;
- DHT22 放置在面包板左侧,栅格孔位方便上拉电阻的跨接;
- OLED 屏插在右侧,I2C 走线尽量短,避免长线干扰;
- DHT22 探头面朝外,远离 ESP32 的散热区(Wi-Fi 模组会轻微发热,影响测温)。
四、完整代码(Arduino 框架)
4.1 库依赖
在 Arduino IDE 中安装以下库:
DHT sensor library(Adafruit)Adafruit Unified SensorAdafruit SSD1306PubSubClient(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 烧录步骤
- Arduino IDE →
工具→ 开发板选 ESP32 Dev Module; - 端口选择对应的 COM 口;
- 波特率 115200,点击上传;
- 打开串口监视器,观察
[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 推荐调试工具
- MQTTBox 或 MQTTX:桌面端订阅调试;
- 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 等,探索更多元的物联网场景。