基于Arduino的多传感器空气质量监测站DIY全攻略

基于Arduino的多传感器空气质量监测站DIY全攻略 1. 项目概述打造你的桌面级空气质量“哨兵”最近几年空气质量越来越成为大家日常关心的话题尤其是家里有老人、小孩或者自己本身就是过敏体质的朋友。PM2.5、二氧化碳浓度这些看不见摸不着的数据其实时刻影响着我们的舒适度和健康。市面上的专业检测仪动辄上千数据封闭而一些便宜的设备精度又让人心里没底。于是自己动手用Arduino搭建一个多功能空气质量监测站的想法就冒出来了。这不仅仅是一个玩具它能让你真正“看见”身边的空气理解开窗通风的时机评估空气净化器的效果甚至成为你智能家居系统的数据源。这个项目的核心就是利用Arduino UNO这块开源硬件的大脑指挥三位“感官敏锐”的传感器士兵DHT11负责感知环境的温湿度MH-Z14监测我们呼出的二氧化碳浓度而PMS5003则专门捕捉空气中漂浮的PM2.5和PM10等颗粒物。最后通过一块小巧的OLED屏幕将所有数据清晰、实时地展示在你面前。整个系统成本可控全部组装下来大概在200-300元人民币但获得的数据维度和可玩性远超同价位的成品。无论你是电子爱好者想练手还是创客想为家里添个实用工具亦或是学生需要一个综合性的物联网实践项目这套方案都能提供一个从硬件连接到软件编程的完整路径。2. 核心硬件选型与设计思路拆解2.1 主控与传感器选型背后的逻辑选择Arduino UNO作为主控几乎是所有入门和中级DIY项目的首选。原因很简单生态极其丰富任何你遇到的问题几乎都能在网上找到现成的库和案例IDE简单易用C语法对于有编程基础的人来说上手快对新手也友好最重要的是其数字和模拟I/O口、串口等资源对于连接多个传感器绰绰有余且5V的工作电压与大部分传感器兼容省去了电平转换的麻烦。传感器是项目的眼睛和鼻子选型直接决定了数据的可信度和项目的价值。DHT11温湿度传感器这是最经典的入门级数字温湿度传感器。我选择它并非因为它精度最高实际上它的湿度精度在±5%RH温度±2°C而是因为其极高的性价比和稳定性。对于室内环境监测这个精度完全足够让我们判断是否干燥需要加湿或者是否闷热需要通风。它的单总线通信协议也非常节省I/O口。MH-Z14A二氧化碳传感器这是本项目中单价最高的部件但也是核心价值所在。CO2浓度是衡量室内空气新鲜度的关键指标。当浓度超过1000ppm时人就容易感到疲倦、注意力不集中。MH-Z14A采用非色散红外NDIR原理这是测量CO2的主流方法相比一些廉价的半导体式传感器它的抗干扰能力强测量结果稳定可靠。它通过PWM或串口输出我们这里使用其PWM输出模式可以简化编程。PMS5003激光颗粒物传感器要检测PM2.5激光散射法是当前最成熟可靠的方案之一。PMS5003内置了风扇和激光器通过吸入空气用激光照射颗粒物再检测散射光来反推颗粒物的数量和大小从而计算出质量浓度。它提供串口数据输出信息非常全面包括PM1.0、PM2.5、PM10的实时质量浓度及颗粒物数量。选择它意味着你获得的是接近商用检测仪级别的颗粒物数据。注意传感器市场型号繁多。务必认准PMS5003还有PMS7003等型号引脚和协议可能略有不同。购买时最好选择带有线缆的套装自己焊接那细小的接口非常麻烦。2.2 显示与供电方案的考量显示部分选择了0.96英寸的SSD1306 OLED屏。相比LCD屏OLED是自发光对比度高在黑暗环境下显示清晰而且功耗更低。I2C接口的版本只需要连接SDA、SCL两根数据线和电源线极大简化了布线。这个小屏幕足以轮流显示多组数据信息密度正合适。供电上整个系统对电流要求不高Arduino UNO可以通过USB口供电比如连接电脑或手机充电器非常方便。如果想做成移动式或壁挂式可以找一个输出为5V/1A以上的DC电源适配器连接到UNO的电源插座上即可。这里有个实操心得如果使用移动电源供电务必确保移动电源不会因为电流过小而自动关机。有些传感器特别是PMS5003启动风扇时会有瞬时电流劣质电源可能导致系统不断重启。3. 硬件连接与电路搭建详解硬件连接是项目从图纸到实物的第一步清晰的接线是后续一切工作的基础。下图清晰地展示了所有模块与Arduino UNO的连接关系你可以把它当作“接线地图”来参考3.1 分步接线指南与避坑点我们按照信号类型和复杂度将接线分为几个部分逐一击破。第一步搭建I2C总线OLED屏幕这是最标准的部分。找到OLED屏的四个引脚VCC、GND、SDA、SCL。OLED VCC-Arduino 5VOLED GND-Arduino GNDOLED SDA-Arduino A4(在UNO上A4引脚同时也是I2C的SDA线)OLED SCL-Arduino A5(在UNO上A5引脚同时也是I2C的SCL线)提示I2C总线允许挂载多个设备所有设备的SDA都接A4SCL都接A5。虽然本项目只有一个OLED但这样接法为未来扩展如增加一个大气压传感器BMP280留出了可能。第二步连接单总线传感器DHT11DHT11有三个或四个引脚四个引脚的会多一个空脚。我们用到三个DHT11 VCC-Arduino 5VDHT11 GND-Arduino GNDDHT11 DATA-Arduino Digital Pin 2(数据引脚可更换为其他数字口但代码中需对应修改)第三步接入PWM输出传感器MH-Z14A CO2传感器MH-Z14A模块通常有多个接口我们需要找到PWM输出引脚。模块上通常会标注“PWM”或“OUT”。接线如下MH-Z14A VCC-Arduino 5VMH-Z14A GND-Arduino GNDMH-Z14A PWM-Arduino Digital Pin 3(这是一个支持外部中断的引脚方便我们精确测量PWM脉宽)第四步连接串口传感器PMS5003—— 最需谨慎的一步PMS5003通过串口UART通信而Arduino UNO的硬件串口Serial对应引脚0-RX和1-TX通常用于和电脑通信上传程序。如果直接连接编程时会因为引脚冲突报错。我们有三种解决方案最佳实践使用SoftwareSerial库创建软串口。这是我最推荐的方法。我们避开0和1引脚用其他数字引脚模拟一个串口来与PMS5003通信。PMS5003 VCC-Arduino 5VPMS5003 GND-Arduino GNDPMS5003 RX-Arduino Digital Pin 10(我们将把这个引脚定义为软串口的TX因为要“发送”指令给PMS)PMS5003 TX-Arduino Digital Pin 11(我们将把这个引脚定义为软串口的RX用来“接收”PMS的数据)物理开关方案如原文所述在PMS5003的RX/TX线和Arduino的0/1引脚之间加入拨动开关。编程时断开开关使用时闭合开关。这很直观但增加了硬件复杂度和接触不良的风险。插拔线方案每次上传程序前手动拔掉连接PMS5003 RX/TX的杜邦线。这是最原始的方法容易出错且麻烦。强烈建议采用第一种软串口方案一劳永逸。下面给出一个完整的接线汇总表模块引脚连接到 Arduino UNO 引脚说明OLED (SSD1306)VCC5V电源正极GNDGND电源地SDAA4 (或 SDA)I2C 数据线SCLA5 (或 SCL)I2C 时钟线DHT11VCC5V电源正极GNDGND电源地DATADigital Pin 2数据信号线MH-Z14AVCC5V电源正极注意电流需求GNDGND电源地PWMDigital Pin 3PWM 输出信号PMS5003VCC5V电源正极需较大电流GNDGND电源地RXDigital Pin 10接收端接Arduino的TXTXDigital Pin 11发送端接Arduino的RXSET悬空通常悬空即可RESET悬空通常悬空即可重要注意事项在连接所有线缆之前务必确保Arduino未通电。先接GND地线再接VCC电源线最后接信号线这是一个好习惯。检查所有连接是否牢固避免虚接。特别是PMS5003的线序TX接RXRX接TX千万不要接反。4. 软件编程与库文件管理硬件搭好了接下来就是赋予它灵魂的代码部分。Arduino项目的代码通常由一个.ino主文件和一系列依赖的库文件组成。4.1 必备库的安装与说明我们需要在Arduino IDE中提前安装以下库。打开“工具”-“管理库...”搜索库名进行安装。DHT sensor library用于驱动DHT11。安装时通常会自动安装其依赖的Adafruit Unified Sensor库。Adafruit SSD1306和Adafruit GFX Library用于驱动OLED屏幕。这是Adafruit公司维护的一套非常强大的图形库。PMS Library搜索“PMS”选择由Sawaiz或Adam Haile维护的库它们能很好地处理PMS5003的串口数据解析。SoftwareSerial这是Arduino内置库无需额外安装用于创建软串口。4.2 核心代码逻辑剖析代码的核心逻辑是初始化所有传感器和屏幕 - 进入主循环 - 依次读取各个传感器的数据 - 处理数据 - 在屏幕上显示。下面我们分块解析关键代码段。首先包含头文件和定义引脚#include Wire.h #include Adafruit_GFX.h #include Adafruit_SSD1306.h #include DHT.h #include PMS.h #include SoftwareSerial.h // 引脚定义 #define DHTPIN 2 #define DHTTYPE DHT11 #define CO2_PWM_PIN 3 #define PMS_RX_PIN 11 #define PMS_TX_PIN 10 // 初始化对象 DHT dht(DHTPIN, DHTTYPE); SoftwareSerial pmsSerial(PMS_RX_PIN, PMS_TX_PIN); // RX, TX PMS pms(pmsSerial); PMS::DATA data; // OLED 屏幕对象 (128x64分辨率I2C地址一般为0x3C) Adafruit_SSD1306 display(128, 64, Wire, -1);初始化设置setup()void setup() { Serial.begin(9600); // 启动硬件串口用于调试输出 dht.begin(); pmsSerial.begin(9600); // PMS5003的默认波特率 pms.passiveMode(); // 设置为被动模式由我们主动请求数据 // 也可以使用 pms.activeMode() 让传感器持续发送但被动模式更省电可控 // 初始化OLED如果失败则通过串口提示 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F(SSD1306 allocation failed)); for(;;); // 死循环阻止继续执行 } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.println(Air Monitor Init...); display.display(); delay(2000); // 配置CO2传感器的PWM引脚为输入 pinMode(CO2_PWM_PIN, INPUT); }关键点解析PMS传感器有主动和被动两种模式。主动模式下它会不停地向串口发送数据可能干扰我们的控制逻辑。被动模式下它等待主控Arduino发送请求指令后才返回一次数据这样时序更清晰也节省功耗。主循环loop()及数据读取函数主循环里我们依次读取并处理数据。为了不让屏幕刷新太快导致闪烁可以设置一个全局刷新间隔比如每2秒更新一组数据。void loop() { unsigned long currentMillis millis(); static unsigned long lastUpdate 0; const long interval 2000; // 更新间隔2秒 if (currentMillis - lastUpdate interval) { lastUpdate currentMillis; // 1. 读取并显示温湿度 readDHT(); // 2. 读取并显示CO2 readCO2(); // 3. 读取并显示颗粒物 readPMS(); // 更新屏幕显示 updateDisplay(); } // 可以在这里添加其他非实时任务 }DHT11数据读取函数示例void readDHT() { float h dht.readHumidity(); float t dht.readTemperature(); // 检查数据是否有效 if (isnan(h) || isnan(t)) { Serial.println(Failed to read from DHT sensor!); // 可以在这里设置默认值或错误标志 return; } // 将读取到的值存入全局变量供显示函数使用 humidity h; temperature t; }MH-Z14A PWM模式读取CO2原理与代码MH-Z14A的PWM引脚会输出一个周期为1004ms的方波其中高电平的持续时间与CO2浓度成正比。浓度范围0-5000ppm对应高电平持续时间0-1004ms。我们需要测量一个完整周期内高电平的时间。void readCO2() { unsigned long th, tl, ppm; do { th pulseIn(CO2_PWM_PIN, HIGH, 1004000) / 1000; // 测量高电平时间单位微秒转毫秒 tl 1004 - th; // 计算低电平时间 } while (th 0 || tl 0); // 确保读到有效脉冲 // 根据公式计算ppm (th - 2) * 5 ppm (th - 2) * 5; co2ppm ppm; // 存入全局变量 }实操心得pulseIn函数可能会因为干扰而返回0。用do...while循环可以确保读到有效的脉冲宽度。测量时最好远离电机、继电器等大电流设备以减少干扰。PMS5003数据读取与解析PMS库大大简化了数据读取过程。我们需要先向传感器发送请求数据的命令然后等待并解析返回的数据包。void readPMS() { pms.wakeUp(); // 唤醒传感器如果之前休眠了 delay(100); // 等待传感器准备 pms.requestRead(); // 发送请求数据命令 if (pms.readUntil(data)) { // 尝试读取数据直到成功或超时 pm1 data.PM_AE_UG_1_0; pm25 data.PM_AE_UG_2_5; pm10 data.PM_AE_UG_10_0; Serial.print(PM1.0: ); Serial.print(pm1); Serial.print(, PM2.5: ); Serial.print(pm25); Serial.print(, PM10: ); Serial.println(pm10); } else { Serial.println(PMS read failed.); } pms.sleep(); // 读取后让传感器休眠延长寿命 }屏幕显示函数updateDisplay()这是与用户交互的界面。我们可以设计一个简单的多屏轮播界面。void updateDisplay() { display.clearDisplay(); display.setCursor(0, 0); // 第一屏温湿度 display.setTextSize(2); display.print(T:); display.print(temperature, 1); // 显示一位小数 display.println( C); display.print(H:); display.print(humidity, 0); // 湿度取整 display.println( %); display.setTextSize(1); display.setCursor(0, 50); display.print(CO2:); display.print(co2ppm); display.print( ppm); display.display(); delay(2500); // 本屏显示2.5秒 display.clearDisplay(); display.setCursor(0, 0); // 第二屏颗粒物 display.setTextSize(2); display.println(PM2.5:); display.println(pm25); display.setTextSize(1); display.setCursor(0, 50); display.print(PM1:); display.print(pm1); display.print( PM10:); display.print(pm10); display.display(); delay(2500); // 本屏显示2.5秒 }这样屏幕就会在温湿度/CO2界面和颗粒物界面之间每2.5秒切换一次信息呈现清晰不拥挤。5. 系统集成、校准与优化5.1 组装与外壳选择当所有模块在面包板上测试无误后可以考虑进行永久性组装。你可以将元件焊接在一块洞洞板万用板上或者设计一块简单的PCB这样更稳固。对于外壳3D打印是最灵活的选择你可以根据屏幕和传感器的位置设计一个带通风孔和支架的盒子。也可以找一个现成的塑料盒用手工开孔。关键点PMS5003需要空气流通外壳上对应其进气口和出气口的位置一定要开孔且不要被其他元件或线缆阻挡。5.2 传感器校准与数据可信度DIY设备的数据准确性是大家最关心的。这里有一些校准和验证的思路DHT11其精度本身有限但一致性尚可。你可以将其读数与一个你信任的温湿度计放在同一环境中对比记录下系统性的偏差如温度恒定偏高1°C然后在代码中减去这个偏移量。MH-Z14A这款传感器出厂时已经过校准通常比较准确。有一个简单的“新鲜空气校准法”将传感器放置在室外通风良好处避免汽车尾气等污染运行至少20分钟此时空气中的CO2浓度接近全球背景值约410-420ppm。如果读数偏差较大可以查阅传感器手册通过发送特定的串口指令进行零点校准。注意切勿在室内或人员密集处进行此操作否则会将错误的高浓度值设为基准。PMS5003激光传感器容易受到湿气、油性颗粒的影响。其读数与专业设备相比在大多数室内环境下趋势一致绝对值可能存在一定偏差。保持传感器清洁至关重要。可以用棉签轻轻清洁激光腔的进气口切勿触碰内部的激光头和透镜。验证时可以点燃一支香在远处制造烟雾观察PM2.5数值是否快速上升来定性判断其灵敏度。5.3 功耗优化与长期运行如果你想用电池供电或长期挂机功耗是需要考虑的。降低屏幕功耗让OLED屏幕在不需要看的时候关闭display.ssd1306_command(SSD1306_DISPLAYOFF)或者降低刷新频率。传感器间歇工作让PMS5003长期处于休眠模式pms.sleep()只在需要读数时唤醒。MH-Z14A功耗相对较高但无法深度休眠可以考虑间隔较长时间如5分钟读取一次CO2。降低Arduino主频通过修改熔丝位可以降低Arduino的工作电压和频率但这属于高级操作且可能影响部分库的时序。使用深睡眠模式让Arduino本身也进入深度睡眠定时由看门狗或外部中断唤醒。这需要更复杂的编程并可能影响串口通信的稳定性。对于家庭插电使用通常无需特别优化。但如果你发现设备发热较大可以检查是否有短路或某个传感器异常耗电。6. 数据记录、可视化与物联网拓展一个能显示实时数据的监测站已经很有用了但如果能让数据说话——生成曲线图、历史记录甚至远程查看那它的价值将大大提升。6.1 本地数据记录SD卡模块添加一个SD卡模块SPI接口可以让Arduino将数据以CSV格式定期写入存储卡。这样你就能获得长时间的历史数据导入电脑用Excel进行分析。接线需要连接MOSI、MISO、SCK、CS四个SPI引脚。代码上你需要引入SD.h库并在每次读取传感器后打开文件追加一行包含时间戳和所有数据的数据然后关闭文件。避坑技巧SD卡操作比较耗时不要在loop()的每次循环中都打开关闭文件这会导致卡顿和卡寿命缩短。可以设置一个写卡间隔如每5分钟或者将数据先缓存在内存中攒够一定数量再一次性写入。6.2 连接物联网平台以Blinker/Home Assistant为例让数据上云实现远程监控和智能联动是物联网项目的精髓。使用Blinker/点灯科技这是一个对国内开发者非常友好的物联网平台。你可以在Arduino上集成Blinker库通过Wi-Fi模块如ESP8266或ESP32将数据上传到手机App。你甚至可以在App上设置报警阈值当PM2.5超标时给你发推送通知。注意这需要将主控从Arduino UNO更换为自带Wi-Fi的ESP8266开发板如NodeMCU或者为UNO额外添加一个ESP-01S模块编程复杂度会有所增加。接入Home Assistant如果你在运行Home Assistant智能家居系统可以将这个监测站变成一个传感器。一种方法是通过ESPHome固件直接刷写到ESP8266上配置非常直观。另一种方法是通过Arduino的串口将数据发送到运行Home Assistant的服务器如树莓派上再使用串口传感器组件来接收。这样你就能在HA的仪表盘上看到漂亮的图表并创建自动化例如“当客厅CO2浓度超过800ppm时自动打开新风系统”。6.3 简单的本地可视化Processing或Python即使不上云你也可以用电脑上的软件做出酷炫的图表。Arduino通过USB串口持续输出数据在loop里使用Serial.print电脑端用一个程序接收并解析这些数据。使用Processing这是一个非常适合可视化的编程语言。你可以写一个简单的Processing脚本读取串口数据实时绘制出温湿度、CO2、PM2.5的折线图效果非常直观。使用Python Matplotlib如果你熟悉Python可以用pyserial库读取串口用matplotlib库绘制实时动态图表。这给了你更大的灵活性比如计算24小时平均值生成日报等。从一个小巧的桌面监测站到拥有历史记录的数据记录仪再到融入智能家居的云端传感器这个项目的拓展路径非常清晰。每一步的升级都会让你对物联网系统有更深的理解。最重要的是你创造的这个工具提供的数据是真实、透明、属于你自己的这份掌控感正是DIY最大的乐趣所在。