1. 从灰尘到数据:为什么我会对家里的空气质量较真
搬到现在的房子三年了,头两年我从来没想过空气质量这件事。直到去年冬天有一段时间,每天早上起来鼻腔都干得发疼,喉咙像被砂纸擦过,孩子也频繁揉眼睛。开窗通风之后症状明显缓解,但我总想知道:屋子里到底什么超标了?凭感觉猜实在不靠谱。
坦白讲,普通家庭对空气质量的认知基本停留在两个极端:要么觉得“天天住着能有什么事”,要么被各种除醛、净化产品制造焦虑。我决定自己动手做一个家庭空气质量监测装置,用数据把这件事搞清楚。项目目标不复杂:实时检测PM2.5、PM10、二氧化碳趋势、TVOC和温湿度,把数据传到手机上,攒下长期运行记录,后面可以慢慢分析规律。
这篇文章是全程记录的第1部分,从需求拆解、传感器选型到组装和固件开发,后面会再写第2部分,讲数据上传、告警配置和长期趋势分析。适合动手能力尚可、会一点代码(哪怕只会抄代码)的朋友参考,预算大约三百块出头,不需要焊台,一把电烙铁、几根杜邦线就够了。
先说结论:这个活没有想象中复杂,但也没有想象中简单。难的不在硬件连接,而在“怎么让数据可信”。传感器买回来就能读数,但读数对不对、有没有漂移、该放在哪里,才是真正决定项目成败的事。
2. 指标太多,先分清这 5 个传感器参数再动手
2.1 PM2.5与PM10:粒径不同,来源和危害都不同
PM2.5是空气动力学直径小于等于2.5微米的颗粒物,PM10是小于等于10微米的颗粒物。打个比方:PM10是粗沙子,PM2.5是细面粉。粗颗粒大多来自扬尘、花粉、宠物皮屑,能被鼻腔阻挡一部分;细颗粒能穿过鼻毛和上呼吸道直接进入肺泡,甚至进入血液循环。
把PM2.5和PM10放在一起测,就能粗略判断污染来源。如果PM10高但PM2.5不高,说明是地面扬尘或花粉这类粗颗粒为主;如果两者都很高且比值接近,大概率是燃烧产物、二手烟、烹饪油烟这类细颗粒污染。
2.2 二氧化碳:你在室内犯困的隐形元凶
二氧化碳本身不是毒气,但室内CO2浓度过高会让人昏昏欲睡、注意力下降。一个15平方米的卧室,关窗睡一整晚,两个成年人可以把CO2浓度从500ppm推到2000ppm以上。数值超过2000ppm时,不少人会出现头痛、烦躁。
我选择用SGP30来估算eCO2(等效二氧化碳),因为它的成本比专业红外CO2传感器(如MH-Z19B)低不少。需要提前说明的是,SGP30的eCO2不是真正的红外测量,而是根据TVOC负载推算出来的等效值,绝对值精度一般,但趋势判断完全够用。
2.3 TVOC:装修污染的主力军团
TVOC(总挥发性有机物)涵盖苯、甲苯、二甲苯、乙苯、苯乙烯等一大类物质。刚装修完的房子,TVOC往往超标数倍,而且释放周期长达数月甚至数年。SGP30内置了VOC算法芯片,可以直接输出TVOC浓度(单位ppb)。
但SGP30也有短板:它需要一段时间的“基线校准”,初次上电的前48小时读数会明显漂移。这一点后面详细说。
2.4 温湿度:不做装饰,空气净化器的性能受它影响
温度和湿度单独看不是污染物,但会直接影响颗粒物传感器的计数结果和化学传感器的反应速度。湿度过高时,颗粒物吸湿膨胀,PM2.5数值可能会明显偏高;温度影响VOC传感器内部的加热电路工作状态。
把温湿度纳入监测,一方面是为了修正其他传感器读数的解读逻辑,另一方面是排查“为什么数值突然变化”时必备的对照变量。
2.5 甲醛:要不要现在就上?我选择暂时不做
甲醛在家装污染里的地位是C位明星,但它对传感器的要求很苛刻。电化学甲醛传感器的寿命通常在2到3年,需要定期校准,便宜模块的交叉敏感度高(酒精、香水都会干扰),价格也不低。第1版我决定先不上电化学甲醛传感器,把重点放在PM2.5/PM10/CO2趋势/TVOC/温湿度这一组参数上。等这套系统稳定运行两三个月之后,再考虑扩展甲醛模块。
提示:如果你预算充足或者刚装修完,可以一步到位上ZE08-CH2O电化学甲醛模块。但要接受一个事实:所有电化学传感器都会随使用时间老化,校准周期远远短于激光颗粒物传感器。
3. 硬件选型:预算、精度、可玩性的取舍
3.1 主控:ESP32是当前最优解
主控芯片我选的是ESP32开发板,价格在25到50元之间。选择理由有三条:
- 自带WiFi和蓝牙,后续数据上传不用外挂通信模块;
- 计算能力对传感器数据处理的负载绰绰有余,后面就算再加屏幕显示或者本地跑简单滤波算法也够用;
- 生态成熟,Arduino示例代码遍地都是,踩坑资料丰富。
如果你手头有树莓派,当然也可以做,但功耗和体积都会更大,成本也高出不少。从“7×24小时长期开机”这个需求出发,ESP32的低功耗优势非常明显。
3.2 颗粒物传感器:Plantower PMS5003是口碑之作
PMS5003是北京攀藤科技的一款激光散射颗粒物传感器,价格70到90元,可以同时输出PM1.0、PM2.5、PM10浓度。它内部的激光器照射气流中的颗粒物,由光电二极管接收散射光信号,再通过算法换算成质量浓度。
同系列有PMS7003、PMS9003等型号,接口定义基本一致。选PMS5003的理由是它的信号稳定性和数据输出格式资料最全,社群使用者最多,后期排查问题方便。
3.3 气体传感器:SGP30兼顾TVOC和eCO2
SGP30是Sensirion公司的一块金属氧化物半导体传感器,I2C接口,价格在35到50元之间。它输出的TVOC和eCO2指标虽然不如实验室级别精确,但在民用监测场景下是具有参考价值的。
另外一个选择是CCS811,同样是VOC传感器,但初版固件问题较多,需要手动升级驱动的概率比较高。SGP30相对省心一些,我最后选了它。
3.4 温湿度传感器:SHT30的稳定表现
温湿度模块有DHT11、DHT22、BME280、SHT30等一票选择。DHT11精度太差,DHT22凑合但时序协议用起来烦,BME280价格略高。SHT30温湿度精度为±0.2°C和±2%RH,I2C接口方便,价格约20元,是最合理的中间选择。
3.5 其他必需品
- 12V/1A电源适配器:PMS5003的激光器需要5V供电,但建议用12V适配器再搭一个稳压模块,供电更稳定;
- LM2596降压模块:把12V降到5V给ESP32和PMS5003供电,约5元;
- 5V转3.3V的逻辑电平转换模块:如果ESP32和传感器之间需要电平匹配,但SGP30和SHT30本身支持3.3V供电,这事可以简化;PMS5003的数字输出口是3.3V电平,ESP32的GPIO输入也是3.3V,直接用杜邦线连接即可。
- 面包板、杜邦线、一个小型亚克力外壳或者3D打印外壳。
整个硬件的预算明细,粗略列一下:
ESP32开发板 35元 PMS5003颗粒物传感器 85元 SGP30气体传感器 45元 SHT30温湿度传感器 20元 LM2596降压模块 6元 12V/1A适配器 15元 面包板、杜邦线、外壳等零碎 30元 合计: 236元左右如果想再压预算,把SGP30换成CCS811(35元),总价能控制在220元附近。但建议不要为了省六七十块钱在核心传感器上降级,数据和体验差距比较大。
4. 接线与安装:从零开始的组装细节
4.1 电源接线:先供电,再接信号线
整体供电链路是:墙插220V → 12V适配器 → LM2596降压到5V → 一路给ESP32的5V引脚,一路给PMS5003的5V引脚。SGP30和SHT30都支持3.3V供电,直接接ESP32的3.3V引脚。
LM2596模块在使用前要用万用表调整输出电压,它上面有个蓝色电位器,边调边测,精确调到5.0V。千万别跳过这一步直接接负载,模块出厂电压常常高于5V,接错会烧板子。
注意:ESP32的5V引脚就是USB供电时的VBUS引脚,接外部5V电源时不用再插USB,否则可能出现双电源冲突,损坏USB口。
4.2 信号接线:I2C和串口的配合
SGP30和SHT30都挂在ESP32的I2C总线上,SGP30的地址是0x58,SHT30的地址是0x44,地址不冲突,并联没问题。
PMS5003使用UART串口和ESP32通信,默认波特率9600bps。但ESP32的硬件串口只有三个,其中一个是烧录口,为了避免干扰,我直接用SoftwareSerial,随便指定两个GPIO引脚来接收数据,这样布局更灵活。
接法汇总:
| 模块 | 引脚 | ESP32引脚 |
|---|---|---|
| SGP30 | VCC | 3.3V |
| SGP30 | GND | GND |
| SGP30 | SCL | GPIO22 |
| SGP30 | SDA | GPIO21 |
| SHT30 | VCC | 3.3V |
| SHT30 | GND | GND |
| SHT30 | SCL | GPIO22 |
| SHT30 | SDA | GPIO21 |
| PMS5003 | VCC | 5V |
| PMS5003 | GND | GND |
| PMS5003 | TXD | GPIO16(用SoftwareSerial RX) |
| PMS5003 | RXD | GPIO17(用SoftwareSerial TX,本方案不用) |
PMS5003只需要读取数据,不需要发送指令,所以它的RXD引脚不接也行。
4.3 外壳:必须保证传感器和空气充分接触
这一步最容易被忽略,也最容易毁掉整组数据。
我第一次把PMS5003装进了一个全封闭的塑料盒子里,只留了两个小孔走线,结果PM2.5读数比其他设备低了一倍。原因很简单:PMS5003的激光探头需要气流持续通过,空气不流通,测到的就是盒子内部那点“死空气”的颗粒物浓度。
外壳设计需要注意两点:
- 颗粒物传感器要留明显的进风口和出风口,风道不能被遮挡,且出风口不要对着进风口;
- SGP30和SHT30需要和空气接触,外壳相应位置必须开孔,但不要放在PMS5003的正风口直吹,否则颗粒物传感器风扇带来的气流扰动会影响VOC传感器的局部环境。
我最后用亚克力板做了一个开放式的框架,六个面只封闭底面和背面,其他面都留出大面积开口,再配了一个小风扇从侧面往PMS5003的进风口方向缓吹(具体来说是一个5V的4010风扇,距离PMS5003大约5厘米)。这个方案实测下来数据基本稳定。
4.4 首次上电检查:别急着写代码
焊接和接线完成后,先不烧录程序,用万用表测一下各组电压:
- 5V电源两端是否稳定在4.8~5.2V之间;
- ESP32的3.3V输出是否正常;
- 确保SGP30和SHT30的VCC引脚没有接到5V。
确认无误后再用USB线连电脑烧录一个最简单的I2C扫描程序,分别确认0x44和0x58两个地址能否被识别。这一步能把你和“莫名其妙连不上传感器”的问题隔离开。
5. 固件设计:如何让数据真正“说话”
5.1 代码框架:Arduino + 库
固件的核心逻辑分四块:I2C初始化、PMS串口数据解析、循环采样、数据外发。第1部分先把数据外发做成调试输出的格式,第2部分再对接MQTT和数据库。
需要安装的库:
- Adafruit SGP30 Sensor
- Adafruit SHT31(SHT30也可以用)
- 内置的Wire.h和SoftwareSerial
5.2 PMS5003数据解析:串口帧格式
PMS5003每秒钟发一组数据帧,帧长32字节。帧头有两个固定字节(0x42 0x4D),第2和第3字节是帧长,后面的数据区包含PM1.0、PM2.5、PM10的CF=1标准浓度和大气环境浓度。
从第4字节开始:
数据区偏移 0-1:PM1.0标准浓度 数据区偏移 2-3:PM2.5标准浓度 数据区偏移 4-5:PM10标准浓度这里有个容易踩的坑:数据是高位在前(Big-endian),也就是高字节在前、低字节在后。如果你直接把两个字节相加,算出来的数值会错得离谱。正确做法是(data[4] << 8) | data[5]。
标准浓度CF=1指的是在“标准颗粒物”特性条件下的质量浓度,主要用于传感器一致性对比;大气环境浓度则是经过空气质量修正后的数值。日常显示建议直接使用大气环境浓度,就是偏移6开始的那组数据。
5.3 采样策略:不能只看瞬时值
颗粒物浓度是不断波动的,特别是在有人走动、做饭、开窗等场景下,瞬时值跳动很大。我采用的是“每1秒读一次原始帧,连续20秒后取平均值”的策略,每20秒输出一组平滑后的数据。
SGP30读取频率不宜过高,官方建议最快1Hz,我设置为每5秒读一次。SGP30内部有算法芯片,需要持续供电才能维持基线,不要频繁断电重置。
温湿度每10秒读一次就够,变化速度远不如颗粒物和VOC。
5.4 基础代码片段
下面给出一段核心采样逻辑,只保留最关键的传感器读取和解析部分:
#include <Wire.h> #include "Adafruit_SGP30.h" #include "Adafruit_SHT31.h" #include <SoftwareSerial.h> #define PMS_SET_PIN 16 // PMS5003 TXD接GPIO16 SoftwareSerial pmsSerial(16, 17); Adafruit_SGP30 sgp; Adafruit_SHT31 sht = Adafruit_SHT31(); unsigned long lastPmsRead = 0; unsigned long lastSgpRead = 0; unsigned long lastShtRead = 0; float pm25Sum = 0; float pm10Sum = 0; int pmCount = 0; struct PmsData { float pm25; float pm10; }; bool readPmsData(PmsData &d) { uint8_t buf[32]; int idx = 0; unsigned long start = millis(); while (millis() - start < 1000) { if (pmsSerial.available()) { uint8_t b = pmsSerial.read(); if (idx == 0 && b != 0x42) continue; // 等帧头 if (idx == 1 && b != 0x4D) { idx = 0; continue; } buf[idx++] = b; if (idx >= 32) break; } } if (idx < 32) return false; // 校验帧长 uint16_t frameLen = (buf[2] << 8) | buf[3]; if (frameLen != 28) return false; // 大气环境浓度:偏移6/8/10 d.pm25 = ((buf[8] << 8) | buf[9]) * 1.0; d.pm10 = ((buf[10] << 8) | buf[11]) * 1.0; return true; } void setup() { Serial.begin(115200); pmsSerial.begin(9600); Wire.begin(); if (!sgp.begin()) Serial.println("SGP30 not found"); if (!sht.begin(0x44)) Serial.println("SHT30 not found"); sgp.setIAQBaseline(0x8B8A, 0x4A4C); // 可加载已保存基线 } void loop() { unsigned long now = millis(); if (now - lastPmsRead >= 1000) { lastPmsRead = now; PmsData d; if (readPmsData(d)) { pm25Sum += d.pm25; pm10Sum += d.pm10; pmCount++; } if (pmCount >= 20) { float avgPm25 = pm25Sum / pmCount; float avgPm10 = pm10Sum / pmCount; pm25Sum = 0; pm10Sum = 0; pmCount = 0; Serial.printf("PM2.5: %.1f ug/m3, PM10: %.1f ug/m3\n", avgPm25, avgPm10); } } if (now - lastSgpRead >= 5000) { lastSgpRead = now; uint16_t tvoc, eco2; if (sgp.IAQmeasure()) { tvoc = sgp.TVOC; eco2 = sgp.eCO2; Serial.printf("eCO2: %d ppm, TVOC: %d ppb\n", eco2, tvoc); } } if (now - lastShtRead >= 10000) { lastShtRead = now; float t = sht.readTemperature(); float h = sht.readHumidity(); if (!isnan(t) && !isnan(h)) { Serial.printf("Temp: %.1f C, Humidity: %.1f %%\n", t, h); } } }5.5 基线校准:SGP30的开局教训
SGP30上电后会自动进行基线校准,典型时间是48小时。这个过程里,它会把当前环境的VOC水平作为基准进行内部算法拟合。如果你一开始在通风良好的阳台校准基线,然后搬进一个甲醛超标的房间,前两天的TVOC读数会很低,直到算法逐步追踪到新环境。
这意味着两件事:
- 第一次使用别急着看绝对数值,先让它在最终部署位置连续运行至少48小时;
- 如果中途断电超过几分钟,基线会丢失,重新上电后又要重新校准。
所以我的做法是:在代码里掉电前把基线保存到EEPROM,启动时通过setIAQBaseline恢复。但这个功能要在稳定运行一段时间之后才可靠,初期不推荐折腾,让它自然地跑两天就好。
6. 放置传感器:同一台设备,位置不同结果天差地别
6.1 别放在窗边,别放在墙角
传感器放置是“数据可信度”的生命线。
放在窗边,特别是朝南的窗户边上,白天阳光直射会让温度传感器读数偏高,开窗时直接测到室外空气,而你要监测的是人在室内活动区域的空气质量,不是窗户边上那一条换气带。
放在墙角,空气流动不畅,颗粒物和VOC都会在局部集聚或滞留。衣柜旁边测出来的甲醛浓度,可能比其他区域高出一大截,而那不是你日常主要活动的地方。
正确的位置应该是:离地面1.2到1.5米,大致是人坐着时呼吸带的高度;距离墙面至少0.5米;避开空调出风口、加湿器、空气净化器出风口、厨房排烟口。
6.2 放置场景:客厅和卧室的取舍
一台设备只能测量一个点,这是物理限制。你的家不是一个均匀混合的盒子,每个房间的空气质量都不一样。我建议的排序是:
- 优先放卧室。人在卧室连续待的时间最长,而且睡眠时门窗紧闭,CO2和TVOC的累积效应最明显;
- 其次放客厅。客厅是白天活动中心,污染源多(油烟、家具释放、家电扬尘);
- 最后考虑儿童房。如果小孩有过敏、哮喘史,儿童房的空气质量优先级应该排到最前面。
我第一台设备放在了卧室,因为最近困扰我最多的就是每天早上起床的鼻腔干痛和犯困感。放进去的第一晚,数据就狠狠打了我一巴掌。
6.3 和空气净化器保持距离
如果你的家中有空气净化器,传感器不能离它太近。净化器出风口附近的PM2.5浓度可能低到比真实值低几个数量级。这就像你在空调出风口下面测室温,怎么测都是空调的风温,而不是房间平均温度。
我建议传感器和净化器之间至少保持2米距离,并且保证传感器不在净化器的直吹路径上。
7. 数据初测:第一周我看到了什么惊喜发现
7.1 卧室的CO2,一个每天被忽视的“隐形高峰”
设备在卧室稳定运行了两天,基线校准完成之后,我开始认真记录数据。最惊人的发现是CO2的变化曲线。
晚上11点入睡,两个成年人关窗关门,卧室CO2从大约600ppm起步,到凌晨3点已经超过1800ppm,早上7点出门前接近2200ppm。白天开窗通风之后,立刻回落到450到500ppm。
这个数据彻底解释了我之前“早上起来头昏脑涨”的问题。2200ppm对于健康成年人来说不至于中毒,但足以让睡眠变浅、晨起乏力。解决方案也很简单:睡觉时把窗户开一条缝,或者装一个带通风功能的窗式通风器。
7.2 做饭对客厅的影响被严重低估
我把设备临时搬到客厅做了一次对照实验。晚饭时间开火炒菜,厨房门没关,客厅PM2.5从30多μg/m³直接飙升到180μg/m³,持续了将近四十分钟才慢慢降下来。
注意这是“客厅”,还不是厨房里的颗粒物浓度。油烟扩散的速度远超我预期,厨房门不关等于整个房子都在“被迫抽油烟”。从那以后我做饭一定关厨房门、开油烟机,而且炒完菜再多开五分钟,把残余油烟抽干净。
7.3 湿度对PM2.5读数的干扰案例
有一天晚上我开加湿器,发现PM2.5读数从40涨到了90。当时我还以为传感器坏了,后来查了很多资料才反应过来,加湿器喷出的水雾本身就是颗粒物。激光颗粒物传感器分不清“水滴颗粒”和“粉尘颗粒”,湿度上来,读数就跟着上去。
这个案例提醒了我:如果发现PM2.5短时间内异常升高,先看湿度曲线。湿度同时上涨,那大概率是水雾干扰,不是真的污染。
7.4 一周数据的初步印象
用Excel简单统计了一下第一周的数据:
| 指标 | 低谷值 | 高峰值 | 常见范围 |
|---|---|---|---|
| PM2.5 | 12 | 180+(做饭时) | 25-60 |
| PM10 | 18 | 260+(做饭时) | 35-80 |
| eCO2 | 450 | 2200+(夜间卧室) | 500-1200 |
| TVOC | 20ppb | 320ppb | 30-100 |
| 温度 | 18°C | 27°C | 20-25°C |
| 湿度 | 32% | 78% | 40-60% |
TVOC在白天家里开暖气的那几天出现过短期峰值,推测和温度升高加速家具材料释放有关。这个趋势值得持续观察,我会在第2部分结合更长时间的记录做详细分析。
8. 校准、维护与长期运行的细节经验
8.1 颗粒物传感器需要定期清理
激光颗粒物传感器内部有一个小风扇,长期运行会积累灰尘,风道堵塞会导致进风量下降,读数偏低。清理周期取决于环境灰尘程度,一般2到3个月清理一次。
清理方法很简单:断电,打开外壳,用气吹(或压缩空气罐)吹掉风扇叶片和风道里的灰尘。注意不要用湿布擦激光镜头,也不要用酒精清洁,避免损坏镜片镀膜。
8.2 SGP30的基线:掉电不是小事
SGP30的基线维护是我这次踩过最大的坑。有一天下雨断电了几分钟,重新上电后我没有恢复基线,SGP30视作“全新传感器”重新校准,导致接下来两天TVOC和eCO2数据明显偏低。
后来我在固件里加了第2部分要讲的“基线定期保存和恢复”逻辑:每半小时读一次sgp.getIAQBaseline(),把baseline值写入EEPROM;启动时检测到EEPROM里有数据就setIAQBaseline恢复。这样断电重启基本不丢基线。
8.3 不要和手机空气质量App的数据直接比绝对值
手机天气App上的空气质量数据来自城市监测站,代表的是宏观区域水平。家用传感器的进风口在室内,测的是室内微环境。两者数据有差异是正常的,直接对比绝对值没有意义。
更有价值的对比方式是:看室内外数据的差值趋势。室外PM2.5 200μg/m³,室内仍然50μg/m³,说明门窗气密性不错;室内比室外高,说明室内有持续污染源,需要排查。
8.4 传感器要不要校准?怎么校准?
这也是新手问得最多的问题。严格来说,PM2.5激光传感器需要和基准仪器(比如β射线法监测仪)做同点位对比校准,普通用户没有这个条件。可行的替代方案是:
- 把设备带到室外环境,和城市监测站的数据对比,观察偏差趋势。注意监测站数据计算周期通常是一小时,对比前自己也要取一小时平均值;
- 购买一台经过校准的民用参考设备(比如某品牌雾霾表)做同一位置对照,看差值是否在合理范围内;
- 使用香烟烟雾或蚊香气溶胶做相对变化的趋势测试,只看曲线形状,不看绝对值。
我在实测中发现,PMS5003出厂精度在多数场景下已经够用,重点是保持风道畅通,而不是频繁校准。
提示:不要被“精度不达标就不敢用”的心理困住。居家空气质量监测的核心价值在趋势判断和相对变化,而不是实验室级别的绝对精度。你真正需要知道的是“什么情况下超标了、持续时间多长、怎么改善”,这些数据完全可以支撑得出结论。
8.5 下一步:为了长期运行,供电和网络要稳定
第1阶段我在USB供电+串口调试输出下运行,这个状态只能短期用,不能长期放在卧室里(拉着一根USB线太难看,而且掉电后数据就断了)。第2部分我会把它改造成:
- 预埋12V电源线到监测位置,降压后给ESP32和传感器供电;
- 增加MQTT数据上报,把数据存到本地服务器,再接入可视化面板;
- 增加断电重连、数据缓存补传机制;
- 在设备状态异常时通过微信推送到手机。
我在这个项目里学到的最重要的一件事是:空气质量监测不是“买一个空气检测仪放桌上”那么简单,它的核心价值在于持续、可靠、有参照系的数据。一个便宜的DIY设备,只要放置合理、长期稳定运行,积累下来的数据可能比一次专业检测更有指导意义,因为专业检测只给你一个时间点的快照,而你自己搭的系统能告诉你每一天、每一小时、每一个生活行为对室内环境的影响。
第2部分我会把整套数据采集架构、可视化方案和告警逻辑完整写出来,到时候见。