This is an Arduino-based battery powered air quality monitor using the optical SHARP GP2Y1010AU0F dust sensor (or its more recent version the GP2Y1014). It reports values in micrograms per cubic meter, which is also the unit of PM2.5 and PM10.

Having lived in Beijing for many years, i am quite concerned about air pollution, even in Hong Kong where luckily the situation is a lot better. After experimenting a bit with dust sensors that are supposed to correlate with PM2.5 and PM10, i decided to build a portable sensor. For my first prototype i used an Industruino PROTO 32u4 kit, without its usual DIN-rail casing; i put it in a laser cut wooden box. A second version uses a cheap 16×2 LCD and a ‘bare-bones’ Arduino on a PCB (see below).

IMG_1326

For reasons outlined here, i opted for the SHARP GP2Y1010AU0F dust sensor (optical), which outputs a voltage that correlates with the volumetric unit of standard PM measurements. However, the datasheet and this application note do not mention the upper diameter of the dust measured (e.g. 2.5 or 10 micron) so we do not know if it is intended to measure PM2.5 or rather PM10. So i have chosen to just call it the ‘dust concentration’ to be clear about this.

The LCD shows the current measurement, refreshed every 2 seconds, as an average of 200 samples, with pulse interval of 10 milliseconds as in the application note. It also shows the long-term average since the unit was switched on, and the minimum and maximum values since the unit was switched on.

PROTOTYPE

The Industruino PROTO kit consists of a topboard PCB with the MCU (32u4 = Arduino Leonardo) and an LCD, and a baseboard with a prototyping area for soldering. I decided to also include a DHT22 temperature/humidity sensor, with pull-up resistor. I added the 150 ohm resistor and 220uF capacitor necessary for the SHARP sensor. And 3 LEDs to indicate 3 levels (green, yellow, red).

IMG_0776

I am powering this project from a LiPo 1200mAh battery cell, with a switch, and a USB booster that brings the voltage up to 5V (necessary to get the full scale of the SHARP sensor).

The code is fairly straight forward, i did make an effort to follow the recommendations of the SHARP datasheet in detail regarding pulse length and interval, because using a different interval seems to affect the results (higher readings). I’m taking the average of 100 samples, so the screen refreshes every 1 second.

Everything fits nicely together in the box, i attached the top and base PCBs to the front panel and applied hot glue to put the panels together.

IMG_0796 IMG_0798

The battery is stuck to the back panel.

IMG_0800

SECOND VERSION

Made out of readily available components, with an Atmega328P on a soldered perf board, and the 16×2 LCD plugged into that with a row of 16 male/female headers.

IMG_1317 IMG_1316

I bought the sensor from Taobao and although it is sold as a GP2Y1010AU0F type, they sent me the GP2Y1014 type, which is NOT the same. I could not find any reliable datasheet for this type, but using the same 10 millisecond sampling interval gave me zero values. Only when i included a delay(100) between the samples it returned realistic values, similar to the GP2Y1010. So instead of displaying the average of 200 sample, i am using only 20 samples with this sensor.

IMG_1362 - Edited

The layout inside the box, with DC/DC booster on the left, to bring the 3V battery output to 5V.

The 2 versions seem to report similar values, more testing and benchmarking with professional sensors needed.

IMG_1379 - Edited

PCB VERSION

IMG_1801

IMG_1843

Code for the 16×2 LCD version:

/*
 Arduino barebones 16MHz (duemilenova) with SHARP dust sensor
 SHARP GP2Y1010AU0F dust sensor output on A0
 timings according to datasheet https://www.sparkfun.com/datasheets/Sensors/gp2y1010au_e.pdf

2xAA bat with USB booster and large cap 1500uF on 5V, GND
 battery voltage on A5

LCD 16x2 
 * LCD RS pin to digital pin 7
 * LCD Enable to digital pin 6
 * LCD D4 pin to digital pin 5
 * LCD D5 pin to digital pin 4
 * LCD D6 pin to digital pin 3
 * LCD D7 pin to digital pin 2
 * LCD R/W pin to ground
 * LCD VSS pin to ground
 * LCD VCC pin to 5V
 * 5K pot to LCD VO pin
 
 AREF to 5V (seems necessary; without this, AREF gave around 4.2V)
 BuffaloLabs 2017, Tom Tobback
*/

// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

const int SHARP_LED_PIN = 8; // inverted
const int LED_GREEN_PIN = 9;
const int LED_YELLOW_PIN = 10;
const int LED_RED_PIN = 11;

// SHARP SENSOR VARIABLES
const int samples = 4; // with delay of 10ms in between as per datasheet /// more needed for GP2Y1014!!
const int upper_limit_green = 20;
const int upper_limit_yellow = 50;

unsigned long concCum = 0; // to calculate all-time average concSHARP for displaying
unsigned long counter = 0; // counter for all-time average

int concMax = 0; // to keep track of min and max
int concMin = 500;

