Let me plan the content:
- Project: Smart Environment Monitoring Station
- Hardware: ESP32 DevKit, DHT22, SSD1306 OLED, breadboard, jumper wires
- Wiring with specific GPIO numbers
- Complete Arduino code with DHT22, OLED, WiFi, MQTT
- Debugging section
- Extensions: 1) Add battery + deep sleep 2) Add multiple sensors (BMP280, MQ-135) 3) Add local web server fallback
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 优势明显:
- 内置 Wi-Fi / Bluetooth,省去外挂模块
- 双核 240MHz,Xtensa LX6 架构
- 丰富的外设接口(I2C、SPI、UART、ADC、PWM)
- 支持深度睡眠,电池供电可达数月
**项目核心指标:**
- 温度精度:±0.5℃(DHT22)
- 湿度精度:±2% RH
- 上报频率:可配置(默认 5s)
- 显示方式:本地 0.96" OLED + 远程 MQTT 双通道
---
二、硬件清单
| 序号 | 元器件 | 型号 | 数量 | 参考价格 |
|---|---|---|---|---|
| 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
└──────────┘
关键注意事项
- **DHT22 上拉电阻**:DATA 线空闲时为高电平,4.7kΩ 上拉电阻必须接在 DATA 与 VCC 之间(部分模块已板载)。
- **I2C 总线长度**:面包板跳线建议 < 20cm,过长会因上拉不足导致显示花屏。
- **电源去耦**: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 传输链路》,敬请期待。