☰
基于Arduino与ESP8266的温湿度及PM2.5环境监测系统设计
2026/10/24 6:00:54 网站建设 项目流程

拿到这个标题,第一反应就是典型的“毕业设计/课程设计热门款”加“个人DIY环境监测入门标配”。但说真的,把它做好做稳并不简单,很多照着网上教程接线的人最后都卡在数据飘、连不上网、读数对不齐这几个地方。我这篇不打算只给一份能用就行的方案,而是把设计思路、传感器选型、电路处理、代码框架、联网调试这些环节全部拆开讲一遍,顺便把踩过的坑也说清楚,希望能帮你少走几趟弯路。

这个项目能完成的事情其实很直观:一块Arduino(或者直接上带WiFi的ESP8266/ESP32开发板),接上温湿度传感器和PM2.5传感器,定期采集数据,通过WiFi上传到服务器或者局域网里的小程序/网页,最终实现用手机随时查看房间、工位、大棚或者工作室的空气状况。适合的对象也很明确:刚学完单片机基础、想尝试“硬件上云”的开发者,以及需要给现有环境做个低成本监测方案而没有头绪的爱好者。

1. 项目整体设计与硬件选型

1.1 先想清楚要测什么、传到哪、怎么看

不要一上来就买一堆模块,先把需求拆成三个问题:测什么参数、要不要显示、数据给谁看。这个项目标题里固定了温湿度、PM2.5、WiFi联网三样,那最基本的目标就是:定时测出这三项数据,让数据能通过WiFi送出去。至于送到哪,我建议第一版先送局域网内的PC或手机端调试,跑通后再对接公网云平台。

这里有一个容易被忽略的设计取舍:Arduino Uno本身不带WiFi,要联网就得外挂ESP8266模块,两个MCU之间还得走串口通信,复杂度一下就上去了。如果标题里没有强制“必须用Arduino Uno”,我强烈建议直接用NodeMCU或ESP32这类自带WiFi的开发板。它们仍然是Arduino生态(Arduino IDE直接开发),但省去了主控与WiFi模块之间的握手逻辑,稳定性能好很多。下面所有内容都以“ESP8266 NodeMCU + Arduino IDE”为主线,同时也会说明和Uno的区别,这样你无论手头有什么都能套用。

1.2 传感器选型:温湿度选DHT22还是SHT30,PM2.5选激光还是红外

温湿度模块是这类的老熟人了,DHT11和DHT22几乎每个教程都会出现。我的建议很直接:预算不是特别紧就上DHT22,成本和DHT11的差价也就是一杯饮料的钱,但精度和稳定性差距肉眼可见。DHT11的湿度误差可以达到±5%,放在空气质量场景里,数据波动大到没法看;DHT22能做到±2%左右,温度精度也能到±0.5°C,家用监测足够。再往上可以选SHT30,I2C接口,不用自己写时序,读数据更稳,就是需要一点库配置经验。

PM2.5传感器比较坑,因为网上便宜的红外式模块(夏普GP2Y1010AU0F为代表)模拟输出,需要运放电路处理,而且对灰尘的辨识度一般,做做定性趋势还可以,当真数据有点勉强。如果要做可信的数值上传,建议选激光散射式的串口模块,比如PMS5003、PMS7003。这类模块内置风扇,串口直接输出颗粒物质量浓度(单位ug/m3),有PM1.0、PM2.5、PM10三组数据,精度靠谱得多。虽然贵几十块,但省掉一堆信号调理电路,开发时间省回来,完全划算。

WiFi这块,如果按“Arduino Uno+ESP8266”的老路线,你需要给ESP8266刷AT固件,然后用SoftwareSerial发指令,什么“AT+CIPSTART”“AT+CIPSEND”,调试起来很折磨人。直接用NodeMCU开发板就轻松多了,固件、引脚、电源都排好了,按照我们常说一句,别自己造车轮。

1.3 供电与结构:很多不稳定的源头是电

传感器和WiFi模块混在一起时,电源问题会被放大。PM2.5激光传感器内部有个风扇,启动瞬间电流能到100mA以上,ESP8266在发射WiFi时电流也会突然飙升(峰值可能接近300-400mA)。如果全部靠Arduino的3.3V引脚供电,电压很容易被拉低,程序就会莫名其妙重启,数据也会出现跳变。

