ESP32 实战 · 2026-06-09 配图
AI Know 自有配图 · 嵌入式 · IoT · 硬件编程

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 有源蜂鸣器,低电平触发

实物连接步骤

  1. DHT22 接线:将传感器的 VCC 接 3V3,GND 接 GND,DATA 接 GPIO4,并在 DATA 与 VCC 之间焊接 4.7kΩ 上拉电阻;若使用成品模块则已板上集成。
  2. OLED 接线:四线制 I2C,VCC 接 3V3,GND 接 GND,SDA 接 GPIO21,SCL 接 GPIO22。屏幕默认地址 0x3C。
  3. MQ-2 接线:VCC 接 5V(注意 ESP32 供电为 3.3V,需保证模块自带稳压),GND 接 GND,AO 模拟输出接 GPIO34。
  4. 电源:使用 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 libraryAdafruit Unified SensorAdafruit SSD1306PubSubClient。以下代码可直接编译烧录。


#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 元的成本,覆盖了嵌入式开发者必须掌握的总线通信、网络协议、人机交互三大能力。读者可在此基础上继续深耕,将监测站升级为真正的“环境智能终端”,为简历添加一个亮眼的实战项目。