需求洞察
你有没有过这种体验:冬天关窗开空调,写代码两小时就开始打哈欠、脑子转不动,以为是自己"困了",其实多半是 CO₂ 浓度从 600ppm 飙到了 1500ppm+;夏天 35°C 闷热得不行,但拿温度计一量却只有 28°C——是湿度 80% 让体感失真;早晨觉得空气"挺清新的",出门才知道 PM2.5 已经 150。空气不会说话,但你的身体一直在用疲劳、头晕、打喷嚏"回应"。
市面上的空气检测仪普遍不便宜:小米米家空气检测仪 ¥349,青萍空气检测仪 ¥599,霍尼韦尔 PM2.5 检测仪 ¥899。对于一个"就想知道桌面 PM2.5 多少"的极客来说,价格离谱。而 DIY 一个简易的桌面空气质量监测站,BOM 物料 122 元就够,而且可以二次开发(阈值、报警逻辑、显示内容都在 Arduino 代码里改)。
本项目的目标是用 ¥122 的物料,做出一个能实时显示温度、湿度、PM1.0、PM2.5、PM10 五项数据,并在异常时通过 LED + 蜂鸣器报警的桌面空气监测站——所有数据本地化,零云端依赖,零隐私泄露。
核心痛点
- 关窗开空调 CO₂ 飙升无感:人体对 CO₂ 不敏感,1500ppm 就开始打哈欠,2500ppm 头晕,5000ppm 直接不适
- 夏闷热体感失真:35°C 但湿度 80% 时体感温度会到 40°C+,不知道湿度就不知道为啥"闷"
- PM2.5 看不见:开窗半小时从 35 涨到 150,但你只看到窗外"有点灰"——数据才是真相
- 智能检测仪贵 + 强制云端:米家/青萍/霍尼韦尔普遍 300+,且数据强制上传云端,隐私堪忧
方案设计
本项目采用「感知 + 主控 + 显示 + 反馈」四件套架构,以 ESP32 为中心,外围 4 个模块各司其职:
| 模块 | 型号 | 接口 | 功能 |
|---|---|---|---|
| 主控 | ESP32 DevKit V1 | — | 双核 240MHz,34 个 GPIO,3.3V 逻辑 |
| 温湿度 | DHT22(AM2302) | 单总线 GPIO 4 | -40~80°C ±0.5°C,0~100%RH ±2% |
| PM2.5 | PMS5003(G7 激光) | UART2 GPIO 16/17 | 0~1000 μg/m³ 激光散射,国产检测仪同款 |
| 显示 | SSD1306 OLED 0.96" | I2C GPIO 21/22 | 128x64 像素,白色,实时显示 4 项数据 |
| 报警 | 三色 LED 模块 + 有源蜂鸣器 | GPIO 25/26/27 + 12 | 3 级状态指示(绿/黄/红)+ 声光报警 |
三级报警阈值(参考国标 GB/T 18801)
| 等级 | 触发条件 | LED 状态 | 蜂鸣器 | 用户感知 |
|---|---|---|---|---|
| 🟢 正常 | PM2.5 ≤ 35 且 温湿正常 | 绿色常亮 | 静音 | 安心工作 |
| 🟡 警告 | 温/湿异常 或 PM2.5 35-75 | 黄色常亮 | 静音 | 开窗/加湿 |
| 🔴 报警 | PM2.5 > 75 | 红色闪烁 | 2kHz 间歇响 | 立刻开窗 + 戴口罩 |
阈值定义:温湿异常 = 温度 > 30°C 或 < 16°C,或 湿度 > 70% 或 < 30%;PM2.5 阈值参考国标 GB/T 18801-2022 空气质量分指数:≤35 为优,35-75 为良,>75 为轻度污染需关注。CO₂ 监测本项目暂不接入(专业 CO₂ 传感器 SCD30 单价 ¥80+,本方案用 PM2.5 间接反映通风状况——PM2.5 升高通常意味着关窗/通风差,CO₂ 往往同步升高)。
技术亮点
- 单总线省引脚:DHT22 用 1 根 GPIO 测温湿度,比 I2C 传感器省协议开销
- UART2 高速传输:PMS5003 用硬件 Serial2(9600 波特率)传输 32 字节数据帧
- I2C 总线复用:OLED 单挂 GPIO 21/22,未来加 SCD30/BMP280 也能直接接
- 被动模式省功耗:PMS5003 切被动模式,ESP32 主动 wakeup 读取,避免持续上报
- 全本地数据:无 WiFi 依赖,无云端上传,零隐私泄露
物料清单
总成本 ¥122,比小米米家空气检测仪便宜 65%。所有物料在淘宝/嘉立创都能买到,BOM 9 项:
| 数量 | 物料 | 单价 | 小计 |
|---|---|---|---|
| 1 | ESP32 DevKit V1(Type-C 口,30 针版) | ¥30 | ¥30 |
| 1 | DHT22 温湿度传感器(AM2302,单总线) | ¥10 | ¥10 |
| 1 | PMS5003 激光 PM2.5 传感器(G7 模组,含 1m 线) | ¥40 | ¥40 |
| 1 | SSD1306 OLED 0.96 寸显示屏(I2C 4 针版) | ¥12 | ¥12 |
| 1 | 三色 LED 模块(共阴极,3 GPIO 控制) | ¥3 | ¥3 |
| 1 | 有源蜂鸣器模块(3.3V/5V 通用,100mA) | ¥2 | ¥2 |
| 1 包 | 杜邦线(公-公 40 根 + 公-母 40 根) | ¥5 | ¥5 |
| 1 | 面包板 400 点(SYB-170) | ¥5 | ¥5 |
| 1 | 5V 3A 电源适配器(DC 5.5x2.1 母头,PMS5003 专用) | ¥15 | ¥15 |
| 总计 | ¥122 | ||
采购建议:
- ESP32 选 Type-C 口的 DevKit V1 改进版(丝印清楚),不要买 Micro-USB 旧版(Type-A 焊接麻烦)
- DHT22 选带小板的版本(带 10kΩ 上拉电阻),裸传感器接 ESP32 信号线不稳
- PMS5003 选带 1m 连接线的版本(默认出货是裸板,自己焊线麻烦),确认有"G7"或"G5"标识(激光散射)
- OLED 选 0.96 寸 4 针 I2C 版(VCC/GND/SCL/SDA),不要买 7 针 SPI 版(接线复杂 2 倍)
- 三色 LED 模块选共阴极(低电平点亮)—本项目用低电平触发更好配 ESP32;选错极性灯不亮
接线与结构
按 ESP32 DevKit V1 的丝印实际位置说明(不是抽象 GPIO 编号,避免新手对着丝印找不着脚):
ESP32 DevKit V1 排针位置参考
- 左侧排针(从下到上):VIN / GND / D13 / D12 / D14 / D27 / D26 / D25 / D33 / D32 / D35 / D34 / VN / IO / VP / EN
- 右侧排针(从下到上):3V3 / GND / D15 / D2 / D4 / RX2 / TX2 / D5 / D18 / D19 / D21 / RX0 / TX0 / D22 / D23
完整接线清单(带颜色 + 丝印)
| 模块 | 引脚 | 颜色 | 连接目标 | 丝印位置 |
|---|---|---|---|---|
| DHT22 | DATA(数据) | 🟢 绿线 | ESP32 GPIO 4 | 右侧排针第 5 脚,标 D4 |
| DHT22 | VCC(电源) | 🔴 红线 | ESP32 3.3V | 右侧排针第 1 脚,标 3V3 |
| DHT22 | GND(地) | ⚫ 黑线 | ESP32 GND | 右侧排针第 2 脚 |
| PMS5003 | TX(数据输出) | 🟢 绿线 | ESP32 GPIO 16(RX2) | 右侧排针第 6 脚,标 RX2 |
| PMS5003 | RX(数据输入) | 🟡 黄线 | ESP32 GPIO 17(TX2) | 右侧排针第 7 脚,标 TX2 |
| PMS5003 | VCC(电源) | 🔴 红线 | 外部 5V 电源正极 | DC 5.5x2.1 母头内芯(务必 5V,不能 3.3V) |
| PMS5003 | GND(地) | ⚫ 黑线 | ESP32 GND + 外部 5V 电源负极 | ESP32 右侧排针第 2 脚;务必共地! |
| OLED 0.96" | VCC(电源) | 🔴 红线 | ESP32 3.3V | 右侧排针第 1 脚,标 3V3 |
| OLED 0.96" | GND(地) | ⚫ 黑线 | ESP32 GND | 右侧排针第 2 脚 |
| OLED 0.96" | SDA(数据) | 🟡 黄线 | ESP32 GPIO 21 | 右侧排针第 11 脚,标 D21 |
| OLED 0.96" | SCL(时钟) | ⚪ 白线 | ESP32 GPIO 22 | 右侧排针第 14 脚,标 D22 |
| 三色 LED 模块 | R(红色) | 🔴 红线 | ESP32 GPIO 25 | 左侧排针第 8 脚,标 D25 |
| 三色 LED 模块 | Y(黄色) | 🟡 黄线 | ESP32 GPIO 26 | 左侧排针第 7 脚,标 D26 |
| 三色 LED 模块 | G(绿色) | 🟢 绿线 | ESP32 GPIO 27 | 左侧排针第 6 脚,标 D27 |
| 三色 LED 模块 | GND(地) | ⚫ 黑线 | ESP32 GND | 左侧排针第 2 脚 |
| 蜂鸣器 | I/O(信号) | 🟡 黄线 | ESP32 GPIO 12 | 左侧排针第 4 脚,标 D12 |
| 蜂鸣器 | VCC(电源) | 🔴 红线 | ESP32 3.3V | 右侧排针第 1 脚,标 3V3 |
| 蜂鸣器 | GND(地) | ⚫ 黑线 | ESP32 GND | 右侧排针第 2 脚 |
⚠️ 关键提醒(不踩这些坑就别想跑通):
- PMS5003 必须用外部 5V 电源,不能接 ESP32 的 3.3V 或 VIN(VIN 在 USB 供电时只有 4.7V,PMS5003 启动电流 100mA 会拉低电压导致重启)。建议直接用 5V/3A 独立电源适配器
- 务必共地:PMS5003 的 GND 必须和 ESP32 的 GND 接在一起,否则 UART 数据会全是乱码(这是 90% 的"读不到数据"故障原因)
- 三色 LED 模块有极性:买之前确认是"共阴极"(GND 共用,3 根信号线高电平点亮)还是"共阳极"(VCC 共用,3 根信号线低电平点亮)。本项目按共阴极写代码(共阴极更常见,淘宝 90% 是共阴)
- 蜂鸣器 100mA 电流:ESP32 单 GPIO 只能输出 12mA,直接驱动 100mA 的蜂鸣器会烧 GPIO。建议买带三极管驱动的蜂鸣器模块(带 NPN 三极管 + 限流电阻),直接 I/O 即可。如果用裸蜂鸣器请外加 S8050 三极管
- DHT22 必加 10kΩ 上拉电阻:DHT22 数据线空闲时需上拉到 VCC,淘宝买的 DHT22 小板已焊接好上拉电阻,但裸传感器需自己外加
- PMS5003 进气口朝下或侧放:传感器内部有风扇,垂直进气效果最好;不要遮挡出气口
3D 打印外壳建议(可选)
如果觉得裸板太乱,可以 3D 打印一个 100mm x 80mm 的外壳(PMS5003 单独开孔露出进气口)。嘉立创 3D 打印 ¥30 起。
核心代码
使用 Arduino 框架,需要安装 4 个库:DHT sensor library(Adafruit)、PMS library(mariuszkacki)、Adafruit_SSD1306、Adafruit_GFX。建议 Arduino IDE 2.x + ESP32 core 2.0.14+。
// =============================================================
// 桌面温湿度空气质量监测站
// ESP32 + DHT22 + PMS5003 + OLED + 三色 LED + 蜂鸣器
// 库:Adafruit DHT sensor library / PMS / Adafruit_SSD1306
// =============================================================
#include <DHT.h>
#include <PMS.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ── 引脚定义 ──
#define DHT_PIN 4 // DHT22 数据线(单总线)
#define DHT_TYPE DHT22
#define LED_RED 25 // 红色 LED(PM2.5 超标报警)
#define LED_YELLOW 26 // 黄色 LED(温湿异常警告)
#define LED_GREEN 27 // 绿色 LED(正常)
#define BUZZER 12 // 有源蜂鸣器(高电平触发)
#define PMS_RX 16 // ESP32 RX2 接 PMS5003 TX
#define PMS_TX 17 // ESP32 TX2 接 PMS5003 RX
// ── 报警阈值(参考 GB/T 18801-2022)──
#define PM25_WARN 35.0 // PM2.5 > 35 警告
#define PM25_ALARM 75.0 // PM2.5 > 75 报警
#define TEMP_HIGH 30.0 // 温度 > 30°C 警告
#define TEMP_LOW 16.0 // 温度 < 16°C 警告
#define HUMI_HIGH 70.0 // 湿度 > 70% 警告
#define HUMI_LOW 30.0 // 湿度 < 30% 警告
// ── OLED 配置 ──
#define SCREEN_W 128
#define SCREEN_H 64
#define OLED_ADDR 0x3C
#define OLED_RESET -1
// ── 初始化对象 ──
DHT dht(DHT_PIN, DHT_TYPE);
HardwareSerial pmsSerial(2); // ESP32 硬件串口 2
PMS pms(pmsSerial);
PMS::DATA pmsData;
Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, OLED_RESET);
// ── 全局变量 ──
float temperature = 0.0;
float humidity = 0.0;
float pm1_0 = 0.0;
float pm2_5 = 0.0;
float pm10 = 0.0;
unsigned long lastSample = 0;
const unsigned long SAMPLE_INTERVAL = 2000; // 2 秒采样一次
void setup() {
Serial.begin(115200);
delay(200);
Serial.println(F("=== Air Monitor Booting ==="));
// LED 引脚模式
pinMode(LED_RED, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(BUZZER, OUTPUT);
digitalWrite(LED_RED, LOW);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_GREEN, LOW);
digitalWrite(BUZZER, LOW);
// DHT22 初始化
dht.begin();
// PMS5003 初始化(Serial2, 9600 波特率, RX=GPIO16, TX=GPIO17)
pmsSerial.begin(9600, SERIAL_8N1, PMS_RX, PMS_TX);
pms.passiveMode(); // 切被动模式:按需读取,传感器不会主动上报
delay(1000); // 等待 PMS5003 启动
// OLED 初始化
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("OLED init failed"));
while (true); // 卡住,方便排查
}
display.display(); // 显示 Adafruit logo
delay(1500);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// 开机自检:三色 LED 依次亮
testLeds();
Serial.println(F("=== Boot Complete ==="));
}
void loop() {
unsigned long now = millis();
// 每 2 秒采一次样
if (now - lastSample >= SAMPLE_INTERVAL) {
lastSample = now;
// 1. 读 DHT22 温湿度
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
temperature = t;
humidity = h;
}
// 2. 读 PMS5003(主动 wakeup + 读取)
pms.wakeUp();
delay(30);
if (pms.read(pmsData)) {
pm1_0 = pmsData.PM_AE_UG_1_0;
pm2_5 = pmsData.PM_AE_UG_2_5;
pm10 = pmsData.PM_AE_UG_10_0;
}
pms.sleep();
// 3. OLED 显示
displayData(temperature, humidity, pm1_0, pm2_5, pm10);
// 4. 报警判断
controlAlerts(temperature, humidity, pm2_5);
// 5. 串口打印(方便调试)
Serial.print("T:"); Serial.print(temperature, 1); Serial.print("C ");
Serial.print("H:"); Serial.print(humidity, 0); Serial.print("% ");
Serial.print("PM1.0:"); Serial.print(pm1_0, 0); Serial.print(" ");
Serial.print("PM2.5:"); Serial.print(pm2_5, 0); Serial.print(" ");
Serial.print("PM10:"); Serial.println(pm10, 0);
}
}
// ── 辅助函数:OLED 显示 ──
void displayData(float t, float h, float pm1, float pm25, float pm10v) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// 第 1 行:温度
display.setCursor(0, 0);
display.print(F("Temp : "));
display.print(t, 1);
display.println(F(" C"));
// 第 2 行:湿度
display.setCursor(0, 12);
display.print(F("Humi : "));
display.print(h, 0);
display.println(F(" %"));
// 第 3 行:PM2.5
display.setCursor(0, 24);
display.print(F("PM2.5: "));
display.print(pm25, 0);
display.println(F(" ug/m3"));
// 第 4 行:PM1.0 / PM10
display.setCursor(0, 36);
display.print(F("PM1/10: "));
display.print(pm1, 0);
display.print(F("/"));
display.print(pm10v, 0);
// 底部状态栏
display.setCursor(0, 54);
if (pm25 > PM25_ALARM) {
display.println(F("[!!] ALARM OPEN WINDOW"));
} else if (pm25 > PM25_WARN || t > TEMP_HIGH || t < TEMP_LOW
|| h > HUMI_HIGH || h < HUMI_LOW) {
display.println(F("[~] WARN CHECK AIR"));
} else {
display.println(F("[OK] GOOD WORK HARD"));
}
display.display();
}
// ── 辅助函数:三色 LED + 蜂鸣器报警 ──
void controlAlerts(float t, float h, float pm25) {
// 先关所有灯
digitalWrite(LED_RED, LOW);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_GREEN, LOW);
noTone(BUZZER);
bool pmBad = (pm25 > PM25_ALARM);
bool thBad = (t > TEMP_HIGH || t < TEMP_LOW
|| h > HUMI_HIGH || h < HUMI_LOW);
bool pmWarn = (pm25 > PM25_WARN && pm25 <= PM25_ALARM);
if (pmBad) {
// PM2.5 严重超标:红色闪烁 + 蜂鸣器间歇响
digitalWrite(LED_RED, HIGH);
tone(BUZZER, 2000, 100); // 2kHz 响 100ms
} else if (thBad || pmWarn) {
// 温湿异常 或 PM2.5 中度污染:黄色常亮
digitalWrite(LED_YELLOW, HIGH);
} else {
// 全部正常:绿色常亮
digitalWrite(LED_GREEN, HIGH);
}
}
// ── 辅助函数:开机 LED 自检 ──
void testLeds() {
digitalWrite(LED_RED, HIGH); delay(200);
digitalWrite(LED_RED, LOW);
digitalWrite(LED_YELLOW, HIGH); delay(200);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_GREEN, HIGH); delay(200);
digitalWrite(LED_GREEN, LOW);
}
代码说明:
- 第 28 行
HardwareSerial pmsSerial(2):ESP32 硬件 Serial2,对应 GPIO 16/17,比 SoftwareSerial 稳定 10 倍 - 第 65 行
pms.passiveMode():PMS5003 切被动模式,传感器不会持续上报,ESP32 主动wakeUp()才读数据,省功耗 - 第 79 行
SSD1306_SWITCHCAPVCC:OLED 内部电荷泵,3.3V 也能驱动(部分模块需 5V,详见数据手册) - 第 130 行
tone(BUZZER, 2000, 100):Arduino 内置 tone() 函数,输出 2kHz 方波 100ms。ESP32 兼容 tone() - 第 159 行
noTone(BUZZER):停止 tone(),必须显式调用,否则蜂鸣器会持续响 - 第 184 行
PMS::DATA pmsData:PMS 库的数据结构,包含 PM1.0/PM2.5/PM10 标准/大气环境两种单位 - 第 195 行
PM_AE_UG_2_5:AE = Atmospheric Environment(大气环境),单位 μg/m³,国标用这个 - 第 220-235 行:报警判断优先级:PM 报警 > 温湿警告 > 正常(避免多状态同时触发)
调试步骤
按以下顺序逐步调试,每步验证通过再进下一步。避免一次接 8 根线发现全错——这种痛苦经历过的都懂。
第 1 步:DHT22 自检(5 分钟)
- 接线:仅接 DHT22 的 3 根线(VCC/GND/DATA),接 ESP32 3.3V/GND/GPIO 4
- 烧录下面这段最小代码(Adafruit DHT 库自带的
DHTtester示例改 1 行:把DHT11改成DHT22) - 打开串口监视器(波特率 115200),看到
Humidity: xx.x% Temperature: xx.x*C→ 接线正确 - 看到
Failed to read from DHT sensor→ 检查:VCC/GND 是否接反、DATA 引脚是否接对、上拉电阻是否虚焊 - 手捂 DHT22,湿度数字应明显上升(验证响应正常)
第 2 步:PMS5003 自检(8 分钟)
- 接线:PMS5003 的 4 根线(VCC/GND/TX/RX)全接,VCC 必须接外部 5V,GND 务必和 ESP32 共地
- 确认 ESP32 RX2=GPIO 16 接 PMS 的 TX,ESP32 TX2=GPIO 17 接 PMS 的 RX(不能反)
- 烧录 PMS 库自带的
PMS_Basic示例(改成 Serial2 即可) - 打开串口监视器,看到 PM1.0/PM2.5/PM10 三个数字(非 0 非 65535)→ 接线正确
- 看到 0 或 65535 → 检查:TX/RX 是否接反、GND 是否共地、5V 电源是否通电(万用表测电源正负极应有 5V)
- 对着 PMS5003 进气口哈气,PM2.5 数字应瞬间飙升到 100+(验证响应)
第 3 步:OLED 自检(3 分钟)
- 接线:OLED 的 4 根线全接(VCC/GND/SDA/SCL),与 ESP32 GPIO 21/22 接好
- 烧录
Adafruit_SSD1306库自带的ssd1306_128x64_i2c示例 - 看到 OLED 显示一个跳动的方块 + 数字统计 → 接线正确
- 黑屏 → 用 I2C Scanner 扫地址(确认是 0x3C 还是 0x3D),检查 SDA/SCL 是否接反、VCC/GND 是否反
第 4 步:三色 LED + 蜂鸣器自检(5 分钟)
- 接线:LED 4 根线(R/Y/G/GND)+ 蜂鸣器 3 根线(I/O/VCC/GND)全接
- 烧录
testLeds()函数(核心代码中已包含),观察三色 LED 是否依次亮 200ms - 三个灯都能亮 → LED 接线正确
- 某灯不亮 → 检查对应的信号线是否接对、模块是共阴还是共阳(极性反了不亮)
- 蜂鸣器能否响 → 单独用
digitalWrite(BUZZER, HIGH); delay(500); digitalWrite(BUZZER, LOW);测试 - 蜂鸣器不响但电压有 → 模块坏了或三极管没焊好;裸蜂鸣器 100mA 会烧 ESP32 GPIO
第 5 步:整合测试(10 分钟)
- 烧录上面的完整核心代码
- 打开串口监视器(波特率 115200),看到 5 项数据(温度/湿度/PM1.0/PM2.5/PM10)持续输出
- OLED 同步显示 4 项数据 + 底部状态栏(OK / WARN / ALARM)
- 正常状态:绿色 LED 常亮,底部显示
[OK] GOOD WORK HARD - 用打火机在 PMS5003 进气口点一下(不要持续打火,会烧坏传感器),PM2.5 应瞬间飙升,红色 LED 闪烁 + 蜂鸣器响 → 报警逻辑正常
- 用嘴对 DHT22 哈气,湿度应上升到 80%+,黄色 LED 应亮 → 温湿异常判断正常
- 串口监视器持续输出数据,无乱码(乱码 = PMS5003 电源不稳或 GND 没共地)
常见故障排查(先看这 5 条)
- PMS5003 一直读 0 或 65535:90% 是 GND 没共地 / TX-RX 接反。万用表测 ESP32 GND 和 PMS5003 GND 应为 0Ω
- DHT22 一直 NaN:VCC/GND 接反会烧传感器;DATA 引脚不对;上拉电阻没焊(裸传感器必加 10kΩ)
- OLED 黑屏:I2C 地址错(试 0x3D);SDA/SCL 接反;VCC/GND 接反烧屏
- 蜂鸣器声音很小:买的是无源蜂鸣器(需 PWM 驱动),本项目用有源蜂鸣器(高电平就响)。看模块背面丝印:"ACTIVE" = 有源
- 报警不触发:阈值定义在代码顶部
#define PM25_ALARM 75.0,按需调整(比如有娃的家庭 PM25_ALARM 调到 50);ESP32 烧录时记得按 BOOT 按钮
设计图
系统架构图(ESP32 中心 + 4 个外围)
┌──────────────────────┐
│ ESP32 DevKit V1 │
│ (主控 + WiFi 可选) │
└──────────┬───────────┘
│
┌───────────┼───────────┬──────────────┐
│ │ │ │
GPIO 4 I2C(21/22) GPIO 25/26/27 GPIO 12
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌─────────┐ ┌────────┐
│ DHT22 │ │ SSD1306 │ │三色 LED │ │ 蜂鸣器 │
│温湿度 │ │ 0.96 OLED│ │ 红/黄/绿│ │ 2kHz │
│±0.5°C │ │ 128x64 │ │ 3 GPIO │ │ 100mA │
└────────┘ └──────────┘ └─────────┘ └────────┘
▲
│
┌────────┴────────┐
│ PMS5003 PM2.5 │ ← 5V 独立电源
│ UART2 16/17 │ (必共地)
│ 0-1000μg/m³ │
└────────────────┘
数据流与报警状态机
| 当前状态 | 触发条件 | 切换到 | 视觉反馈 |
|---|---|---|---|
| OK 正常 | PM2.5 升至 35-75 | WARN 警告 | 🟡 黄色常亮 |
| OK 正常 | 温/湿越界(T>30/T<16/H>70/H<30) | WARN 警告 | 🟡 黄色常亮 |
| WARN 警告 | PM2.5 升至 >75 | ALARM 报警 | 🔴 红闪 + 蜂鸣器 |
| WARN 警告 | 所有数据回正常 | OK 正常 | 🟢 绿色常亮 |
| ALARM 报警 | PM2.5 降至 <35 | OK 正常 | 🟢 绿色常亮 |
| ALARM 报警 | PM2.5 降至 35-75(仍污染) | WARN 警告 | 🟡 黄色常亮 |
状态机优先级:ALARM > WARN > OK,即只要任一严重条件满足,状态取最高级。这避免了"PM2.5 95 但温度刚好正常"被错误归为 OK 的问题。
PM2.5 健康影响对照表(参考 WHO 2021 指南)
| PM2.5 (μg/m³) | 等级 | AQI 颜色 | 健康影响 | LED 状态 |
|---|---|---|---|---|
| 0 - 12 | 优 | 🟢 绿 | 无影响,正常活动 | 绿色常亮 |
| 12 - 35 | 良 | 🟡 黄绿 | 敏感人群注意 | 绿色常亮 |
| 35 - 75 | 轻度污染 | 🟡 黄 | 儿童/老人应减少户外 | 黄色常亮 |
| 75 - 115 | 中度污染 | 🟠 橙 | 所有人应减少户外 | 红色闪烁 + 蜂鸣器 |
| 115+ | 重度污染 | 🔴 红 | 应戴 N95,开空气净化器 | 红色闪烁 + 蜂鸣器 |
功耗与采样频率对照表
| 模块 | 工作电压 | 工作电流 | 采样频率 | 节能策略 |
|---|---|---|---|---|
| ESP32 主控 | 3.3V | ~80mA(WiFi 关) | — | 关闭 WiFi/BT 可降至 20mA |
| DHT22 | 3.3-5V | 2.5mA(采样峰值) | 2 秒/次 | 功耗低,无需节能 |
| PMS5003 | 5V | ~100mA(工作) | 2 秒/次(被动) | 采样后 pms.sleep() 降至 1mA |
| OLED | 3.3-5V | ~20mA(显示时) | — | 夜间可 display.dim() 降亮度 |
| 三色 LED | 3.3V | ~60mA(全亮) | — | 报警时亮,正常时只亮 1 颗 ~20mA |
| 蜂鸣器 | 3.3-5V | ~100mA(响时) | — | 间歇响 100ms/次 |
| 合计 | ~280mA 峰值 | — | 建议 5V/3A 电源(PMS5003 必须 5V 独立) | |
扩展玩法
基础版 ¥122 跑通后,下面这些二次开发都很容易:
- 加 SCD30 CO₂ 传感器(¥80,I2C 接口):能直接测 CO₂ 浓度(400-10000ppm),比 PM2.5 更准——同主题不同方案,预算够可以升级
- 加 WiFi + MQTT:数据推到 Home Assistant 长期记录,看一年 PM2.5 趋势图(需外网,本地局域网也行)
- 加 SD 卡模块(¥5):离线记录 7 天数据,每分钟一条,CSV 格式用 Excel 打开分析
- 加语音播报(DFPlayer Mini ¥10 + 小喇叭 ¥3):PM2.5 超标时语音"开窗通风",老人小孩也能听懂
- 联动空气净化器:PM2.5 > 100 时通过红外/智能插座自动开净化器(参考 06-26 项目的红外发射方案)
- 加 LCD 大屏(1.8 寸 TFT ¥15):替代 OLED,客厅也能看清数据,颜值更高
金句
"空气不会说话,但 PM2.5 会让你打喷嚏;ESP32 不会呼吸,但能替你看护每一次呼吸。"
本项目总成本 ¥122,比小米米家空气检测仪便宜 65%,数据本地化无云端依赖,三级报警机制覆盖 90% 室内空气异常场景(DHT22 + PMS5003 + OLED + LED + 蜂鸣器全打通)。所有阈值、显示内容、报警逻辑都可在 Arduino 代码里改——动手做,让数据为每一次呼吸负责。