我的经验是:外接5V/2A适配器,通过USB进NodeMCU板,传感器的VCC尽量从5V引脚取(只要传感器支持5V输入),并把每个传感器模块上的板载稳压降到3.3V给逻辑部分用。DHT22可以用5V供电,PMS5003的VCC用5V供电完全没有问题。这样就不用纠结电流不够的问题了。如果坚持用电池供电,至少要选18650两节或一个5V/2A的移动电源芯片方案,不要指望USB口带得动全部外设。

结构上,PM2.5传感器要保证进出风口不被遮挡,最好固定在设备外壳的侧边让空气可以自然流入;DHT22要避免贴到发热元件,也不能放在阳光直晒的位置。我自己见过一个案例,把DHT22放在ESP8266正上方,结果温度永远比其他房间温度高3度,一看就是被板子烤的。

2. 电路搭建与传感器细节解析

2.1 接线图与引脚分配

假设你用NodeMCU开发板,引脚分配可以这样:

  • DHT22 Data脚 -> D4(GPIO2),供电接5V,GND接GND,Data与5V之间建议加一个4.7kΩ到10kΩ上拉电阻(有些模块板载已带,不用重复加)。
  • PMS5003串口发送数据到NodeMCU的D3(GPIO0)和D2(GPIO4)。注意方向:PMS5003的TXD接NodeMCU的RX引脚,PMS5003的RXD接NodeMCU的TX引脚。如果板上引脚名不好区分,就记住交叉接。
  • 如果加OLED屏幕(128x64,I2C),SDA接D1(GPIO5),SCL接D2(GPIO4)。

这里有个比较关键的坑:NodeMCU的部分引脚在启动时保持特定电平,不能乱接。比如GPIO2和GPIO0如果在启动时被拉低,会进入下载模式,导致板子无法正常启动。DHT22接在D4(GPIO2)一般没问题,因为DHT的Data线空闲时是高的;但如果有些模块带LED指示灯影响电平,就要注意。最保险的办法是把传感器接到D5、D6这类普通IO口,避开启动敏感的GPIO0/2/15。

PMS5003是3.3V TTL串口,NodeMCU也是3.3V逻辑,可以直接相连。如果你用Arduino Uno(5V逻辑),就要用电平转换芯片,不能直接接,否则会烧模块。

2.2 温湿度传感器的时序原理和拉高问题

DHT22/DHT11的数据引脚用的是单总线协议,时序要求比较严格。程序发送一个低电平起始信号,传感器应答后开始连续输出40位数据。最常见的问题是:如果延时精度不够,或者线路过长导致边沿变缓,读出来的数据就可能一直是0、NaN或校验错误。

DHT类库现在主流是DHT-sensor-library,用起来很简单,但底层也是靠sleep和digitalRead去模拟时序,对中断不友好的环境会很脆。如果你在代码里同时开启了WiFi,ESP8266的底层库会频繁使用中断,这确实可能干扰DHT的时序。我试过的稳定方案是:读取DHT数据前,先短暂关闭WiFi(WiFi.disconnect())或者使用DHT的异步读取方式,再或者换成带I2C接口的SHT30,彻底绕开时序问题。这算是在这个项目上最有价值的避坑点之一。

2.3 PM2.5激光传感器串口数据解析

PMS5003输出的是被动模式下的串口数据包,每帧32字节,以0x42 0x4D开头(ASCII字符“BM”),后面跟着帧长度、各粒径浓度数据、CRC校验。标准做法是写一个状态机,在串口缓冲里找帧头,读到32字节后校验,再从第5、7、9字节提取PM1.0、PM2.5、PM10浓度值(16位整数)。

很多教程直接给一个简单版解析函数,只在空闲时间读,容易出现数据错位。建议处理方式:在串口事件里逐字节读取,缓存到数组,每拿到一个新字节就尝试匹配帧头,累计到预期长度后做校验。这样的状态机代码几十行而已,但稳定度提升很大,再也不会被一个乱码字节毁掉整帧数据。

如果你选的是GP2Y1010AU0F这种模拟输出模块,那就要用ADC采样,而且必须配合脉冲驱动和运放,复杂度完全不在一个量级。不是玩玩而已的话,我劝你直接换PMS系列,省心太多。

3. 软件设计与核心代码实现

3.1 程序整体框架:状态机式还是轮询式

考虑到WiFi、传感器、显示三个任务并发,最自然的写法是轮询:主循环里分时处理,比如每2秒读一次PM2.5,每5秒读一次DHT,每10秒上传一次数据。这样避免了把传感器读取放在中断里导致的各种莫名崩溃。

当然也可以用定时器或millis()控制节奏,而不是delay()。delay()会阻塞整个循环,WiFi底层协议栈无法及时收包,连接和重连都会变得迟钝。你自己想一下,WiFi模块也需要“呼吸”来维持连接,你把它的氧气全堵了,它当然要罢工。建议所有时间控制都基于millis()的状态判断,把“现在该干什么”写在主循环里。

3.2 温湿度读取代码(DHT22)

以DHT-sensor-library为例,核心代码很短:

#include <DHT.h> #define DHTPIN 4 // 注意:这是D2引脚对应的GPIO4,根据你实际接线改 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); void setup() { Serial.begin(115200); dht.begin(); } void loop() { float h = dht.readHumidity(); float t = dht.readTemperature(); if (isnan(h) || isnan(t)) { Serial.println("DHT read failed"); return; } Serial.printf("Temp: %.1f C, Hum: %.1f%%\n", t, h); delay(2000); }

在实际项目里,建议把readTemperature(false)写完整,默认返回值就是摄氏度。如果你的传感器是DHT11,把DHTTYPE改成DHT11就行。注意读失败的次数可能不少,至少要做3次重试,连续失败超过5次再报警,不要读到一次失败就认为传感器坏了。

3.3 PM2.5读取代码(PMS5003状态机解析)

下面是简化了但很实用的状态机代码,能直接放进你的项目:

#include <SoftwareSerial.h> #define PMS_RX 5 // 接PMS5003 TXD -> NodeMCU D1(GPIO5) #define PMS_TX 4 // 接PMS5003 RXD -> NodeMCU D2(GPIO4)(如果不发命令可以不接) SoftwareSerial pms(PMS_RX, PMS_TX); uint8_t buf[32]; int bufIndex = 0; bool frameStarted = false; void setup() { Serial.begin(115200); pms.begin(9600); } void loop() { while (pms.available()) { uint8_t byte = pms.read(); if (bufIndex == 0 && byte != 0x42) continue; if (bufIndex == 1 && byte != 0x4D) { bufIndex = 0; continue; } buf[bufIndex++] = byte; if (bufIndex == 32) { handleFrame(buf); bufIndex = 0; } } } void handleFrame(uint8_t* data) { // 校验 uint16_t sum = 0; for (int i = 0; i < 30; i++) sum += data[i]; uint16_t check = (data[30] << 8) | data[31]; if (check != sum) { Serial.println("PM2.5 checksum error"); return; } uint16_t pm25 = (data[5] << 8) | data[6]; uint16_t pm10 = (data[7] << 8) | data[8]; Serial.printf("PM2.5: %d ug/m3, PM10: %d ug/m3\n", pm25, pm10); }

解析这部分的要点在于,必须把SoftwareSerial的波特率设为9600(PMS5003默认)。代码里没有发任何指令,只读取被动推送的数据,够用了。如果你想要传感器进入主动模式或者设置唤醒等,需要单独发指令,这里不多扩展。校验位很容易被忽视,但加上了之后能防止大量假数据,别省。

3.4 WiFi连接与数据上传

ESP8266连WiFi最简单的方法是使用ESP8266WiFi.h库,连接代码大家都会,但有几个细节决定稳不稳定。

第一,连接要带超时控制,不能无限while(WiFi.status() != WL_CONNECTED),否则路由器故障时程序就死在那了。我习惯写成最多尝试20次,每次500ms,仍然失败就重启或继续主循环,等下一次上传周期再重连。

