Lego Air Quality Sensor


Poor air quality is a problem that affects the health of many communities, and measuring air quality can be extraordinarily important to people with health problems. In this module you will build a simple air quality sensor with some standard electronic components and legos.

General Information:

Main Curriculum Tie:

Environmental Science

Additional Curriculum Ties:

Physics, Electronics

Career Connections:

Many engineers spend their careers working to keep our environment clean.  Doing so requires understanding of where pollution is and where it comes from.

Typical Time Frame:

10 min instructions

20 min to build

10 min to wire

15 min to test

Typical Group Size

Groups of 2 to 3

Student Prior Knowledge:

Some basic knowledge of electronics


Essential Questions:





#include <Adafruit_NeoPixel.h> //include the adafruit library needed to control the neopixels

#ifdef __AVR__

  #include <avr/power.h>  //include power use reduction code, if it's not already there


// define constants

#define NEOPIN      6   //digital pin where the neopixel is plugged in

#define NEONUM      8   //number of neopixel leds

#define SENPIN      A5  //the pin for the analog in from the photoresistor

#define SENPOW      3   //the 5V power for the sensor

#define SENGND      2   //the ground for the sensor

#define AVGNUM     100  //the number of measurements to average

// define variables

int     senval=0;         //the reading from the photoresistor

int     minsenval=2000; //the minimum reading

int     maxsenval=0;    //the minimum reading

int     percent=0;      //the percent between the min and max

int     nlights=0;      //the number of lights to turn on

int     iavg=0;         //counter for average

int     avgsum=0;

float   meansens=0;     //average sensor value

float   tau=200;        //time constant for low pass filter

float   alpha=10;       //smoothing factor, high alpha leads to less noise but also less sensitivity to changes

float   filtsenval=0;   //filtered sensor value

int     dt=0;           //change in time from one datum to another

int     t=0;            //time

int     i=0;            //counter for loops

// Initialize the 'neopixel' object, which will be the object used to communicate with the

// indicator lights, by sending each one a red green or blue value

Adafruit_NeoPixel neopixels = Adafruit_NeoPixel(NEONUM, NEOPIN, NEO_GRB + NEO_KHZ800);

