Air Quality AQI Monitor: PMS5003 Dust Sensor Project
India has 9 of the world’s 10 most polluted cities by PM2.5 levels, making an AQI monitor with PMS5003 dust sensor one of the most practically valuable Arduino/ESP32 projects for Indian makers. The PMS5003 from Plantower measures PM1.0, PM2.5, and PM10 particulate matter using laser light scattering with laboratory-grade accuracy. This guide covers complete wiring, firmware, AQI calculation using India’s CPCB standards, and IoT-connected air quality monitoring for homes and workplaces.
India’s Air Quality Crisis and AQI Standards
India’s Central Pollution Control Board (CPCB) defines the National Ambient Air Quality Standards (NAAQS) and the Air Quality Index (AQI). Unlike WHO guidelines, India’s AQI uses 8 pollutants: PM2.5, PM10, NO₂, SO₂, CO, O₃, NH₃, and Pb. For DIY monitors, PM2.5 and PM10 are the most practically measurable.
| AQI Range | Category | PM2.5 (µg/m³) | PM10 (µg/m³) |
|---|---|---|---|
| 0-50 | Good | 0-30 | 0-50 |
| 51-100 | Satisfactory | 31-60 | 51-100 |
| 101-200 | Moderate | 61-90 | 101-250 |
| 201-300 | Poor | 91-120 | 251-350 |
| 301-400 | Very Poor | 121-250 | 351-430 |
| 401-500 | Severe | >250 | >430 |
PMS5003 Sensor Specifications
- Particle size measured: PM1.0, PM2.5, PM10 (µg/m³)
- Measurement range: 0-500 µg/m³ (0-1000 µg/m³ max)
- Accuracy: ±10 µg/m³ @ PM2.5 < 100 µg/m³; ±10% above
- Interface: UART 9600 baud (3.3V TTL)
- Supply voltage: 5V ± 0.5V
- Current: 100mA typical (fan motor), 200mA peak
- Response time: ≤10 seconds to stable reading
- Operating temperature: -10°C to 60°C (covers all Indian conditions)
- Lifetime: >3 years (fan-based design, monitor fan noise for end-of-life)
- India price: ₹800-1400
UART Wiring with Arduino and ESP32
/* PMS5003 Wiring
*
* PMS5003 Pin Color Arduino (SW Serial) ESP32
* VCC Purple 5V 5V (use USB power)
* GND Purple GND GND
* SET White Digital 7 (optional) GPIO 5 (optional)
* RXD White Pin 10 (SoftSerial) GPIO 17 (UART2 TX)
* TXD White Pin 11 (SoftSerial) GPIO 16 (UART2 RX)
* RESET White Optional Optional
* NC Green Not connected Not connected
* NC Green Not connected Not connected
*
* WARNING: PMS5003 uses 3.3V logic on UART, NOT 5V!
* If using Arduino 5V: Add 1kΩ voltage divider on TXD line
* PMS5003 TXD → 1kΩ → Arduino pin
* → 2kΩ → GND
* (This divides 3.3V to 2.2V, still reads as HIGH on Arduino)
*
* ESP32: Direct connection (both use 3.3V UART)
*/
PMS5003 Data Protocol and Parsing
// PMS5003 Arduino Code with AQI Calculation
// Using SoftwareSerial for Arduino Uno (limited RAM for full parsing)
// For Arduino Mega or ESP32: use hardware Serial1/Serial2
#include <SoftwareSerial.h>
SoftwareSerial pmsSerial(11, 10); // RX, TX
struct PMS5003Data {
uint16_t PM_SP_UG_1_0; // Standard particle PM1.0 (µg/m³)
uint16_t PM_SP_UG_2_5; // Standard particle PM2.5
uint16_t PM_SP_UG_10_0; // Standard particle PM10
uint16_t PM_AE_UG_1_0; // Atmospheric environment PM1.0
uint16_t PM_AE_UG_2_5; // Atmospheric environment PM2.5 (use this!)
uint16_t PM_AE_UG_10_0; // Atmospheric environment PM10 (use this!)
uint16_t particle_0_3; // Particles >0.3µm per 0.1L
uint16_t particle_0_5;
uint16_t particle_1_0;
uint16_t particle_2_5;
uint16_t particle_5_0;
uint16_t particle_10_0;
};
PMS5003Data data;
bool readPMS5003() {
// PMS5003 frame: 0x42 0x4D + 28 bytes data + 2 bytes checksum = 32 bytes total
if (pmsSerial.available() < 32) return false;
// Find frame start
if (pmsSerial.read() != 0x42) return false;
if (pmsSerial.read() != 0x4D) return false;
// Read 28 data bytes + 2 checksum
uint8_t buf[28];
uint16_t checksum = 0x42 + 0x4D;
for (int i = 0; i < 28; i++) {
buf[i] = pmsSerial.read();
if (i < 26) checksum += buf[i];
}
// Verify checksum
uint16_t receivedChecksum = (buf[26] << 8) | buf[27];
if (checksum != receivedChecksum) {
Serial.println("PMS5003 checksum error!");
return false;
}
// Parse 16-bit values (big-endian)
data.PM_SP_UG_1_0 = (buf[2] << 8) | buf[3];
data.PM_SP_UG_2_5 = (buf[4] << 8) | buf[5];
data.PM_SP_UG_10_0 = (buf[6] << 8) | buf[7];
data.PM_AE_UG_1_0 = (buf[8] << 8) | buf[9];
data.PM_AE_UG_2_5 = (buf[10] << 8) | buf[11];
data.PM_AE_UG_10_0 = (buf[12] << 8) | buf[13];
return true;
}
void setup() {
Serial.begin(115200);
pmsSerial.begin(9600);
Serial.println("PMS5003 AQI Monitor Ready");
delay(5000); // Allow sensor fan to stabilize
}
void loop() {
if (readPMS5003()) {
int aqi_pm25 = calculateAQI_PM25(data.PM_AE_UG_2_5);
int aqi_pm10 = calculateAQI_PM10(data.PM_AE_UG_10_0);
int overallAQI = max(aqi_pm25, aqi_pm10); // Highest sub-index = AQI
Serial.print("PM2.5: "); Serial.print(data.PM_AE_UG_2_5);
Serial.print(" µg/m³ | PM10: "); Serial.print(data.PM_AE_UG_10_0);
Serial.print(" µg/m³ | AQI: "); Serial.print(overallAQI);
Serial.print(" - "); Serial.println(getAQICategory(overallAQI));
}
delay(2000);
}
String getAQICategory(int aqi) {
if (aqi <= 50) return "Good";
if (aqi <= 100) return "Satisfactory";
if (aqi <= 200) return "Moderate";
if (aqi <= 300) return "Poor";
if (aqi <= 400) return "Very Poor";
return "SEVERE - Hazardous!";
}
CPCB AQI Calculation Formula
// India CPCB AQI Calculation (sub-index formula)
// AQI = [(IHi - ILo) / (CHi - CLo)] × (Cp - CLo) + ILo
// Where: Cp = measured concentration, C/I = breakpoint table values
int calculateAQI_PM25(float pm25) {
// CPCB PM2.5 breakpoints (24-hour average)
// [concentration_low, concentration_high, aqi_low, aqi_high]
float breakpoints[][4] = {
{0, 30, 0, 50}, // Good
{30.1, 60, 51, 100}, // Satisfactory
{60.1, 90, 101, 200}, // Moderate
{90.1, 120, 201, 300}, // Poor
{120.1, 250, 301, 400}, // Very Poor
{250.1, 1000, 401, 500} // Severe
};
for (int i = 0; i = breakpoints[i][0] && pm25 <= breakpoints[i][1]) {
float aqi = ((breakpoints[i][3] - breakpoints[i][2]) /
(breakpoints[i][1] - breakpoints[i][0])) *
(pm25 - breakpoints[i][0]) + breakpoints[i][2];
return (int)aqi;
}
}
return 500; // Beyond scale
}
int calculateAQI_PM10(float pm10) {
float breakpoints[][4] = {
{0, 50, 0, 50},
{50.1, 100, 51, 100},
{100.1, 250, 101, 200},
{250.1, 350, 201, 300},
{350.1, 430, 301, 400},
{430.1, 1000, 401, 500}
};
for (int i = 0; i = breakpoints[i][0] && pm10 <= breakpoints[i][1]) {
float aqi = ((breakpoints[i][3] - breakpoints[i][2]) /
(breakpoints[i][1] - breakpoints[i][0])) *
(pm10 - breakpoints[i][0]) + breakpoints[i][2];
return (int)aqi;
}
}
return 500;
}
Recommended Product
5V Active Buzzer Module for Arduino
Add audible AQI alerts — buzzer activates when PM2.5 exceeds 90 µg/m³ (Moderate to Poor) for indoor air quality warnings in homes and offices.
Category: Audio & Sound Modules
IoT Dashboard with ThingSpeak
// ESP32 PMS5003 + ThingSpeak IoT Dashboard
// Free ThingSpeak account: 3 million messages/year
// Update interval: Every 15 seconds minimum (ThingSpeak limit)
#include <WiFi.h>
#include <HTTPClient.h>
#define THINGSPEAK_KEY "YOUR_WRITE_API_KEY"
const char* ssid = "YOUR_WIFI";
const char* password = "YOUR_PASSWORD";
void sendToThingSpeak(float pm25, float pm10, int aqi) {
if (WiFi.status() != WL_CONNECTED) return;
HTTPClient http;
String url = "http://api.thingspeak.com/update?api_key=";
url += THINGSPEAK_KEY;
url += "&field1=" + String(pm25, 1);
url += "&field2=" + String(pm10, 1);
url += "&field3=" + String(aqi);
http.begin(url);
int httpCode = http.GET();
if (httpCode == 200) {
Serial.println("ThingSpeak updated!");
}
http.end();
}
// Visualize at: https://thingspeak.com/channels/YOUR_CHANNEL_ID
// Share public URL with neighbors for community monitoring
Sensor Enclosure and Deployment Tips
Proper enclosure is critical for outdoor PMS5003 deployment in India’s harsh climate:
- Gill screen: Use a radiation/solar shield around the sensor to prevent direct sunlight heating (which falsely elevates readings). Commercial radiation shields cost ₹500-800; DIY with white PVC pipe sections works equally well.
- Fan orientation: PMS5003 has a built-in fan. Mount with air inlet facing downward (prevents rain ingress) and outlet facing down-wind.
- Monsoon protection: Add Gore-Tex or PTFE membrane filter over air inlet to block humidity above 85% RH — PMS5003 overreads PM at high humidity due to hygroscopic particle growth.
- Height: Mount at 1.5-2m above ground (breathing height) for human exposure monitoring; 10m above ground for ambient air quality comparison with CPCB stations.
Recommended Product
8-Channel Solid State Relay Module for Arduino
Control HEPA air purifiers, ventilation systems, or building management HVAC based on AQI readings — full automation response to air quality conditions.
Category: Industrial Automation
Frequently Asked Questions
Q: Is PMS5003 accurate enough to compare with official CPCB stations?
A: PMS5003 typically reads 1.2-1.5× higher than regulatory reference instruments (beta attenuation monitors) in Indian urban environments. This is because PMS5003 uses a generic particle refractive index assumption, while Indian urban PM is denser (higher BC/mineral dust content). Apply a correction factor: PM25_corrected = PMS5003_reading × 0.7 for North Indian cities. For South India and coastal areas, factor ≈ 0.75-0.80.
Q: How often should I replace the PMS5003 fan?
A: Rated for >3 years of continuous operation. Signs of fan degradation: increased noise level, reduced counting efficiency (lower readings than expected), erratic readings. In India’s dusty conditions (especially construction zones or high-PM areas), service life may be 18-24 months. The entire sensor requires replacement (fan not separately replaceable in practice).
Q: Why does PM reading spike at certain times of day?
A: Common patterns in India: (1) 6-9 AM: cooking smoke from neighbors, garbage burning, morning traffic, (2) 12-2 PM: peak traffic + solar-induced boundary layer mixing bringing down upper-level pollution, (3) 7-10 PM: evening traffic, diesel generator exhausts, post-sunset calm winds concentrating pollutants. Log data over weeks to see your location’s daily pattern.
Q: What level of PM2.5 requires wearing a mask indoors in India?
A: Based on CPCB guidelines and health studies: Indoor PM2.5 >35 µg/m³ (24h average) indicates potential health impacts for sensitive groups. Above 90 µg/m³ (AQI Moderate) warrants N95 mask use during prolonged exposure. Many Delhi homes reach 100-200 µg/m³ indoors during winter inversions even with closed windows.
Q: Can PMS5003 detect smoke from crop burning?
A: Yes — crop burning smoke (common in Punjab, Haryana, western UP in Oct-Nov) produces extremely high PM2.5 readings (often 200-500+ µg/m³) with distinctive organic carbon signature. PMS5003 readings during these events closely correlate with CPCB station data and satellite-derived aerosol measurements.
Add comment