第二,HTTP上传用HTTPClient库,向服务器POST JSON数据。下面是一个简单可用的上传函数:

#include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <ArduinoJson.h> const char* ssid = "你的WiFi名字"; const char* password = "你的WiFi密码"; const char* serverUrl = "http://192.168.1.100:8080/api/data"; void uploadData(float temp, float humi, int pm25) { if (WiFi.status() != WL_CONNECTED) { if (!connectWiFi()) return; } HTTPClient http; http.begin(serverUrl); http.addHeader("Content-Type", "application/json"); StaticJsonDocument<256> doc; doc["temp"] = temp; doc["humi"] = humi; doc["pm25"] = pm25; String body; serializeJson(doc, body); int code = http.POST(body); if (code > 0) { Serial.printf("Upload OK, code=%d\n", code); } else { Serial.printf("Upload failed, error=%s\n", http.errorToString(code).c_str()); } http.end(); } bool connectWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); int tries = 0; while (WiFi.status() != WL_CONNECTED && tries < 20) { delay(500); tries++; } return WiFi.status() == WL_CONNECTED; }

注意serverUrl第一版想简单调试,可以先在电脑上装个“处理接收数据的本地服务端”,后面再接入云平台。用什么服务端完全看你自己熟悉什么,只要能接收HTTP POST并显示数据就行。

如果你要对接常见的物联网云平台,一般在平台上创建产品、添加设备,拿到三个关键参数:设备ID、API Key、数据模板。然后把你上面的上传函数改一下URL和JSON字段名即可。不要一上来就上云,先用局域网IP调试通,再接入平台,排查起来会轻松很多。

4. 数据显示与报警联动:从“能传”到“能用”

4.1 加入OLED屏实现本地显示

虽然标题核心是WiFi联网,但现场没有屏幕调试时太痛苦。我习惯在项目里加一块0.96寸OLED,实时显示当前温湿度和PM2.5,顺便显示WiFi连接状态。I2C接口只用两根线,代码也不复杂。

显示用U8g2库或Adafruit_SSD1306库都可以。U8g2功能多,但字号、缓冲区设置比较烧脑;SSD1306库更轻量。我给出的建议是:先只显示一行“WiFi OK/NO”,再显示“T=25.3C H=60%”,最后显示“PM2.5=35”。OLED的刷新不要太频繁,每秒一次够用了,反正人眼刷新也看不出多大差别。

OLED的I2C地址一般是0x3C,如果你接好后屏幕不亮,用I2C扫描程序查一下地址,确认你的接线有没有问题。这算基础,但每次都能拦截一半以上的硬件通病。

4.2 超限报警与消息推送

数据传到服务器或者公网平台后,报警逻辑通常放云端或服务端处理,比放在单片机里更灵活。我的常用做法:在服务端配置阈值,比如PM2.5超过75ug/m3就触发报警。服务端收到数据后判断一次,超过阈值就通过Webhook方式把消息推到钉钉、企业微信、或某个“公众号模板消息”服务。这样设备端只负责上传,阈值可以随时改而不用刷固件。

如果什么都不想搭建,也可以在ESP8266代码里直接判断,超过阈值就让蜂鸣器响。蜂鸣器不要直接接GPIO,电流不够也容易伤引脚,用一个三极管或者模块驱动才行。

4.3 数据存储与历史曲线

只显示实时数值是“能用”,能查历史数据才是“好用”。最简单的方案是用一个极简的后端,接收JSON数据后写入数据库或CSV文件。如果你不想自己写后端,就直接用物联网平台,平台里都带数据存储和折线图功能,能省很多事。但要注意免费版的数据存储频率和条数有限制,上传间隔别低于1分钟。

我做过一个实际场景,在自己书房放了一个小盒子,每5分钟上传一次数据,跑了三个月,功率大概就几瓦。用平台自带的历史曲线对比天气变化,能发现下雨前湿度曲线会缓慢抬升,这些规律用来判断房间的通风条件非常有用。

5. 调试过程中的常见问题与避坑实录

5.1 DHT传感器读数为NaN或0

这个问题排名第一,原因一般有三个:接线错误、上拉电阻缺失、WiFi中断干扰时序。

