💡 划重点
- 本项目用 ESP32 做一个小型气象站:BME280 采集温度、湿度、气压,ESP32 运行本地 Web Server,手机连同一 Wi-Fi 后打开网页即可查看数据,并能通过 PWM 调节小风扇通风除湿。整机目标成本控制在 70 元内,适合阳台、花园等半户外场景。核心特色是“低门槛、可上报、可执行”:不只显示数据,还能根据湿度远程开风扇,形成感知—控制—反
需求洞察
目标用户是喜欢养花、种菜或关注阳台微气候的普通爱好者,也包括需要低成本环境监测的创客学生。真实痛点有三类:普通温湿度计只能看本地读数,不能远程查看;市售气象站价格高、封闭、难扩展;阳台昼夜温差和湿度变化大,植物易闷根或冻伤,用户希望及时通风。做一个百元内、能网页查看、能控制风扇的小型气象站,既有实用价值,也是学习 ESP32、I2C、PWM 和 Web Server 的完整项目,值得作为入门到进阶的硬件编程实践。
方案设计
感知层使用 BME280,通过 I2C 读取温度、湿度、气压,精度和稳定性适合室内外微气候。控制层由 ESP32 负责采样、滤波、阈值判断和网页服务,采用非阻塞心跳灯提示运行状态。执行层用 GPIO25 输出 PWM,经 MOS 管低边驱动 5V 小风扇,占空比可网页调节,用于除湿和空气循环。日志/联网层连接家庭 Wi-Fi,启动 HTTP 服务,网页展示实时数据、风扇档位和刷新时间;后续可扩展为定时上报到局域网服务器或 MQTT。整机采用 5V USB 供电,结构上注意 BME280 远离风扇和发热件,保证测量代表性。
物料清单
| 物料 | 数量 | 用途 | 估算单价 | 小计 |
|---|---|---|---|---|
| ESP32 开发板 | 1 | 主控与 Web Server | 25 元 | 25 元 |
| BME280 模块 | 1 | 温湿度气压采集 | 12 元 | 12 元 |
| 5V 小风扇 | 1 | 通风除湿 | 8 元 | 8 元 |
| 2N7002/MOS 管 | 1 | 风扇 PWM 驱动 | 1 元 | 1 元 |
| 1kΩ 电阻 | 1 | MOS 栅极限流 | 0.5 元 | 0.5 元 |
| LED 与 220Ω 电阻 | 1 套 | 心跳指示 | 0.5 元 | 0.5 元 |
| 1N4148 二极管 | 1 | 风扇续流保护 | 0.5 元 | 0.5 元 |
| 面包板与杜邦线 | 1 套 | 快速接线 | 6 元 | 6 元 |
| 5V USB 电源 | 1 | 整机供电 | 10 元 | 10 元 |
| 防水盒/扎带 | 1 套 | 阳台安装 | 4 元 | 4 元 |
| 合计 | 67.5 元 |
接线与结构
BME280 接 3.3V、GND,SDA 接 GPIO21,SCL 接 GPIO22;LED 经 220Ω 接 GPIO2 到 GND;风扇 PWM 信号从 GPIO25 经 1kΩ 电阻接 MOS 管栅极,源极接地,漏极接风扇负极,风扇正极接 5V,并在风扇两端反并联 1N4148。GPIO25 只输出 PWM 信号,不直接带风扇,避免过流损坏。结构上把 ESP32 与电源放入防水盒,BME280 靠近百叶通风口且远离风扇出风口,风扇向外抽风。若风扇电流较大,建议独立 5V 电源并与 ESP32 共地;本方案小风扇可由 USB 电源共用。
核心代码
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
Adafruit_BME280 bme;
WebServer server(80);
const char* SSID = "你的WiFi";
const char* PASS = "你的密码";
const int FAN_PIN = 25, LED_PIN = 2;
int fanDuty = 0;
unsigned long lastBlink = 0;
bool ledOn = false;
void handleRoot() {
float t = bme.readTemperature();
float h = bme.readHumidity();
float p = bme.readPressure() / 100.0F;
String html = "<!doctype html><meta charset='utf-8'><h2>小型气象站</h2>";
html += "<p>温度:" + String(t, 1) + " ℃</p>";
html += "<p>湿度:" + String(h, 1) + " %</p>";
html += "<p>气压:" + String(p, 1) + " hPa</p>";
html += "<p>风扇PWM:" + String(fanDuty) + "</p>";
html += "<p><a href='/fan?duty=0'>关风扇</a> ";
html += "<a href='/fan?duty=128'>中档</a> ";
html += "<a href='/fan?duty=255'>全速</a></p>";
server.send(200, "text/html; charset=utf-8", html);
}
void handleFan() {
if (server.hasArg("duty")) {
fanDuty = constrain(server.arg("duty").toInt(), 0, 255);
ledcWrite(0, fanDuty);
}
server.sendHeader("Location", "/");
server.send(303);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
Wire.begin(21, 22);
if (!bme.begin(0x76)) Serial.println("BME280未找到");
ledcSetup(0, 25000, 8);
ledcAttachPin(FAN_PIN, 0);
ledcWrite(0, 0);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
server.on("/", handleRoot);
server.on("/fan", handleFan);
server.begin();
}
void loop() {
server.handleClient();
if (millis() - lastBlink > 1000) {
lastBlink = millis();
ledOn = !ledOn;
digitalWrite(LED_PIN, ledOn);
}
}
调试步骤
先只接 BME280,用 I2C 扫描确认地址为 0x76,串口观察温湿度气压是否合理;再接入 LED,确认每秒心跳闪烁;随后接 MOS 管和风扇,网页分别切换 0、128、255,观察转速与电流,确保 MOS 管不发热;然后连接 Wi-Fi,串口记录 IP,手机浏览器访问并测试刷新;最后放入阳台外壳,连续运行 24 小时,校准湿度阈值和风扇档位。若网页打不开,先检查手机与 ESP32 是否同一网段;若数据异常,检查 BME280 是否被风扇热风直吹。