💡 划重点

  • 本项目用 ESP32 做主控,BME280 采集温度、湿度、气压,GPIO25 输出 PWM 驱动小风扇,GPIO2 做心跳灯,板内运行本地 Web Server。手机连同一 Wi-Fi 后打开网页,即可查看实时气象数据、最近记录,并手动或自动调节风扇转速。整套物料控制在 70 元内,适合阳台、花园和小型种植箱。

需求洞察

目标用户是阳台养花、阳台晾晒、家庭菜园和小型温室的爱好者。他们真正关心的不是“气象数据上云”,而是三件事:当前阳台是否闷热高湿、需不需要立刻通风、历史变化有没有异常。成品气象站往往价格高、数据必须依赖厂商云、风扇联动另购控制器,坏了也难修。自己搭一套本地网页气象站,数据留在局域网内,手机浏览器直接看,风扇能按阈值自动启动,成本低且可二次开发。它值得做,是因为把“感知—判断—执行—查看”压缩到一块 ESP32 上,既解决实际通风问题,又是很好的硬件编程练手项目。

方案设计

感知层由 BME280 通过 I2C 读取温度、湿度、气压,采样周期 2 秒,连续采样 5 次取中位数,减少阳台热辐射造成的抖动。控制层在 ESP32 内实现阈值判断和回差控制:温度高于 30℃或湿度高于 75% 时启动风扇,温度低于 28℃且湿度低于 70% 时停止,避免临界点频繁启停;同时保留网页手动 PWM 覆盖。执行层用 GPIO25 输出 25 kHz PWM,经 MOS 管驱动 5V 小风扇,低占空比起步实现软启动。日志/联网层采用 STA 优先、失败转 AP 的方式:连接家庭路由后提供 http://设备IP/ 页面,页面展示实时值、风扇状态和最近 20 条记录;数据保存在内存环形缓冲中,也可扩展为向局域网 HTTP 服务上报 JSON。GPIO2 心跳灯以 1 秒闪烁表示主循环正常。

物料清单

物料 数量 用途 估算单价
ESP32 DevKit 开发板 1 主控与 Web Server 22 元
BME280 模块 1 温湿度气压采集 12 元
5V 小风扇 1 通风执行 9 元
AO3400 MOS 管 1 风扇 PWM 驱动 1.5 元
1N5819 二极管 1 风扇续流保护 0.5 元
电阻、LED、杜邦线 1 套 限流、指示、接线 6 元
5V 2A 电源 1 系统供电 12 元
旧塑料盒 1 外壳 0 元
合计 63 元

接线与结构

BME280 使用 3.3V 供电,SDA 接 GPIO21,SCL 接 GPIO22,GND 共地。风扇 PWM 信号从 GPIO25 经 1 kΩ 电阻接 AO3400 栅极,源极接地,漏极接风扇负极,风扇正极接 5V,并在风扇两端反并联 1N5819。LED 心跳灯从 GPIO2 串 330 Ω 电阻到 GND。给定接线可直接使用,无需改脚;唯一调整是风扇不能由 GPIO 直驱,必须加 MOS 管,否则启动电流会拉低 ESP32。结构上把 BME280 放在外壳通风处,远离风扇出风和电源发热区;风扇对准百叶窗或通风孔,ESP32 与电源分仓固定。

核心代码

以下为 Arduino 骨架,按 ESP32 Arduino Core 2.x 编写,需安装 Adafruit BME280 库。


#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_BME280.h>

const char* SSID = "你的WiFi";
const char* PASS = "你的密码";
const int PIN_FAN = 25, PIN_LED = 2;
WebServer server(80);
Adafruit_BME280 bme;
float temp = NAN, hum = NAN, pres = NAN;
int manualDuty = -1;          // -1 表示自动
bool fanOn = false;
unsigned long lastSample = 0, lastBlink = 0;
bool ledState = false;

void readSensors() {
  temp = bme.readTemperature();
  hum = bme.readHumidity();
  pres = bme.readPressure() / 100.0F;
}

void controlFan() {
  int duty;
  if (manualDuty >= 0) {
    duty = manualDuty;
  } else {
    if (!fanOn && (temp > 30 || hum > 75)) fanOn = true;
    if (fanOn && temp < 28 && hum < 70) fanOn = false;
    duty = fanOn ? 180 : 0;   // 8 位 PWM,约 70%
  }
  ledcWrite(0, duty);
}

void handleRoot() {
  String html = "<!doctype html><meta charset='utf-8'><title>阳台气象站</title>";
  html += "<h2>小型气象站</h2><p>温度:" + String(temp,1) + " ℃<br>湿度:" +
          String(hum,1) + " %<br>气压:" + String(pres,1) + " hPa</p>";
  html += "<p>风扇:" + String(fanOn ? "自动运行" : "停止") + "</p>";
  html += "<input type='range' min='0' max='255' value='180' oninput='fetch(\"/fan?duty=\"+this.value)'>";
  html += "<p><a href='/api/data'>JSON 数据</a></p>";
  server.send(200, "text/html; charset=utf-8", html);
}

void handleData() {
  String json = "{\"t\":" + String(temp,1) + ",\"h\":" + String(hum,1) +
                ",\"p\":" + String(pres,1) + ",\"fan\":" + String(fanOn ? 1 : 0) + "}";
  server.send(200, "application/json", json);
}

void handleFan() {
  if (server.hasArg("duty")) manualDuty = constrain(server.arg("duty").toInt(), 0, 255);
  server.send(200, "text/plain", "ok");
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_LED, OUTPUT);
  Wire.begin(21, 22);
  if (!bme.begin(0x76)) Serial.println("BME280 未找到,检查地址 0x76/0x77");
  ledcSetup(0, 25000, 8);
  ledcAttachPin(PIN_FAN, 0);
  WiFi.begin(SSID, PASS);
  unsigned long t0 = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - t0 < 10000) delay(300);
  if (WiFi.status() != WL_CONNECTED) {
  } else Serial.println(WiFi.localIP());
  server.on("/", handleRoot);
  server.on("/api/data", handleData);
  server.on("/fan", handleFan);
  server.begin();
}

void loop() {
  server.handleClient();
  if (millis() - lastSample > 2000) {
    lastSample = millis();
    readSensors();
    controlFan();
  }
  if (millis() - lastBlink > 1000) {
    lastBlink = millis();
    ledState = !ledState;
  }
}

调试步骤

先只接 BME280,用 I2C 扫描确认地址为 0x76 或 0x77,串口打印温湿度是否随呼吸变化。再接 LED 和风扇,先用固定 ledcWrite(0, 80) 测试风扇能否低速启动;若不能,提高到 120 并检查 MOS 管方向。随后连接 Wi-Fi,串口查看 IP,手机浏览器访问根路径,确认页面和 /api/data 的 JSON 正常。最后用吹风机低温档靠近传感器,观察温度上升后风扇是否在 30℃ 阈值启动,并测试网页滑条能否覆盖自动控制。外壳合盖后连续运行 2 小时,检查电源发热和传感器是否受风扇气流影响。

设计图

小型气象站上报网页结构flow
1
感知输入
→
2
控制处理
→
3
执行反馈
→
4
数据上报