排查步骤:

  • 用最简单的DHT例程,不打开WiFi,只读传感器,确认硬件没问题。
  • 如果例程正常、上电后依然间歇性报错,把DHT的Data线靠近电源线,减少干扰。
  • 如果开WiFi后才出问题,按前面说的,读取DHT前短暂暂停WiFi,或者换SHT30。
  • 检查数据线是否过长,超过50cm很容易失败。

5.2 PM2.5数据一直不变或为0

大概率是串口接线不对,或波特率不对,或者是信号线被其他库占用。PMS5003上电后有大约30秒稳定时间(风扇转起来),刚上电的数据可能偏低,等一会儿再看。如果你的传感器买到的是被动模式版本,可能不会连续输出数据,需要发主动模式指令,0x42 0x4D 0xE1 0x00 0x00 0x01 0x70,网上搜得到标准命令。

5.3 WiFi频繁断连重连

先排除供电问题。测一下NodeMCU的3.3V引脚电压,如果低于3.2V就该检查电源适配器了。再检查路由器信道和信号强度,2.4G信号最好保持在-60dBm以上。固件里加上自动重连机制,发现WiFi.status() != WL_CONNECTED就重新连接,而不是只连接一次。

另外,在代码里不要用delay阻塞太久。上传HTTP时如果服务器响应慢,整个循环会被卡住,底层WiFi协议栈无法维持连接。建议给HTTP请求设置http.setTimeout(3000),不要让它无限等。

5.4 数据跳变严重

干扰主要来自电源和线路布局。DHT22的信号线不要跟PMS5003的串口线绑在一起,两者分开放,用屏蔽线更好。PM2.5模块的风扇会产生振动,虽然数字传感器不受影响,但模拟传感器会被噪声害死。另外,传感器最好用短而粗的线连接电源地平面,我见过有人用杜邦线飞了20cm,数据完全没法看。

电源上可以在模块电源引脚就近并联一个100uF电解电容和0.1uF瓷片电容,这个组合能滤掉大部分毛刺。

5.5 数据上传频率与服务器压力

如果你用的是免费云平台,上传频率太密容易触发限流。我建议环境监测场景下,每分钟上传一次足够。如果做实时性要求高的场景,也要控制在10秒以上,毕竟传感器响应和空气变化没快到毫秒级。

5.6 问题速查表

现象大概率原因解决动作
DHT返回NaN时序被干扰/接线不良重插线、短接线、读取前暂停WiFi
PM2.5为0串口交叉错/波特率错检查TXD/RXD、串口9600
WiFi连不上路由器2.4G关闭/密码错确认SSID与密码、开启2.4G
设备反复重启电源电流不够换2A适配器,峰值电流要够
数据偶尔上传失败HTTP超时/网络抖动增加重试,超时缩短
采集值严重偏高传感器靠近热源移开散热元件,改善通风

6. 扩展思路与实际项目落地感想

如果你走完上面这些流程,你已经拥有一个完整可用的环境监测节点。再往下扩展,我可以给你几个方向:

  • 多节点组网:每个房间放一个子站,ESP8266连同一个路由器,数据上传到同一个服务端,按设备ID区分。
  • 硬件升级:把ESP8266换成ESP32,增加蓝牙和更大内存,可以接入更多传感器,甚至做本地离线分析。
  • 联动控制:检测到PM2.5超标,通过继电器打开空气净化器;检测到土壤湿度低,加水泵灌溉。这些都是同一套数据链路的不同出口。
  • 离线缓存:在断网时把数据存到Flash或SD卡,联网后再补传,这个在信号差的场景下非常好用。

我个人实际使用中最强烈的体会是:这个项目的难点从来不在代码本身,而在“稳定”这两个字。一个能跑三分钟的Demo和一台能连续运行三个月不垮的监测设备之间,隔着的是对电源、干扰、时序、重连机制的认真对待。尤其是WiFi上传部分,解决掉“掉线”和“数据错位”这两个问题,成就感会特别明显。

希望这篇复盘能帮你把这个经典的Arduino联网设计从“跑通”做到“靠谱”。如果你按这个思路做出来,欢迎回来分享你踩到的其他坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询