Created
February 15, 2025 03:22
-
-
Save fxprime/140285371f4cfa000606689a41dfaa5d to your computer and use it in GitHub Desktop.
Using ESP32 to measure PM2.5 using PMS3003
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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