void setup() {        //initialization code, only runs at the board's power up

  Serial.begin(9600); //start talking to a computer if connected


  pinMode(SENPOW, OUTPUT);      // sets this digital pin as output

  pinMode(SENGND, OUTPUT);      // sets this digital pin as output

  digitalWrite(SENPOW, HIGH);   // sets the sensor's circuit power to 5V

  digitalWrite(SENGND, LOW);    // sets the sensor's ground to 0V


  neopixels.begin(); // This initializes the NeoPixel software library.

  for(int i=0;i<NEONUM;i++){ //for all neopixel lights, set to an initial blue color

    neopixels.setPixelColor(i, neopixels.Color(1,1,50)); //set color to rgb value (blue here)

  };  //turn on the lights to the color they were set to above

  delay(500);  //wait half a second (500 milliseconds) to show the user that the board reset

  meansens=analogRead(SENPIN);    //average sensor value

  //In this loop for three seconds, find the average low light reading from the sensor

  //Make sure no particulate matter is going through the sensor at this point

  while (millis()<3000){

    i++;  //counter

    senval=analogRead(SENPIN);   //read from the sensor

    meansens=(meansens*(i)+senval)/(i+1);  //calculate a new mean

    if (minsenval>senval) {minsenval=senval;}  //find a new possible min

    if (maxsenval<senval) {maxsenval=senval;}  //find a new possible max

    delay(50);   //pause for 0.05 seconds

    Serial.print("Calibrating, meas: ");    //write results to a computer, if attached

    Serial.print(senval); Serial.print("  Mean: ");



  for(int i=0;i<NEONUM;i++){ //for all neopixel lights, set to an initial cyan color

    neopixels.setPixelColor(i, neopixels.Color(1,51,50)); //set color to rgb value (cyan here)

  };  //turn on the lights to the color they were set to above

  delay(500);  //wait half a second (500 milliseconds) to show the user that the board calibrated


  int range=maxsenval-maxsenval;    //calculate the range in the low light condition

  minsenval=meansens;               //minimum sensor value

  maxsenval=maxsenval+range;        //maximum sensor value

  pinMode(13, OUTPUT);      // digital pin linked to the led indicator on board

  digitalWrite(13, HIGH);   // turn on the led indicator to again show calibration is over


void loop() {     //the code in this loop runs repeatedly, as long as the board is powered

  delay(50);      //pause 0.05 seconds

  dt=millis()-t;  //change in time since the last reading

  t=millis();     //current time in milliseconds

  alpha=dt/(tau+dt);          //filter value

  senval=analogRead(SENPIN);  //read from the sensor


  filtsenval=filtsenval+alpha*(senval-filtsenval); //the filtered sensor reading


  iavg++;                     //counter for moving window average

  if (iavg>=AVGNUM) iavg=0;   //reset iavg each time the max is reached

  if (maxsenval<filtsenval) {maxsenval=filtsenval;}   //keep lookinmg for and updated the max sensor reading

  percent=round(100*(filtsenval-minsenval)/(maxsenval-minsenval));  //calculte the percent the sensor reading is between its max and min

  nlights=round(percent*NEONUM/100);      //turn on a number of lights corresponding to the percent sensor reading (100% = all on)

  if (nlights<1) {nlights=1; percent=0;}  //keep at least one light always on, don't allow a percentage < 0%

  if (percent>100) {nlights=NEONUM; percent=100;}  //don't allow a percentage above 100%

  //Print data to computer, if attached

  Serial.print(senval);     Serial.print("__"); Serial.print(minsenval);  Serial.print("__");  

  Serial.print(maxsenval);  Serial.print("__"); Serial.print(nlights);    Serial.print("__");

  Serial.print(percent);    Serial.print("__"); Serial.println(filtsenval);


  for(int i=0;i<nlights;i++){         //loop through the lights and turn them on as needed

    neopixels.setPixelColor(i, neopixels.Color(percent,100-percent,0));  //turn these on to a grb value between green and red depending upon percent

  }; // This sends the updated pixel color to the hardware.

  for(int i=nlights;i<NEONUM;i++){    //turn off other lights

    neopixels.setPixelColor(i, neopixels.Color(0,0,0));

  }; // This sends the updated pixel color to the hardware.


Instructional Procedures:

  1. Group students into teams of, preferably, 3 (greater than 4 would not be advised).
  2. Give the background information on air quality and how the sensors are meant to work.
  1. This slide show may be useful: 
  1. Pass out assorted lego pieces, along with the white LED light, photoresistor circuit, fan, and indicator lights.

  1. Have student plan and maybe sketch out their sensor designs, and discuss their design decisions. You might ask them to consider:
  1. Where the detector should be placed relative to the white LED? Should it be in the path of the light or at some angle? We want scattered light and so the sensor should be at a 90 degree angle off of the light path, and set back a bit so that the light isn’t constantly hitting the side of the sensor.
  2. How can we get the greatest range of response from the sensor? Make sure it’s as dark as possible when there are no particulates in the air. This would mean they need to shield the sensor from outside light, which may necessitate a couple layers of legos over the sensor.
  1. Why would we want to maximize the range? The greater the range the more sensitive their sensor will be to small changes in air quality.
  1. How long should the light and air paths be designed? How high should they be? What do we want to maximize and minimize? Is turbulent or laminar (non-turbulent) flow important?  The air will tend to flow in smooth streamlines with relatively little turbulence and so it is best to have a channel that is one brick high in front of the sensor (otherwise the stream carrying the particulates can go above the sensor’s detection). The path lengths should be sufficient to keep stray light out and keep the LED from shining on the sensor directly.
  2. How can you minimize noise from stray light?  They must shield the sensor with dark legos and keep the light intensity from changing due to shadows and switching on or off room lights.
  3. How do the colors of the blocks or the thicknesses matter? Do they? Dark blocks are better from keeping out stray light.
  1. Give 20 min for teams to create their own lego sensor.
  1. As student teams finish instruct them on how to wire their components to the arduino board. The diagrams in this module (specifically in the materials section, and the one-page handout at the end of the module) should be useful in clarifying wire connections. The wiring could be simplified and made a bit clearer by using a breadboard, but we have designed this module such that it will run only using an Arduino Uno’s native connections.
  1. The white LED should be connected to ground and the 3.3V source. Connecting to the 5V source could burn out the LED.
  2. D The black wire of the fan should be connected to ground, and the red wire to the 5V source (Use Vin on the board; it is at 5V, when powered through the USB port).
  3. The black wire on the sensor circuit should connect to Digital Pin 2 and the red wire should connect to Digital Pin 3. In the Arduino code, Pin 2 will be set to 0 V (‘LOW’) and Pin 3 will be set to 5V (‘HIGH’). We are using the digital pins because we more grounds and 5V than the board has, and the sensor requires very little current. If using a breadboard they could be powered with the standard 5V and ground. To read the voltage from between the resistors in the photoresistor sensor, the third wire (which will be some color other than red or black) should be connected to the Analog-In Pin 5 on the Arduino.
  4. The red wire on the Neopixel Stick should connect to 5V and the black to ground. The third wire should connect to Digital Pin 6. The Arduino code uses this pin to alter the light intensities.
  1. Make sure clear clean air is flowing through their sensor for the first four seconds; during that time the code will find the minimal signal. The lights will turn blue when first powered on and while it calibrates; once it is done calibrating the blue lights will turn off. The sensor will then appear noisy. Blinking from green to red due to the fact that it self calibrates. As it detects more and more ‘pollution’ it moves it’s maximum reading up. As such, each sensor has to experience high ‘pollution’ before it can display properly. Flow fog or the mist from the mist generator into the sensor to set a proper maximum.
  2. Once it has calibrated, the sensor should respond properly to mist and any other sort of particulates flowing into it.
  3. Go around the room and calibrate and test each sensor with the mist generator or fog machine as they are finished. Pay special attention to:
  1. Sensor susceptibility to ambient light noise (which may introduce systematic or random noise). Some sensors can be made to change their reading by shading them with your hand, or bringing them into light (a phone’s flashlight near the sensor block can be used as a good test of sensor robustness to ambient light conditions). If a student’s sensor reacts to an outside light source, then they should better shield the sensor.
  2. Sensor drift (systematic noise). Over time, the sensors’ minimum reading may change, although the device remains in the same conditions. This presents a good opportunity to talk about the problem of sensor drift. Drift may happen for many reasons: the sensor may get dirty, the power sent to the light may change slightly, and so on. Furthermore, you may find drift is very significant upon a cold start of the sensor; the first time you turn it on, it may calibrate poorly. To reset the sensor, either unplug and plug the sensor back in, or, preferably, press the reset button on the corner of the Arduino board, just next to its USB port. When you reset the board, be sure to allow at least 4 seconds of clean air to flow into it or you may corrupt the calibration.
  3. Sensor noise (random noise). When you flow a constant stream of mist or fog into the sensor you will likely not see a constant signal. It will peak and fall frequently. This random noise may be due to several factors and should be a question for the students to consider. Firstly, the flowing air can be turbulent through their sensor and ‘pollution’ passes by in turbulent eddies, which may have different concentrations of particulates, altering the the amount of light scattered. Alternatively, the smoke or mist may travel in thin streams if the flow is laminar, which may, at times, move above the sensor’s optimal viewing area and then dip back down into it. If the air chamber is too high this may be a significant problem. Also, electronic noise may be a problem if the sensor is placed too near to another electrical device, like a cell phone, or perhaps even the motor on the fan.
  1. Note the Arduino code has a low-pass filter built into it; it is controlled by a variable named alpha. You may wish to alter this value to show students the tradeoff between low noise and fast sensor response. The higher the value of alpha, the less noise in the reading but slower the sensor will respond to changes in air quality.
  1. Have the teams explain their designs and its performance to the class. Discuss means to improve their sensors and the design factors that seem to be the most important to sensor performance.
  1. If your students are hosting an AirU sensor, discuss how their sensor is very similar in its functionality to the professional sensor used in the AirU research program and the same problems can affect this sensor.
  1. You may wish to other design constraints. Sensors can be weighed to find the working sensor that uses the least amount of materials. External light sources can be used to find the sensor least sensitive to noise, and so on
  2. If time allows, students could attempt to improve their sensors and test them again.

Background for Teachers:

To present the background you may use the accompanying slide show: 

Air Quality Background:

The World Health Organization estimates that in 2012 approximately 3.7 million people died as a result of ambient air pollution. Among commonly monitored air pollutants, fine particulate matter (PM2.5), particles with diameters smaller than 2.5 microns, has the greatest adverse health effects. Elevated PM2.5 levels are a particularly important issue in northern Utah where PM2.5 levels can exceed national ambient air quality standards for periods ranging from a few days to weeks, particularly in the winter.  These episodes of poor air quality create significant health and quality-of-life consequences for the region’s citizens, including increased incidence of asthma, juvenile arthritis, and mortality.


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 for communicating air quality. These stations are equipped with high-quality, costly instruments that meet federal monitoring requirements. However, sparsely distributed stations may not accurately represent the pollutant gradients within a city. In Salt Lake City differences in elevation, land use and other factors result in daily average PM2.5 concentrations at the neighborhood-level that may not be well represented by the nearest state monitoring station. In addition to sparse spatial distribution, the government monitoring stations have limited temporal resolution. For example, only two stations in Salt Lake County provide hourly PM2.5 levels. This gap in temporal resolution is particularly important in light of studies suggesting that even short-term increases in pollutant levels increase the incidence and severity of asthma and cardiac events.


Networks of low-cost, air-quality sensors can help bridge these spatial and temporal gaps and provide key information to air-quality managers, health-care providers, and the community at large to better understand air quality and minimize exposure risks. However, many of these low-cost sensors lack independently gathered calibration data, quality assurance procedures, or descriptions of when the sensors may provide inaccurate readings. Presenting unreliable or uncertain information from sensor networks can cause either unnecessary public concern or complacency about pollution levels and the associated health risks.


Two of the project goals are to leave each classroom with a low-cost, air-quality sensor and to have your class help determine:

Electronics Background:

Only very basic understanding of electronics is useful but not absolutely necessary.

Arduino Board: All the needed connections on the arduino board are highlighted in the figure in the Materials section of this module.

White LED: The LED light connects to 3.3 V to produce a white light in the sensor’s light source. Note that LEDs are directional; most of the time the shorter wire on the LED should go to ground and the longer wire to the higher voltage, but not necessarily. If the LED does not turn on you may try switching the ground to the other wire. The LED can easily fit in lego blocks meant to hold axles, or a solid block can be drilled to fit the two wire leads and the light can be glued to the surface.

Fan: The fan should have a red and black wire. The black wire should attach to ground and the red to 5V supply (Use Vin when powered by a USB cord). Have the students wire up the fan and take note of which direction it blows air so that they install it correctly. It is likely better to pull air through the sensor than to blow air into it; the air will move more smoothly through the sensor. Care should be taken to keep fingers away from the spinning fan.

Photoresistor Sensor: A photoresistor is a component that resists the flow of electrical current dependent upon how much light is shining on it. Because the Arduino boards can only measure voltage, we must use the photoresistor in a simple voltage divider circuit and measure the voltage between the photoresistor and another resistor. We want the voltage signal to increase when more light hits the photoresistor and the photoresistor we are using decreases resistance with more light. As such, our voltage divider should have the photoresistor nearest to the 5V power supply and a standard static resistor nearest to ground. See the circuit diagram for the sensing circuit in the Materials section.

During our experiments our brand of photoresistor ranged from 5,760 to 4,183 ᘯ, from clear air to air filled with mist. To assure we have sufficient voltage response we chose a static resistor of 10 kᘯ.  We pre-solder and secure this voltage divider to a lego block prior to starting this teaching module, but a more in-depth procedure could have the students create their own sensing circuit.

Note that the black wire from the sensing circuit should go to Pin 2 on the arduino (which the code sets to ground, 0V)). The red wire should go to Pin 3, which the code sets to 5V. For our arduino code to respond properly, the third wire should go to the analog in pin, A5.

Indicator Light: The NeoPixel light strip we are using ( has eight lights but it is very simple to use and hook up. The black wire should go to the arduino ground, and the red to the arduino 5V. The third wire must be plugged into the digital pin number 6 in order for the script to properly operate the lights. Once connected and calibrated (as described in the methods section) the lights should go from green to red and increase in number as they detect more particulates in the air.

Light Scattering Background:

The sensors built by the students (and many research sensors) work by measuring scattered light. As light travels through clean air, which is free of particulate pollution, it travels roughly in a straight path. Almost none of the light could make, say, a 90° turn. However, if particulate matter filled the air, light may be reflected off the particles in a random direction. You can readily see such scattered light if, for example, you shine a laser through a cloud of smoke; the result will appear as a line of scattered light traveling through the cloud, whereas with clean air you may not ever detect a laser was passing through the space (Such a demonstration with a laser pointer and fog machine can be a good introductory addition to this module; you could also use a blue laser and tonic water compared to regular water as the tonic water’s quinine will scatter the laser light).


While this light is scattered randomly, some of the light will be scattered towards our sensor and detected. The more particulate matter in the air, the more likely it is that a photon of light will find its way to the sensor (a photoresistor in the instance of this module), and therefore we can quantify the amount of particulate pollution in the air.

Alternatively one could attempt to detect the amount of light lost after passing through the polluted air, by placing the sensor directly across from the light source. In this case more light would hit the sensor in clean air than in polluted air. If students attempt this method they are not likely to be successful due to the fact that the sensor will likely be saturated with light and insensitive to the loss of a very small percentage of its total intensity. Scattered light is a prefered indicator of particulate matter because 1. such sensors are typically more sensitive to the range of light intensity between zero and minimal light than between two large intensities of light; and 2. scattered light can be more indicative of particle size.

As such, we would suggest a possible working design for such a sensor may look like the following illustration (though we would recommend you do not share this with students; results are more memorable if they are allowed to explore their design options and then explain their performance):

The sensor is 90° off from the light path, and somewhat setback and a fair distance from the light to avoid direct light hitting the sensor.  The air path makes several turns to avoid light pollution from the outside. However, several designs may be successful and students should be encouraged to exercise their creativity.

Video Demonstrations of This Module 

These teaching modules are supported by the National Science Foundation and the Rocky Mountain Power Foundation.


Air Quality Sensor - Handout

Air quality is a serious concern for many vulnerable populations in our community. Using the background information given by your instructor, you are being asked to design a sensor system to monitor the quality of our air. Along with an assortment of building blocks, you have been given several parts to aid you in this design task:

The sensor has a photoresistor which changes a voltage signal depending on the amount of light hitting it. Sensor ground and 5V are supplied by Pin 2 and 3, respectively.

The white LED acts as the light source for the sensor, and should be powered with 3.3V. This is the light to be detected as an indication of pollution.

The Fan is to be used to pull or push air through the sensor. It requires 5V power that may come from the Vin Pin.


The indicator light needs 5V power and it must also be connected to a digital pin (Pin 6). More lights turn on as more light hits the sonor’s photoresistor.

All the components must connect, using wire jumpers, to the supplied arduino board, as shown above. Double check that you have wired your device correctly before you plug in your device.

Once your sensors are built we will test their performance. Good luck and be creative!