Arduino Air Quality Sensor


The issue of poor air quality is a significant concern that impacts the well-being of numerous communities, particularly individuals with health conditions. The ability to measure air quality accurately is of utmost importance. In this module, you will have the opportunity to construct a basic air quality sensor using affordable and easily accessible components.Arduino Air Quality Sensor

Materials & Methods

  • The hardware components required for this project include an Arduino Uno board or an equivalent model, such as the one available at It is worth noting that we have used various inexpensive models from Amazon and SparkFun RedBoards, all of which have performed equally well as the traditional Arduino Uno.
  • To power the board, you can utilize a USB cord connected to a phone charger, a phone external battery, or a computer. Alternatively, you can use a 9V battery connection with an adapter like the one found at
  • The air quality sensor employed in this project is sourced from Plantower ( Our code is compatible with several models, including 1003, 3003, 5003, and 7003. However, please note that in the Arduino code, you will need to modify the variable “LENG” according to the specific model being used. The code contains detailed comments to guide you through this process. You can readily purchase these sensors from various sources.Materials Methods
    • To simplify the process of connecting wires for 5V and ground, a breadboard and male-to-female jumpers are recommended. A breadboard provides a convenient platform for organizing and connecting the necessary wires, especially when utilizing all the optional components in this module. Alternatively, you can create your own multi-pronged wires for 5V and ground by soldering.
    • You can find a variety of breadboards and jumpers on Amazon at the following links:
      – Breadboards:
      – Male-to-Female Jumpers:
    • When it comes to visualizing particulate concentration, you have several options to choose from, depending on the level of involvement you desire. It is important to note that these low-cost air quality sensors provide a relative estimation of particulate matter (PM) concentration. To obtain accurate estimates, calibration of the sensor for the specific types of particles being observed is necessary.
    sensor We have incorporated a 16-column by 2-row LCD screen with the I2C Arduino package for this project. The provided code will display various information such as the time elapsed since the Arduino was powered up, PM1.0, PM2.5, and PM10 concentrations. Numerous LCD screens are available in the market that can be used for this purpose. We have utilized one from Amazon, ensuring that it includes the necessary board requiring only 4 wires for connection. Please note that when selecting an LCD screen, ensure it is not just the screen itself but also includes the required board. Here is an example from Amazon: Card Reader 1

    A microSD card reader is essential for recording data over extended periods, which can be later analyzed on a computer. You will also need a microSD card for storage. It is recommended to use a card with a capacity smaller than 32 GB and ensure that it is formatted as FAT16 or FAT32 (most cards come pre-formatted, but reformatting may be required for certain models).

    Here are links to examples of microSD card readers available on Amazon:
    – MicroSD Card Reader Adapter Module for Arduino:
    – MicroSD Card (for storage):

    For monitoring the particulate concentrations, you can use the serial monitor in the Arduino sketch editor by connecting the Arduino to your computer via USB. To access the serial monitor, you can use the keyboard shortcut “Ctrl + Shift + M” or navigate to it through the “Tools” menu.

    Additionally, if you have Python installed on your computer, the Python script provided at the end of this module can be used to create real-time plots of air quality.

    Other Materials you May Want:

    Timekeeping: It is important to note the starting time for each sensor. The Arduino will record data with the first reading at t = 0, and it is crucial to know the corresponding time of day for accurate data analysis.

    GPS: If you wish to compare your readings to those taken at different locations, you may want to measure your precise location. You can access GPS data from various phone map or compass apps, or you can retrieve the location later by using Google Maps and clicking on the specific data collection locations.

    Weather Station: Temperature, pressure, and wind velocity in your area may be of interest, but keep in mind that this data can also be obtained from weather websites, although with potentially lower resolution.

    Wiring: The Arduino, sensor, LCD screen, and SD card reader should be interconnected according to the provided instructions. However, if you prefer to read data through a computer, you can omit the LCD screen or SD card reader. Please note that the wiring diagram depicted is applicable to all models except for the 7003; refer to the image of the 7003 sensor above for the appropriate wiring modifications specific to the AQ sensor.Materials

    Wiring Table:

    (not 7003)


    Goes To

    SD Card

    Goes To

    LCD I2C Screen

    Goes To


    1 or 2

    5 V






    3 or 4





    5 V



    3.3 V
















    5 V

    6, 7, & 8


    Not Used




    Prior knowledge of Arduino is recommended, and it may be beneficial to begin with online Arduino examples if you are unfamiliar with them. You can find helpful resources at the Arduino website ( The Arduino board must include the “LiquidCrystal I2C” library packages, which need to be installed in the Arduino sketch editor. It is a one-time setup process.

    install arduino packages

    In general, to install arduino packages:

    To begin, open the Arduino Sketch Editor. Then follow these steps:

    1. Click on “Sketch” in the menu bar.
    2. Go to “Include Library” and select “Manage Libraries”.
    3. In the search bar that appears, enter the name of the library you need.
    4. Click on the desired library package and then click on the “Install” button.

    You can access the Arduino script that reads data from the sensor by following this link:

    This code is also available at the end of this teaching module as an appendix. If you want to view the PM values measured by this script without using the LCD screen, you can upload the script to the Arduino board, open the Arduino Sketch Editor, and click on “Tools” followed by “Serial Monitor” (or use the keyboard shortcut “Ctrl + Shift + M”).

    If you wish to plot and view real-time particulate counts on your computer, we have provided a Python script for that purpose. If you haven’t installed Python before, you can refer to our screencast instructions here: You can find the Python code at the end of this teaching module as an appendix, or you can access it through this link: [insert link].

    Background for Teachers

    Air Quality Background:

    The World Health Organization has estimated that approximately 3.7 million deaths in 2012 were caused by ambient air pollution. Among the various air pollutants, fine particulate matter (PM2.5), which refers to particles with diameters smaller than 2.5 microns, has the most significant adverse health effects. In northern Utah, elevated levels of PM2.5 are a pressing issue, often surpassing national ambient air quality standards for extended periods, especially during winter. These episodes of poor air quality have severe implications for the health and well-being of the region’s residents, leading to increased cases of asthma, juvenile arthritis, and mortality.

    Both government agencies, such as the Utah Division of Air Quality (DAQ), and citizens rely on air quality data from sparsely distributed monitoring stations for planning purposes and public communication. These stations are equipped with expensive, high-quality instruments that meet federal monitoring requirements. However, due to the sparse spatial distribution of these stations, they may not accurately represent the pollutant gradients within a city. In Salt Lake City, variations in elevation, land use, and other factors contribute to daily average PM2.5 concentrations at the neighborhood level that may not be adequately represented by the nearest state monitoring station. Furthermore, the government monitoring stations have limited temporal resolution, with only two stations in Salt Lake County providing hourly PM2.5 levels. This temporal gap is particularly concerning as studies suggest that even short-term increases in pollutant levels can elevate the incidence and severity of asthma and cardiac events.

    To address these spatial and temporal gaps in air quality monitoring, networks of low-cost air quality sensors can play a crucial role. These sensors can provide valuable information to air quality managers, healthcare providers, and the community, helping to better understand air quality and minimize exposure risks. However, many low-cost sensors lack independently gathered calibration data, quality assurance procedures, or descriptions of potential inaccuracies in their readings. Presenting unreliable or uncertain information from sensor networks can lead to unnecessary public concern or a false sense of security regarding pollution levels and associated health risks.

    The goals of this project include equipping each classroom with a low-cost air quality sensor and involving students in determining how well outdoor air quality measurements represent local conditions within and around their school. Additionally, students will assess the performance of the sensors over time.

    Regarding electronics background, a basic understanding of electronics is helpful but not necessary. The primary concern is ensuring that the wires are connected correctly.

    The Arduino board, depicted in the figure provided in the Materials section of this module, essentially functions as a miniature computer. It receives data from the air quality sensor, processes it to calculate particulate concentrations, and then presents the data to the user in the form of LCD characters, a file on the SD card, or real-time plotting on a computer.

    The air quality (AQ) sensor utilizes laser light scattering to measure particulate counts. These sensors work by detecting scattered light. The internal structure of one such sensor is illustrated in the accompanying figure. Although some models may have different air inlet configurations, they all operate by pulling air through a dark chamber containing a light detector and a laser beam.models

    When laser light passes through clean air without particulate pollution, it typically follows a relatively straight path. This means that very little, if any, light would make a sharp 90° turn and reach the sensor. However, when the air contains particulate matter, the light can be reflected off the particles in random directions. This phenomenon can be observed by shining a laser through a cloud of smoke, for example, resulting in a visible line of scattered light traveling through the cloud. In contrast, if the air is clean, the laser beam may pass through the space without any noticeable effects.

    A helpful demonstration to introduce this concept in the module could involve using a laser pointer and a fog machine. By directing the laser beam through the fog, the scattered light will become visible, showcasing the impact of particulate matter. Another demonstration option is using a blue laser and comparing its interaction with tonic water (which contains quinine, a substance that scatters light) and regular water. The scattering of light by the quinine in the tonic water can help illustrate the effect of particulate matter on laser light.the quinine in the tonic will scatter the light

    While the scattered light from the laser is random in direction, a portion of it will be scattered towards our sensor and detected. The amount of particulate matter in the air directly influences the likelihood of a light photon reaching the sensor, allowing us to quantify the level of particulate pollution.

    LCD Screen: This device is designed specifically for displaying real-time data. It requires a 5V power supply and a ground connection, along with two additional wires to transmit the data to be displayed.

    MicroSD Card Reader/Writer: This device also requires power and communication connections with the Arduino. The Arduino will create a file named “PM_Data.csv” on the SD card. This file is in CSV (comma-separated values) format, compatible with programs like Excel and other data analysis tools. If the file already exists, the Arduino will add new data to the existing file, appending it after the previous data. It is advisable to periodically delete the file to prevent the card from filling up. Each time the sensor system restarts, it generates a new random RunID and includes it as the header for each new data set in the CSV file. This approach compensates for the Arduino’s inability to provide actual time or date information (as its clock resets at each restart). The user should match the RunID with a known start time and date.

    There are numerous potential research questions that students can explore using the air quality sensor. While students are encouraged to come up with their own creative research questions, here are a couple of examples to get them started:

    1. Fireplaces: How does the type of fuel (wood vs. gas) affect particulate pollution levels? How far can the effects of a wood-burning fireplace be detected?

    2. Clothes dryers: Can particulate matter be detected in the laundry room when the dryer is on compared to when it is off? Can it be detected near the dryer vent? If a dryer vent is clogged, can a significant amount of particulate matter enter the house?

    3. Candles or incense: Are these significant health risks? How do they compare in terms of particulate pollution levels? If a candle is lit in one room, what impact does it have on particulate matter in other rooms?

    4. Aerosols (hairspray, cleaners): Do these products generate significant particulates in the room where they are used? Are the particles different from those produced by a fire? (Note: Avoid spraying directly around the sensor to prevent coating the detector, which could affect its performance.)

    5. Cleaning/dusting: Is there evidence that performing household chores can pose hazards to health?

    6. Cooking: Does the type of food being cooked or the cooking method affect particulate pollution levels?

    7. Soldering: What are the risks associated with soldering electronic components? How does ventilation impact particulate pollution levels?

    8. Powders: Do activities like playing in a sandbox with materials such as flour or mixing cement generate significant particulate pollution?

    9. Industrial sources: What are the particulate concentration levels near a gravel pit, incinerator, power plant, or restaurant? Does the type of restaurant (e.g., wood-fired pizza place vs. sandwich shop) make a difference?

    10. Weather effects: How do factors such as wind, rain, and humidity affect readings from sources of particulate pollution?

    11. Elevation: If the sensor is taken on a hike during a day with poor air quality, can evidence of the inversion layer be observed, and how quickly does the transition occur? Additionally, sending the sensor up on a tethered weather balloon can provide valuable data, but tracking elevation will require the use of a phone app.

    12. Air quality events: What are the effects of fireworks used during local holidays? How does a forest fire occurring a couple of states away impact air quality?

    13. Indoor vs. outdoor: What is the relationship between indoor and outdoor air quality? Does it vary based on the season or the age of the home?

    14. Motor vehicles: What is the effect of living near a busy street compared to an isolated neighborhood? How far-reaching is the impact of school buses and parental drop-offs and pickups at schools?

    These are just a few examples, and there are countless other research questions that can be explored using the air quality sensor.


     //*Plantower Pin 1 5V to 5V on Arduino
     //*Plantower Pin 2 GND to GND on Arduino
     //*Plantower Pin 3 SET to 3.3V on Arduino (if 0 V it will put the sensor in a low-power standby)
     //*Plantower Pin 4 RX to Digital Pin 4 on Arduino (which will act as the transmitting pin on the Arduino sending data to the sensor's recieving pin 4)
     //*Plantower Pin 5 TX to Digital Pin 3 on Arduino (which will act as the recieving pin on the Arduino taking data from the sensor's transmitting pin 5)
     //*Plantower Pin 6 RESET does not need to be connected, if set to 0V the sensor will reset
     //*Plantower Pin 7 & 8 Not Connected
     //*I2C LCD Screen is optional; code will work without it
     //*I2C LCD Screen Gnd to Arduino Gnd on Arduino
     //*I2C LCD Screen Vcc to 5V on Arduino
     //*I2C LCD Screen SDA to Pin A5 on Arduino
     //*I2C LCD Screen SCL to Pin A4 on Arduino
     //*SD Card Reader is optional; code will work without it
     //*SD Card Reader Gnd to Arduino Gnd on Arduino
     //*SD Card Reader Vcc to 5V on Arduino
     //*SD Card Reader MISO to Pin 12 on Arduino
     //*SD Card Reader MOSI to Pin 11 on Arduino
     //*SD Card Reader SCK (CLOCK) to Pin 13 on Arduino
     //*SD Card Reader CS to Pin 10 on Arduino (may change for different card readers)
     //*The TX pin on the sensor connects to pin 11 on the Arduino
     //*Plantower Reading Portion of Code altered from: Zuyang @ HUST, Date:March.25.2016
    #include <SoftwareSerial.h>  //package allowing serial communication
    #include <LiquidCrystal_I2C.h> //include LiquidCrystal_I2C library if lc display is attached
    #include <SD.h>//needed for sd card reader communication
    LiquidCrystal_I2C lcd(0x3F,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display, NOTE: Some I2C LCDs will need the address changed from 0x27 to 0x3F
    //Define the length of the information beingretrieved from the sensor, this portion must change for different sensor models
    //#define LENG 31   //0x42 + 31 bytes equal to 32 bytes, Use for Model 1003, 5003
    #define LENG 23   //0x42 + 21 bytes equal to 24 bytes, Use for Model 3003
    #define TIME_BETWEEN_READINGS 1000  //number of milliseconds between readings from the sensor (should be more than once a second)
    const bool useplotter = false; //true if using arduino's built-in serial plotter, false otherwise. Use false is plotting with python
    const int chipSelect = 10;//pin for chip select (CS) on microsd card reader (reader is optional)
    unsigned char buf[LENG];  //Contains the information from the sensor
    unsigned short PM01=-1;          //variable that will hold the PM1.0 value
    unsigned short PM25=-1;         //variable that will hold the define PM2.5 value
    unsigned short PM10=-1;         //variable that will hold the define PM10 value
    unsigned short PMavg=-1;           //Average of all PM readings
    bool useSD=true;       //goes false if there is an SD reader error
    unsigned short i=0;  // just a counter
    bool gotitright=false; //true if the buffer length was right, to account for different plantower models
    String SD_String="";//where the string for the SD card data will be kept
    static unsigned long lastReadingTime=0;  //variable to keep track of time (in ms) of last reading
    //NOTE if the sensor never connects appropriately, then it will read -1 concentration
    // Pin 3 will be the Arduino's RX (recieving pin) connected to the sensor's TX (Plantower Pin 5)
    // Pin 4 will be the Arduino's TX (transmitting pin) connected to the sensor's RX (Plantower Pin 4)
    SoftwareSerial PMSerial(3, 4);   //initialize the serial connection, 3 & 4 are the digital pin numbers used on the arduino
    byte Char2p[8] = {//create a 2 with a point character
     0b01100,  0b10010,  0b00010,  0b00100,  0b01000,  0b10000,  0b11110,  0b00001};
    byte Char5[8] = {//create a smaller 5 characer
      0b01111,  0b01000,  0b01000,  0b00110,  0b00001,  0b00001,  0b01110,  0b00000};
    byte Char10[8] = {  //create a 10 character
      0b00000,  0b10111,  0b10101,  0b10101,  0b10101,  0b10101,  0b10111,  0b00000};
    void setup()
      PMSerial.begin(9600);   //Serial communication with the sensor
      PMSerial.setTimeout(1500);    //Time to wait for sensor to respond (should sample each second, at most)
      Serial.begin(9600);  //Serial communication with a possible computer through the USB cable
      while (!Serial) {;} // wait for serial port to connect. Needed for native USB port only
      Serial.println("Starting LCD...");
      lcd.init();                      // initialize the lcd
      lcd.backlight();   //turn on the backlight
      lcd.setCursor(0,0);    //place the cursor
      lcd.print("Starting!..");  //let the user know it's starting
      Serial.println("LCD Initialized...");
      if (!useplotter) {Serial.println("Initializing card."); }
      if (!SD.begin(chipSelect)) {if (!useplotter) {Serial.println("Card failed, or not present"); lcd.setCursor(0,0); lcd.print("Card NOT found.");}}//start talking to the SD card reader
      else {if (!useplotter) {Serial.println("Card initialized."); lcd.setCursor(0,0); lcd.print("Card detected.");}}
      File dataFile ="PM_Data.csv", FILE_WRITE); //Open a file named PM_Data.csv
      randomSeed(analogRead(0)+dataFile.size());//seed the random number generator with something a bit random...
      if (!useplotter) {Serial.print("File Size: ");Serial.println(dataFile.size());}
      SD_String="RunID: "+String(random(0,10))+String(random(0,10))+String(random(0,10))+String(random(0,10));
      if (!useplotter) {Serial.print("Run ID: ");Serial.println(SD_String);}
      lcd.setCursor(0,1);    //place the cursor
      lcd.createChar(0, Char2p); // Sends the custom char to lcd
      lcd.createChar(1, Char5); // Sends the custom char to lcd
      lcd.createChar(2, Char10); // Sends the custom char to lcd
      if (dataFile) { //if the file opened correctly (SD reader attached)
        dataFile.println(SD_String);//print RunID in the file
        SD_String="t(s),PM1.0 (ug/m3),PM2.5 (ug/m3),PM10 (ug/m3)";   //header for csv file to be created on SD card
        dataFile.println(SD_String);//print the header in the file
        dataFile.close();//close the file
      else {
        Serial.println("Error opening data file on SD card"); //alert user to error
        useSD=false;//stop trying to use the SD reader
      delay(4000);//pause so user can get session ID
      if (!useplotter) {Serial.print("t(s)\t");Serial.print("PM1.0\t");Serial.print("PM2.5\t");Serial.print("PM10\t");Serial.println("PMavg");} //alert user to error
    void loop()  //This is what the Arduino will do over and over, while it has power
      String SD_String = "";//data to be written to SD card
      if(PMSerial.find(0x42)){    //check if there is a serial connection to the sensor
        PMSerial.readBytes(buf,LENG);  //read the information from the sensor and put it in the variable buf
        if(buf[0] == 0x4d){  //first read value
          if(checkValue(buf,LENG)){//check if some data of some length was recieved from the sensor
            PM01=transmitPM01(buf); //count PM1.0 value of the air detector module
            PM25=transmitPM2_5(buf);//count PM2.5 value of the air detector module
            PM10=transmitPM10(buf); //count PM10 value of the air detector module
            PMavg=(PM01+PM25+PM10)/3; //find the average PM count
      if (millis() - lastReadingTime >=TIME_BETWEEN_READINGS)   //Take a reading every TIME_BETWEEN_READINGS
          if (useSD){ //SD reader still good
            SD_String = String(int(round(lastReadingTime/1000)))+","+String(PM01)+","+String(PM25)+","+String(PM10)+","; //data: time and the PM values
            File dataFile ="PM_Data.csv", FILE_WRITE); //open the file
            if (dataFile) {  //if it opened correctly...
              dataFile.println(SD_String);  //write the data to the file
              dataFile.close();   //close the file
            else {
              Serial.println("Error opening data file on SD card"); //alert to possible error
          lastReadingTime = millis(); //update time of last reading
          if (!useplotter){Serial.print(int(round(lastReadingTime/1000)));Serial.print("\t");}
          lcd.clear();   //clear the lcd
          lcd.setCursor(0,0);        //print to the lcd
          lcd.print(" t: "); //time
          lcd.setCursor(8,0);        //PM1.0
          lcd.print(" 1: ");
          lcd.setCursor(0,1);        //PM2.5
          lcd.print((char)0);  // 2. character
          lcd.print((char)1);  // 5 character
          lcd.print(": ");
          lcd.print((char)2);  //10 character
          lcd.print(": ");
    char checkValue(unsigned char *thebuf, char leng)
      char receiveflag=0;
      int receiveSum=0;
      for(int i=0; i<(leng-2); i++){
      receiveSum=receiveSum + 0x42;
      if(receiveSum == ((thebuf[leng-2]<<8)+thebuf[leng-1]))  //check the serial data
        receiveSum = 0;
        receiveflag = 1;
      return receiveflag;
    int transmitPM01(unsigned char *thebuf)
      int PM01Val;
      PM01Val=((thebuf[3]<<8) + thebuf[4]); //count PM1.0 value of the air detector module
      return PM01Val;
    //transmit PM Value to PC
    int transmitPM2_5(unsigned char *thebuf)
      int PM2_5Val;
      PM2_5Val=((thebuf[5]<<8) + thebuf[6]);//count PM2.5 value of the air detector module
      return PM2_5Val;
    //transmit PM Value to PC
    int transmitPM10(unsigned char *thebuf)
      int PM10Val;
      PM10Val=((thebuf[7]<<8) + thebuf[8]); //count PM10 value of the air detector module
      return PM10Val;

Leave a Comment

Your email address will not be published. Required fields are marked *