Skip to content

Instantly share code, notes, and snippets.

@fxprime
Created February 15, 2025 03:22
Show Gist options
  • Save fxprime/140285371f4cfa000606689a41dfaa5d to your computer and use it in GitHub Desktop.
Save fxprime/140285371f4cfa000606689a41dfaa5d to your computer and use it in GitHub Desktop.
Using ESP32 to measure PM2.5 using PMS3003
#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_PCF8574.h>
#include <SoftwareSerial.h>
#include <WiFi.h>
#include <NTPClient.h> // Library : NTPClient by Fabrice Weinberg
#include <WiFiUdp.h>
#include <TimeLib.h> // Library : Time by Michael Margolis
#define WIFI_SSID "<SSID>" // change WIFI SSID
#define WIFI_PASSWORD "<PASS>" // change WIFI PASSWORD
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 7 * 3600, 60000); // Update every 60 seconds
LiquidCrystal_PCF8574 lcd(0x27);
EspSoftwareSerial::UART usbSerial;
#define PMSSERIAL usbSerial
bool pms_process();
void pmsSleep();
void pmsWakeUp();
int PM_SP_UG_1_0, PM_SP_UG_2_5, PM_SP_UG_10_0;
int PM_AE_UG_1_0, PM_AE_UG_2_5, PM_AE_UG_10_0;
String getFormattedDateTime() {
unsigned long rawTime = timeClient.getEpochTime();
setTime(rawTime);
char formattedDateTime[20];
sprintf(formattedDateTime, "%02d/%02d/%04d %02d:%02d:%02d", day(), month(), year(), hour(), minute(), second());
return String(formattedDateTime);
}
void connectToWiFi() {
uint32_t start = millis();
Serial.print("Connecting to Wi-Fi");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
if (millis() - start > 10000) {
Serial.println("==================== FAIL TO CONNECT WIFI < CHECK WIFI CREDENTIAL > ========================");
delay(100);
ESP.restart();
}
}
Serial.println();
Serial.print("Connected with IP: ");
Serial.println(WiFi.localIP());
Serial.println();
}
void network_init() {
// Connect to Wi-Fi
WiFi.setAutoReconnect(true);
connectToWiFi();
// Initialize NTP client
Serial.println("Time setting.");
timeClient.begin();
while (!timeClient.isTimeSet()) {
timeClient.update();
Serial.println(timeClient.getFormattedTime());
delay(1000);
}
Serial.println("Time is set.");
}
void setup() {
Serial.begin(115200);
usbSerial.begin(9600, EspSoftwareSerial::SWSERIAL_8N1, 19, 18, false, 95, 11);
network_init();
Serial.println(F("PMS sensor on SWSerial"));
lcd.begin(20, 4);
lcd.setBacklight(255);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Modulemore PM2.5 ");
lcd.setCursor(0, 1);
lcd.print(" Air Quality ");
lcd.setCursor(0, 2);
lcd.print(" Monitoring ");
lcd.setCursor(0, 3);
lcd.print(" DEMO ");
pmsWakeUp();
delay(1000);
}
void loop() {
if (pms_process()) {
Serial.print("PM2.5: ");
Serial.println(PM_SP_UG_1_0);
Serial.print("PM1.0: ");
Serial.println(PM_SP_UG_2_5);
Serial.print("PM10.0: ");
Serial.println(PM_SP_UG_10_0);
Serial.println();
lcd.clear();
lcd.setCursor(1, 0);
lcd.print(getFormattedDateTime());
lcd.setCursor(0, 1);
lcd.print("pm2.5: ");
lcd.print(PM_SP_UG_1_0);
lcd.print(" ug/m3");
lcd.setCursor(0, 2);
lcd.print("pm1.0: ");
lcd.print(PM_SP_UG_2_5);
lcd.print(" ug/m3");
lcd.setCursor(0, 3);
lcd.print("pm10.0: ");
lcd.print(PM_SP_UG_10_0);
lcd.print(" ug/m3");
pmsSleep();
delay(10000);
pmsWakeUp();
delay(5000);
}
}
bool pms_process() {
static uint8_t dataIndex = 0;
static uint16_t checksum = 0;
static uint16_t calculatedChecksum = 0;
static uint16_t frameLen = 0;
static uint8_t payload[30];
uint8_t ch;
while (usbSerial.available()) {
ch = usbSerial.read();
switch (dataIndex) {
case 0:
if (ch != 0x42) {
return false;
}
calculatedChecksum = ch;
break;
case 1:
if (ch != 0x4D) {
dataIndex = 0;
return false;
}
calculatedChecksum += ch;
break;
case 2:
frameLen = ch << 8;
calculatedChecksum += ch;
break;
case 3:
frameLen |= ch;
// Unsupported sensor, different frame length, transmission error e.t.c.
if (frameLen != 2 * 9 + 2 && frameLen != 2 * 13 + 2) {
dataIndex = 0;
return false;
}
calculatedChecksum += ch;
break;
default:
if (dataIndex == frameLen + 2) {
checksum = ch << 8;
} else if (dataIndex == frameLen + 2 + 1) {
checksum |= ch;
if (calculatedChecksum == checksum) {
PM_SP_UG_1_0 = (((uint16_t)payload[0]) << 8) | payload[1];
PM_SP_UG_2_5 = (((uint16_t)payload[2]) << 8) | payload[3];
PM_SP_UG_10_0 = (((uint16_t)payload[4]) << 8) | payload[5];
PM_AE_UG_1_0 = (((uint16_t)payload[6]) << 8) | payload[7];
PM_AE_UG_2_5 = (((uint16_t)payload[8]) << 8) | payload[9];
PM_AE_UG_10_0 = (((uint16_t)payload[10]) << 8) | payload[11];
} else {
Serial.println("Error checksum");
}
dataIndex = 0;
return true;
} else {
calculatedChecksum += ch;
uint8_t payloadIndex = dataIndex - 4;
if (payloadIndex < sizeof(payload)) {
payload[payloadIndex] = ch;
}
}
}
dataIndex++;
}
return false;
}
void pmsSleep() {
uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x00, 0x01, 0x73 };
PMSSERIAL.write(command, sizeof(command));
}
void pmsWakeUp() {
uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74 };
PMSSERIAL.write(command, sizeof(command));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment