本教程将指导您制作一台专业级的空气质量检测仪。这个项目使用经济实惠的ESP8266和PMS5003传感器,配合OLED显示屏,不仅能实时显示PM2.5数值,还能通过手机APP随时查看数据。总成本70元,相比几百的用的便宜,用的心理踏实。
功能特点
- 实时检测PM2.5和PM10浓度
- OLED屏幕直观显示数据
- WiFi无线连接,支持远程数据查看
- 手机APP实时监控
- 支持数据历史记录和趋势分析
- 24小时持续监测
成品
网页
APP
硬件需求
ESP8266开发板(如NodeMCU、Wemos D1 mini等)10元
PMS5003/PMS7003系列颗粒物传感器 50元
0.96寸OLED显示屏(I2C接口,128x64分辨率)9元
若干杜邦线
接线说明
PMS传感器接线(5V供电):
VCC → ESP8266的VIN(5V)
GND → ESP8266的GND
TX → D6(GPIO12)
RX → D5(GPIO14)
OLED显示屏接线:
VCC → 3.3V
GND → GND
SDA → D2(GPIO4)
SCL → D1(GPIO5)
准备工作
Arduino IDE配置:
安装ESP8266开发板支持
安装必要的库:
WiFiManager(用于WiFi配置)
PubSubClient(MQTT客户端)
Adafruit GFX和Adafruit SSD1306(OLED驱动)
ArduinoJson(JSON数据处理)
使用说明
首次使用:
给设备上电后,会创建一个名为"AP"的WiFi热点
用手机连接该热点
在弹出的配置页面中设置WiFi信息
设备会自动连接配置好的WiFi
正常使用:
设备每3秒更新一次显示屏数据
每10秒向MQTT服务器发送一次数据
OLED屏幕显示实时PM2.5数值
代码
#include <ESP8266WiFi.h>
#include <WiFiManager.h>
#include <PubSubClient.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ArduinoJson.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <TimeLib.h>
// WiFi断开图标的位图数据
const uint8_t wifi_disconnected_icon[] PROGMEM = {
0b00000000,
0b00000000,
0b01111110,
0b00000000,
0b00111100,
0b00000000,
0b00011000,
0b00000000
};
// OLED显示屏设置
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// PMS传感器串口设置
#define PMS_RX 12 // D6
#define PMS_TX 14 // D5
SoftwareSerial pmsSensor(PMS_RX, PMS_TX);
// MQTT设置
const char* mqtt_server = "47.115.210.16";
const int mqtt_port = 1883;
const char* mqtt_username = "cc1fb73f-ec43-3b37-522";
const char* mqtt_password = "b5c6d62";
const char* mqtt_client_id = "mqtt_d59ea842-b79";
const char* mqtt_topic = "devices/telemetry";
// 时间设置
const long utcOffsetInSeconds = 8 * 3600; // UTC+8
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);
// 更新间隔设置
unsigned long lastMQTTPublish = 0;
const long MQTT_PUBLISH_INTERVAL = 10000; // MQTT发布间隔10秒
const long DISPLAY_UPDATE_INTERVAL = 3000; // 显示更新间隔3秒
WiFiClient espClient;
PubSubClient client(espClient);
bool isWiFiConnected = false; // WiFi连接状态标志
String currentTime = ""; // 当前时间字符串
// PMS数据结构
struct PMS_data {
uint16_t pm1_0;
uint16_t pm2_5;
uint16_t pm1_0_std;
uint16_t pm2_5_std;
bool valid;
} pms_data;
void setup() {
Serial.begin(115200);
Serial.println("\n启动中...");
// 初始化I2C和OLED显示屏
Wire.begin(4, 5); // SDA = GPIO4 (D2), SCL = GPIO5 (D1)
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println("OLED初始化失败");
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// 显示启动画面
showInitScreen();
// 初始化PMS传感器
Serial.println("初始化PMS传感器...");
pmsSensor.begin(9600);
pms_data.valid = false;
// 配置MQTT客户端
client.setServer(mqtt_server, mqtt_port);
client.setKeepAlive(60); // 设置keepalive为60秒
// 在后台启动WiFi配置
WiFiManager wifiManager;
wifiManager.setConfigPortalTimeout(30);
wifiManager.startConfigPortal("AP");
// 检查WiFi连接状态
isWiFiConnected = (WiFi.status() == WL_CONNECTED);
if(isWiFiConnected) {
timeClient.begin();
client.setServer(mqtt_server, mqtt_port);
}
// 立即开始读取和显示数据
readPMSdata();
updateDisplay();
}
void loop() {
static unsigned long lastDisplayUpdate = 0;
unsigned long currentMillis = millis();
// 更新WiFi状态
isWiFiConnected = (WiFi.status() == WL_CONNECTED);
// 如果WiFi连接,更新时间和MQTT
if (isWiFiConnected) {
timeClient.update();
currentTime = getFormattedTime();
if (!client.connected()) {
reconnectMQTT();
}
client.loop();
// MQTT发布
if (pms_data.valid && currentMillis - lastMQTTPublish >= MQTT_PUBLISH_INTERVAL) {
publishData();
lastMQTTPublish = currentMillis;
}
}
// 定期更新显示
if (currentMillis - lastDisplayUpdate >= DISPLAY_UPDATE_INTERVAL) {
readPMSdata();
updateDisplay();
lastDisplayUpdate = currentMillis;
}
}
String getFormattedTime() {
time_t rawtime = timeClient.getEpochTime();
struct tm * ti;
ti = localtime (&rawtime);
char buffer[20];
sprintf(buffer, "%02d:%02d:%02d", ti->tm_hour, ti->tm_min, ti->tm_sec);
return String(buffer);
}
bool readPMSdata() {
uint8_t buffer[32];
uint16_t sum = 0;
// 清空接收缓冲区
while(pmsSensor.available()) {
pmsSensor.read();
}
// 等待数据
delay(1000);
if (pmsSensor.available() < 32) {
pms_data.valid = false;
return false;
}
// 检查帧头
if (pmsSensor.read() != 0x42 || pmsSensor.read() != 0x4D) {
pms_data.valid = false;
return false;
}
// 读取剩余数据
pmsSensor.readBytes(buffer, 30);
// 计算校验和
sum = 0x42 + 0x4D;
for (int i = 0; i < 28; i++) {
sum += buffer[i];
}
// 验证校验和
if (sum != ((buffer[28] << 8) | buffer[29])) {
Serial.println("校验和错误");
pms_data.valid = false;
return false;
}
// 解析数据
pms_data.pm1_0_std = (buffer[4] << 8) | buffer[5];
pms_data.pm2_5_std = (buffer[6] << 8) | buffer[7];
pms_data.pm1_0 = (buffer[10] << 8) | buffer[11];
pms_data.pm2_5 = (buffer[12] << 8) | buffer[13];
// 打印调试信息
Serial.println("解析后的数据:");
Serial.printf("PM1.0: %d, PM2.5: %d\n",
pms_data.pm1_0, pms_data.pm2_5);
pms_data.valid = true;
return true;
}
void updateDisplay() {
display.clearDisplay();
// 标题栏
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(2, 2);
display.print("AIR QUALITY");
// WiFi状态指示
if (!isWiFiConnected) {
display.drawBitmap(SCREEN_WIDTH - 10, 2, wifi_disconnected_icon, 8, 8, SSD1306_WHITE);
}
// 上方分隔线
display.drawLine(0, 12, SCREEN_WIDTH, 12, SSD1306_WHITE);
// 显示PM2.5数据
display.setTextSize(1);
display.setCursor(8, 26);
display.print("PM2.5");
if (pms_data.valid) {
// PM2.5数值(反色显示)
String pm25Str = String(pms_data.pm2_5);
int16_t valueX = SCREEN_WIDTH - (pm25Str.length() * 12) - 8;
int16_t boxWidth = pm25Str.length() * 12 + 4;
int16_t boxHeight = 18;
display.fillRect(valueX - 2, 22, boxWidth, boxHeight, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK);
display.setTextSize(2);
display.setCursor(valueX, 23);
display.print(pm25Str);
// 显示单位
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(SCREEN_WIDTH - 30, 42);
display.print("ug/m3");
} else {
display.setTextSize(1);
display.setCursor(8, 30);
display.print("Reading sensor...");
}
// 下方分隔线
display.drawLine(0, SCREEN_HEIGHT - 12, SCREEN_WIDTH, SCREEN_HEIGHT - 12, SSD1306_WHITE);
// 显示时间(如果有)
if (isWiFiConnected && !currentTime.isEmpty()) {
display.setTextSize(1);
display.setCursor(2, SCREEN_HEIGHT - 10);
display.print(currentTime);
}
display.display();
}
void publishData() {
if (!isWiFiConnected || !pms_data.valid) return;
StaticJsonDocument<200> doc;
doc["pm25"] = pms_data.pm2_5;
doc["pm10"] = pms_data.pm1_0;
doc["time"] = currentTime;
char buffer[200];
serializeJson(doc, buffer);
if (client.publish(mqtt_topic, buffer)) {
Serial.println("MQTT数据发送成功");
Serial.println(buffer);
} else {
Serial.println("MQTT数据发送失败");
}
}
void reconnectMQTT() {
if (!isWiFiConnected) {
Serial.println("WiFi未连接,无法连接MQTT");
return;
}
// 打印当前网络状态
Serial.print("WiFi信号强度: ");
Serial.println(WiFi.RSSI());
Serial.print("本地IP: ");
Serial.println(WiFi.localIP());
int attempts = 0;
while (!client.connected() && attempts < 5) { // 增加重试次数
Serial.println("尝试MQTT连接...");
Serial.print("ClientID: ");
Serial.println(mqtt_client_id);
Serial.print("Username: ");
Serial.println(mqtt_username);
client.setSocketTimeout(10); // 设置socket超时时间为10秒
if (client.connect(mqtt_client_id, mqtt_username, mqtt_password)) {
Serial.println("MQTT已连接");
break;
} else {
Serial.print("MQTT连接失败, rc=");
Serial.print(client.state());
Serial.print(" 错误描述: ");
// 添加错误代码说明
switch(client.state()) {
case -4: Serial.println("MQTT_CONNECTION_TIMEOUT");
break;
case -3: Serial.println("MQTT_CONNECTION_LOST");
break;
case -2: Serial.println("MQTT_CONNECT_FAILED");
break;
case -1: Serial.println("MQTT_DISCONNECTED");
break;
case 1: Serial.println("MQTT_CONNECT_BAD_PROTOCOL");
break;
case 2: Serial.println("MQTT_CONNECT_BAD_CLIENT_ID");
break;
case 3: Serial.println("MQTT_CONNECT_UNAVAILABLE");
break;
case 4: Serial.println("MQTT_CONNECT_BAD_CREDENTIALS");
break;
case 5: Serial.println("MQTT_CONNECT_UNAUTHORIZED");
break;
}
attempts++;
delay(5000); // 增加重试间隔到5秒
}
}
}
void showInitScreen() {
display.clearDisplay();
display.setTextSize(2);
// 显示标题
const char* line1 = "Air PM2.5";
int16_t line1Width = strlen(line1) * 12;
display.setCursor((SCREEN_WIDTH - line1Width) / 2, 16);
display.println(line1);
const char* line2 = "Monitor";
int16_t line2Width = strlen(line2) * 12;
display.setCursor((SCREEN_WIDTH - line2Width) / 2, 32);
display.println(line2);
display.display();
delay(1000); // 减少启动画面显示时间
}
传感器手册