1. 项目概述
这个周末我完成了一个挺有意思的小项目——用单片机做了个空气质量检测仪。作为一个在嵌入式领域摸爬滚打多年的老工程师,我发现很多初学者对这类项目既感兴趣又不知从何下手。今天就详细分享一下我的实现过程,从硬件选型到软件调试,把踩过的坑和积累的经验都整理出来。
这个检测仪的核心功能是实时监测室内空气中的PM2.5、PM10、二氧化碳(CO2)和挥发性有机物(TVOC)浓度,通过OLED屏幕显示数据,超标时还会触发蜂鸣器报警。整套系统成本控制在200元以内,非常适合作为毕业设计或创客项目。下面我就从硬件设计开始,一步步拆解实现细节。
2. 硬件设计与选型
2.1 核心器件选型
选择单片机时,我在STM32和ESP32之间犹豫了很久。最终选择了ESP32-WROOM-32D,主要基于三点考虑:
- 内置Wi-Fi/蓝牙,方便后续扩展物联网功能
- 双核240MHz主频,性能足够处理传感器数据
- 价格仅25元左右,性价比极高
传感器方面做了如下配置:
- PM2.5/PM10:攀藤PMS5003(激光散射原理,精度±10%)
- CO2/TVOC:Sensirion SCD30(NDIR原理,±30ppm精度)
- 温湿度:BME280(I2C接口,±3%RH湿度精度)
提示:购买PMS5003时注意区分G(工业级)和ST(民用级)版本,后者寿命约8000小时,更适合个人项目。
2.2 电路设计要点
电源部分采用AMS1117-3.3V稳压芯片,输入接5V移动电源,输出给各模块供电。关键设计细节:
- 每个传感器电源脚并联100μF电解电容+0.1μF陶瓷电容,抑制电压波动
- I2C总线加10kΩ上拉电阻(SCL/SDA分别接GPIO22/21)
- 蜂鸣器驱动电路用S8050三极管,基极串1kΩ电阻保护IO口
PCB布局时特别注意:
- PMS5003远离其他传感器(避免风扇气流干扰)
- BME280不要贴在主控芯片正下方(防止发热影响读数)
- 所有数字地和模拟地单点连接在稳压芯片GND脚
3. 软件实现解析
3.1 开发环境搭建
使用PlatformIO+VSCode开发,比Arduino IDE更专业。关键配置步骤:
- 安装ESP32开发板支持包
- 添加依赖库:
- Adafruit_Sensor(传感器驱动框架)
- U8g2(OLED显示库)
- Sensirion_SCD30(CO2传感器专用库)
cpp复制// platformio.ini关键配置
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
adafruit/Adafruit Unified Sensor@^1.1.4
olikraus/U8g2@^2.32.15
sensirion/SCD30@^1.0.1
3.2 核心算法实现
传感器数据处理采用滑动窗口滤波算法,代码实现要点:
cpp复制#define WINDOW_SIZE 10
float pm25Readings[WINDOW_SIZE];
int currentIndex = 0;
float smoothPM25(float newReading) {
pm25Readings[currentIndex] = newReading;
currentIndex = (currentIndex + 1) % WINDOW_SIZE;
float sum = 0;
for(int i=0; i<WINDOW_SIZE; i++) {
sum += pm25Readings[i];
}
return sum / WINDOW_SIZE;
}
空气质量指数(AQI)计算采用分段线性插值法,以PM2.5为例:
cpp复制int calculatePM25_AQI(float concentration) {
float c = floor(concentration * 10) / 10; // 保留1位小数
if(c <= 12.0) return map(c, 0, 12, 0, 50);
else if(c <= 35.4) return map(c, 12.1, 35.4, 51, 100);
else if(c <= 55.4) return map(c, 35.5, 55.4, 101, 150);
// 其他分段省略...
}
3.3 低功耗优化
为延长电池续航,实现了以下优化策略:
- 动态采样频率:AQI<100时每5分钟采样一次,>100时改为1分钟
- 屏幕休眠:无操作30秒后关闭OLED,按按键唤醒
- 传感器轮询:非必要时不启动PMS5003风扇(通过MOSFET控制电源)
实测表明,这些优化使整机工作电流从85mA降至平均12mA,2000mAh电池可连续工作约7天。
4. 校准与测试
4.1 传感器校准
CO2传感器必须进行基线校准:
- 将设备放在室外通风处(400-450ppm环境)
- 连续运行至少20分钟
- 调用scd30.setAutoSelfCalibration(true)
PM传感器校准更复杂,我的土方法是:
- 用专业检测仪(如TSI 8530)作为基准
- 记录10组对比数据
- 在代码中添加补偿系数:
cpp复制float calibratePM25(float raw) {
return raw * 0.92 + 3.5; // 通过实验得出的修正公式
}
4.2 系统测试数据
在15㎡密闭房间内测试3小时,数据对比如下:
| 参数 | 本设备 | 专业设备 | 误差 |
|---|---|---|---|
| PM2.5 | 58μg/m³ | 62μg/m³ | 6.5% |
| CO2 | 1243ppm | 1187ppm | 4.7% |
| TVOC | 0.8mg/m³ | 0.75mg/m³ | 6.7% |
5. 常见问题解决
5.1 传感器读数异常
现象:PMS5003偶尔返回负值
解决方法:
- 检查UART接线(RX/TX不要接反)
- 在读取前添加50ms延时确保数据就绪
- 添加数据有效性校验:
cpp复制if(pm25 < 0 || pm25 > 999) {
Serial.println("Invalid PM data!");
return lastValidValue;
}
5.2 OLED显示闪烁
根本原因是ESP32的I2C时钟速度太快(默认100kHz),解决方法:
- 在setup()中降低时钟频率:
cpp复制Wire.setClock(40000); // 设为40kHz - 使用双缓冲机制:先在内存绘制完整帧,再一次性刷新到屏幕
5.3 无线干扰问题
当启用Wi-Fi时,SCD30的I2C通信可能失败。解决方案:
- 将I2C时钟线换成屏蔽线
- 在代码中添加重试机制:
cpp复制int retry = 0;
while(!scd30.dataAvailable()) {
if(++retry > 3) {
Serial.println("SCD30 timeout!");
break;
}
delay(50);
}
6. 项目扩展方向
这个基础版本完成后,还可以进一步扩展:
- 物联网功能:通过MQTT协议上传数据到Home Assistant
- 历史数据记录:添加SD卡模块存储CSV格式数据
- 可视化分析:用ESP32内置蓝牙发送数据到手机APP
我在实际使用中发现,放在新装修的卧室里特别实用。有次TVOC突然升高,排查发现是墙胶挥发导致的,及时通风避免了健康风险。这种能解决实际问题的项目,做起来特别有成就感。