
ESP32 智能环境监测站实战教程
一、项目背景
随着物联网技术在智慧家居与工业场景中的深度落地,环境数据的实时采集与远程可视化已成为嵌入式开发的基础能力。本项目以 ESP32-DevKitC 为核心控制器,外接 DHT22 温湿度传感器、MQ-2 气体传感器与 0.96 寸 OLED 显示屏,构建一套可独立运行的环境监测节点。设备通过 WiFi 接入局域网后,以 MQTT 协议将数据推送至云端 Broker,配合 Home Assistant、Node-RED 或自建 EMQX 服务即可实现手机端实时告警与历史曲线回看。整个项目涉及 GPIO 编程、I2C 总线、ADC 采集、FreeRTOS 多任务以及网络协议栈,是嵌入式开发者从单机裸机向物联网进阶的经典练手案例。
二、硬件清单
| 序号 | 元器件 | 型号规格 | 数量 | 参考单价 |
|---|---|---|---|---|
| 1 | 主控板 | ESP32-DevKitC v1(CP2102 或 CH340 版本均可) | 1 | 25 元 |
| 2 | 温湿度传感器 | DHT22(AM2302) | 1 | 9 元 |
| 3 | 显示屏 | 0.96 寸 SSD1306 OLED(I2C 接口,128×64) | 1 | 12 元 |
| 4 | 气体传感器 | MQ-2 可燃气体/烟雾模块(模拟输出) | 1 | 6 元 |
| 5 | 面包板 | 400 孔大面包板 | 1 | 5 元 |
| 6 | 杜邦线 | 公-公、公-母 各 20 根 | 1 套 | 4 元 |
| 7 | 数据线 | Micro-USB 或 Type-C(视开发板型号) | 1 | 3 元 |
三、接线图说明
本项目实物布局采用“一”字型排布,主控板居中放置,传感器分布在两侧,方便理线。严禁带电插拔杜邦线,所有接线需在断电状态下完成。
GPIO 资源分配
| 模块 | 信号线 | ESP32 引脚 | 说明 |
|---|---|---|---|
| DHT22 | DATA | GPIO4 | 单总线协议,需 4.7kΩ 上拉电阻 |
| OLED | SDA | GPIO21 | I2C0 数据线 |
| OLED | SCL | GPIO22 | I2C0 时钟线 |
| MQ-2 | AO | GPIO34 | ADC1_CH6,仅可输入 |
| 状态 LED | — | GPIO2 | 板载蓝色 LED |
| 蜂鸣器(可选) | I/O | GPIO25 | 有源蜂鸣器,低电平触发 |
实物连接步骤
- DHT22 接线:将传感器的 VCC 接 3V3,GND 接 GND,DATA 接 GPIO4,并在 DATA 与 VCC 之间焊接 4.7kΩ 上拉电阻;若使用成品模块则已板上集成。
- OLED 接线:四线制 I2C,VCC 接 3V3,GND 接 GND,SDA 接 GPIO21,SCL 接 GPIO22。屏幕默认地址 0x3C。
- MQ-2 接线:VCC 接 5V(注意 ESP32 供电为 3.3V,需保证模块自带稳压),GND 接 GND,AO 模拟输出接 GPIO34。
- 电源:使用 Micro-USB 连接电脑或 5V/1A 充电器;板载稳压芯片 AMS1117-3.3 会将电压降至 3.3V。
提示:GPIO34、35、36、39 只能作为输入引脚,本项目用 34 读取 MQ-2 模拟电压是正确选择;切勿用作输出。
四、完整代码(Arduino 框架)
在 Arduino IDE 中安装 ESP32 内核(Tools → Board → esp32 → ESP32 Dev Module),并通过库管理器安装 DHT sensor library、Adafruit Unified Sensor、Adafruit SSD1306、PubSubClient。以下代码可直接编译烧录。
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <Adafruit_SSD1306.h>
// ============== 配置区 ==============
const char* WIFI_SSID = "your_ssid";
const char* WIFI_PASS = "your_password";
const char* MQTT_HOST = "broker.emqx.io";
const int MQTT_PORT = 1883;
const char* MQTT_TOPIC = "home/env/monitor1";
const char* MQTT_USER = "";
const char* MQTT_PASS = "";
// ============== 硬件引脚 ==============
#define DHT_PIN 4
#define DHT_TYPE DHT22
#define MQ2_PIN 34
#define LED_PIN 2
#define BUZZER_PIN 25
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
DHT dht(DHT_PIN, DHT_TYPE);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
WiFiClient espClient;
PubSubClient mqtt(espClient);
unsigned long lastPublish = 0;
const unsigned long PUBLISH_INTERVAL = 5000;
void connectWiFi() {
Serial.printf("WiFi 连接中: %s", WIFI_SSID);
while (WiFi.status() != WL_CONNECTED) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
delay(300);
Serial.print('.');
}
Serial.println("\nWiFi 已连接 IP: " + WiFi.localIP().toString());
digitalWrite(LED_PIN, HIGH);
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("MQTT 握手...");
String clientId = "esp32-" + String(random(0xffff), HEX);
if (mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) {
Serial.println("已连接");
} else {
Serial.print("失败 rc=");
Serial.print(mqtt.state());
Serial.println(" 5 秒后重试");
delay(5000);
}
}
}
void publishData(float t, float h, int gas) {
char payload[256];
"{\"device\":\"env-monitor-1\",\"temp\":%.1f,\"humi\":%.1f,\"gas\":%d,\"rssi\":%d}",
t, h, gas, WiFi.RSSI());
Serial.println("已发布: " + String(payload));
}
void updateOLED(float t, float h, int gas) {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.println("== Env Monitor ==");
display.setTextSize(2);
display.setCursor(0, 16);
display.printf("T:%.1fC", t);
display.setCursor(0, 36);
display.printf("H:%.1f%%", h);
display.setTextSize(1);
display.setCursor(0, 56);
display.printf("Gas:%d WiFi:%d", gas, WiFi.RSSI());
display.display();
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, HIGH); // 关闭蜂鸣器
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED 初始化失败");
while (true);
}
display.clearDisplay();
display.display();
dht.begin();
connectWiFi();
connectMQTT();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWiFi();
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
unsigned long now = millis();
if (now - lastPublish >= PUBLISH_INTERVAL) {
lastPublish = now;
float t = dht.readTemperature();
float h = dht.readHumidity();
int gas = analogRead(MQ2_PIN);
if (isnan(t) || isnan(h)) {
Serial.println("DHT22 读取异常");
return;
}
updateOLED(t, h, gas);
publishData(t, h, gas);
if (gas > 2500 || t > 50.0) {
digitalWrite(BUZZER_PIN, LOW); // 触发蜂鸣器
} else {
}
}
}
烧录时选择正确的 COM 端口与 4MB Flash 分区方案即可。
五、调试指南
1. 串口无输出:确认波特率为 115200;ESP32 部分开发板需按住 BOOT 键再按 EN 键进入下载模式。
2. DHT22 一直返回 NaN:检查 GPIO4 上拉电阻是否缺失;DHT22 采样间隔需 ≥ 2 秒;数据线过长会引入干扰,建议控制在 20cm 以内。
3. OLED 仅显示白屏或花屏:99% 是 I2C 地址错误,可运行 I2C 扫描例程确认;地址通常为 0x3C,若检测到 0x3D 需修改 display.begin 第二参数。
4. MQTT 反复重连:在 mqtt.state() 返回值中,-2 表示网络不通,-4 表示认证失败;公共 Broker broker.emqx.io 在国内可能不稳定,可自建 Mosquitto 或使用 OneNET、阿里云 IoT 平台。
5. 传感器数据跳变剧烈:MQ-2 在通电后需预热 20 秒以上;ADC 读数可做滑动平均滤波,例如保留最近 8 次采样取均值。
6. 看门狗触发:若在 loop 中加入 delay(1000) 且未使用 FreeRTOS 任务,ESP32 会触发 Task Watchdog,建议改用 millis() 计时方案,本项目代码已采用。
六、进阶方向
方向一:低功耗电池供电改造
引入 Deep-Sleep 模式,使用 esp_deep_sleep_start() 在两次采集之间休眠,将平均电流降至 0.8mA 以下;外接 18650 电池配合 TP4056 充电板可实现数月续航。同时将 MQTT 替换为 LoRaWAN 或 NB-IoT,扩展到农业大棚、地下管廊等无 WiFi 场景。
方向二:边缘计算与本地决策
在 ESP32 上部署轻量级 TensorFlow Lite Micro 模型,基于历史温湿度数据训练一个“异常模式识别”分类器。当模型判定“有人在室内抽烟”或“室温骤降”这类事件时,本地即可触发蜂鸣器与继电器控制空调,无需依赖云端往返,提升响应速度至 100ms 内。
方向三:多节点 Mesh 组网
使用 ESP-MESH 或 ESP-NOW 协议,将多个监测节点组成自愈型 Mesh 网络。网关节点(Root)汇总所有子节点数据并统一上报云端;任意节点掉线不会影响整体拓扑,可轻松部署在工厂车间、温室大棚等大范围场景。
本项目以不到 70 元的成本,覆盖了嵌入式开发者必须掌握的总线通信、网络协议、人机交互三大能力。读者可在此基础上继续深耕,将监测站升级为真正的“环境智能终端”,为简历添加一个亮眼的实战项目。