void setup(void) {

pinMode(SHARP_LED_PIN, OUTPUT); // SHARP LED switch
 pinMode(SHARP_LED_PIN, HIGH); // LED off
 
 pinMode(LED_GREEN_PIN, OUTPUT);
 digitalWrite(LED_GREEN_PIN, HIGH);
 pinMode(LED_YELLOW_PIN, OUTPUT);
 digitalWrite(LED_YELLOW_PIN, HIGH);
 pinMode(LED_RED_PIN, OUTPUT);
 digitalWrite(LED_RED_PIN, HIGH);

Serial.begin(9600);
 Serial.print("SHARP dust sensor GP2Y1010/1014 with sample number: ");
 Serial.println(samples);

lcd.begin(16, 2);
 lcd.setCursor(0, 0);
 lcd.print("BuffaloLabs");
 lcd.setCursor(0, 1);
 lcd.print("DUST SENSOR v1.0");

digitalWrite(LED_GREEN_PIN, LOW);
 digitalWrite(LED_YELLOW_PIN, LOW);
 digitalWrite(LED_RED_PIN, LOW);

delay(100);
}

/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////

void loop(void) {

//////////////////////////////////////////// READ SHARP SENSOR ////////////////////////////////////////////////
 float dust_voltage = 0;
 int dust_analog;
 for (int i = 0; i < samples; i++) {
 digitalWrite(SHARP_LED_PIN, LOW); // power on the LED
 delayMicroseconds(280); // DATASHEET pulse width 0.32ms = 280us + 40us
 dust_analog = analogRead(A0); // read the dust value
 delayMicroseconds(40); // DATASHEET pulse width 0.32ms = 280us + 40us
 digitalWrite(SHARP_LED_PIN, HIGH); // turn the LED off
 delayMicroseconds(9680); // DATASHEET recommended pulse cycle 10ms = 320us + 9680us
 dust_voltage += dust_analog * 5.0 / 1023.0 ; // voltage 0-5V
 delay(500); // only for GP2Y1014!!! remove for GP2Y1010 and increase sample size to 200
 }
 dust_voltage = dust_voltage / samples;

// CALCULATE SHARP RESULTS
 int concSHARP = 172 * dust_voltage - 100; // density microgram/m3
 concSHARP = constrain(concSHARP, 0, 500);
 
 concCum += concSHARP; // for all-time average
 counter++;
 int concAvg = concCum / counter; 
 
 if (concSHARP > concMax) concMax = concSHARP; // update min and max
 if (concSHARP < concMin) concMin = concSHARP;

//////////////////////////////////////// READ BATTERY VOLTAGE ///////////////////////////
 int battery_analog = 0; // take average of 5 battery voltage readings to reduce noise
 for (int i = 0; i < 5; i++) {
 battery_analog += analogRead(A5);
 delay(10);
 }
 battery_analog = battery_analog / 5;
 long battery_voltage = 10L * 5L * battery_analog / 1023L; // times 10 for integer value (larger than int, use long)
 int battery_percent = map(battery_voltage, 20, 30, 0, 10); // divided by 10 for less noise, only in 10s
 battery_percent = constrain(10 * battery_percent, 0, 100);

/////////////////////////////////////// PRINT DATA TO SERIAL ////////////////////////////////
 Serial.print(dust_voltage);
 Serial.print("\t\t");
 Serial.print(concSHARP);
 Serial.print("\t\t");
 Serial.print(concCum);
 Serial.print("\t\t");
 Serial.print(counter);
 Serial.print("\t\t");
 Serial.print(concAvg);
 Serial.print("\t\t");
 Serial.print(battery_voltage);
 Serial.print("\t\t");
 Serial.println(battery_percent);

////////////////////////////////////////////// DISPLAY ///////////////////////////////////////
 
 if (counter == 1) lcd.clear();
 lcd.setCursor(0,0);
 lcd.print("now:");
 lcd.print(concSHARP);
 lcd.print(" ");
 lcd.setCursor(0,1);
 lcd.print("avg:");
 lcd.print(concAvg);
 lcd.print(" ");
 lcd.setCursor(8,0);
 lcd.print(concMin);
 lcd.print(",");
 lcd.print(concMax);
 lcd.print(" ");
 lcd.setCursor(8,1);
 lcd.print("bat:");
 lcd.print(battery_percent);
 lcd.print("% ");
 
/////////////////////////////////////////////// SWITCH LED INDICATORS /////////////////////////////////////
 if (concSHARP < upper_limit_green) { // based on short time average
 digitalWrite(LED_GREEN_PIN, HIGH);
 digitalWrite(LED_YELLOW_PIN, LOW);
 digitalWrite(LED_RED_PIN, LOW);
 }
 else if (concSHARP < upper_limit_yellow) {
 digitalWrite(LED_GREEN_PIN, LOW);
 digitalWrite(LED_YELLOW_PIN, HIGH);
 digitalWrite(LED_RED_PIN, LOW);
 }
 else {
 digitalWrite(LED_GREEN_PIN, LOW);
 digitalWrite(LED_YELLOW_PIN, LOW);
 digitalWrite(LED_RED_PIN, HIGH);
 }

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Leave a Reply

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