Matha Goram
Published © LGPL

What Is the Air Like?

This note focuses on an inexpensive air quality sensor that has been in popular use for several years to quantify indoor particulate matter.

EasyFull instructions provided30 minutes164
What Is the Air Like?

Things used in this project

Hardware components

Elegoo Arduino UNO Rev 3
×1
Seeed Shinyei PPD42NS dust sensor
×1
Elegoo DuPont connection wires
×5
Elegoo Breadboard
×1
Elegoo Baseplate
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Dust sensor test configuration

Logical diagram
04 ppd42ns jovx0lcvdh

Schematic diagram

Pin connections between sensor and Arduino UNO
05 ppd42ns lkgeu2sid9

Code

ppd42ns-01.ino.ino

C/C++
Display readings from dust sensor to Arduino serial terminal window
/*
 * ppd42ns-01.ino
 * Basic Hello World exercise for Shinyei PPD42NS sensor
 * Author Chris Nafis
 * Reference //www.howmuchsnow.com/arduino/airquality/grovedust/
 * Update for tutorial use
 * 2019-03-25
 * 0.2 initial variation
 * 
 * All rights to this code are in accordance with Chris Nafis' publication.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 * Notes
 * This sample code for Shinyei PPD42NS/PPD42NJ Particle Sensor was published at:
 * http://www.sca-shinyei.com/pdf/PPD42NS.pdf <== HTTP 404 error now! :(
 * 
 * This code measures the concentration of particulate matter in air defined as:
 * particles per 0.01 cubic ft ( particles per 283 mL )
 * 
 * Based on work by Chris Nafis http://www.howmuchsnow.com/arduino/airquality/grovedust/
 * 
 * Connections:
 * Sensor Pins
 * 1 => Arduino GND
 * 2 => unused
 * 3 => Arduino +5VDC
 * 4 => Arduino digital pin 8
 * 5 => unused
 *
*/

const int pinDustSensor = 8;        // D8 on UNO

unsigned long observationPeriod = 30000; // obtain a reading every minute (30,000 ms)
const unsigned long samplePeriod = 30000;// Sample time is 30 seconds for one batch of pulse data, milliseconds
                                    // time between logging = observationPeriod + samplePeriod
unsigned long observationStart = 0; // marker for start of current observation period

void setup()
{
  Serial.begin(115200);            // conventional default baud rate these days for serial port
  pinMode(pinDustSensor, INPUT);    // read sensor data from digital pin 8
  if (observationPeriod < samplePeriod) // sanity check for any fat-fingering situation
    observationPeriod = samplePeriod + 30000;
  observationStart = millis();      // mark the start time of current observation period
}                                   // setup()

void calcReading()                  // read data and calculate concentration
{
  unsigned long duration = 0;       // cumulative pulse width duration for current sampling period
  unsigned long elapsedTime = 0;    // register to track sample time cumulatively
  unsigned long sampleStart = 0;    // timestamp for start of current sampling period
  bool sampling = true;             // = false when sampling period is completed for one observation
  unsigned long timeStamp = 0;      // current time stamp value, ms
  float ratio = 0;                  // independent variable (for regression polynomial)
  float concentration = 0;          // dependent variable

  sampleStart = millis();           // start of current sampling period

  while (sampling)
  {
    duration += pulseIn(pinDustSensor, LOW);// Obtain current low pulse width and increment duration
    elapsedTime = millis() - sampleStart;
    if (elapsedTime > samplePeriod)
    {                               // If sampling period is attained then calculate concentration
      ratio = duration / (elapsedTime * 10.0);
                            // Equation proposed by by Chris Nafis after polynomial regression analysis
      concentration = (((1.1 * ratio - 3.8) * ratio) + 520.0) * ratio + 0.62;
      timeStamp = millis() / 1000;  // cosmetic display in seconds
      Serial.print("Timestamp: ");
      Serial.print(timeStamp);      // right-justification omitted
      Serial.print(" s, duration: ");
      Serial.print(duration);
      Serial.print(", concentration: ");
      Serial.print(concentration);
      Serial.println(" pp 0.01 cu ft");
      sampling = false;               // current sampling phase is complete
    }
  }
  return;
}                                     // calcReading()

void loop()
{
  if ((millis() - observationStart) > observationPeriod)
  {
    calcReading();                    // best to use helper routine for modularity
    observationStart = millis();      // reset the counters for the next sampling period
  }
}                                     // loop()

ppd42ns-02.ino.ino

C/C++
Display dust sensor readings in Arduino serial plotter window
/*
 * ppd42ns-02.ino
 * Basic Hello World exercise for Shinyei PPD42NS sensor to display in serial plotter
 * Author Chris Nafis
 * Reference //www.howmuchsnow.com/arduino/airquality/grovedust/
 * Update for tutorial use
 * 2019-03-25
 * 0.2 initial variation
 * 
 * All rights to this code are in accordance with Chris Nafis' publication.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 * Notes
 * This sample code for Shinyei PPD42NS/PPD42NJ Particle Sensor was published at:
 * http://www.sca-shinyei.com/pdf/PPD42NS.pdf <== HTTP 404 error now! :(
 * 
 * This code measures the concentration of particulate matter in air defined as:
 * particles per 0.01 cubic ft ( particles per 283 mL )
 * 
 * Based on work by Chris Nafis http://www.howmuchsnow.com/arduino/airquality/grovedust/
 * modified by baqwas
 * 
 * Connections:
 * Sensor Pins    Arduino Pins
 *  1          => GND
 *  2          => unused
 *  3          => +5 VDC
 *  4          => Digital pin 8
 *  5          => unused
 *
*/

const int pinDustSensor = 8;        // D8 on UNO Rev 3
unsigned long observationPeriod = 30000; // obtain a reading every minute (30,000 ms)
const unsigned long samplePeriod = 30000;// Sample time is 30 seconds for one batch of pulse data, milliseconds
                                    // time between logging = observationPeriod + samplePeriod
unsigned long observationStart = 0; // marker for start of current observation period

void setup()
{
  Serial.begin(115200);            // conventional default baud rate these days for serial port
  pinMode(pinDustSensor, INPUT);    // read sensor data from digital pin 8
  if (observationPeriod < samplePeriod) // sanity check for any fat-fingering situation
    observationPeriod = samplePeriod + 30000;
  observationStart = millis();      // mark the start time of current observation period
}                                   // setup()

void calcReading()                  // read data and calculate concentration
{
  unsigned long duration = 0;       // cumulative pulse width duration for current sampling period
  unsigned long elapsedTime = 0;    // register to track sample time cumulatively
  unsigned long sampleStart = 0;    // timestamp for start of current sampling period
  bool sampling = true;             // = false when sampling period is completed for one observation
  unsigned long timeStamp = 0;      // current time stamp value, ms
  float ratio = 0;                  // independent variable (for regression polynomial)
  float concentration = 0;          // dependent variable

  sampleStart = millis();           // start of current sampling period
  while (sampling)
  {
    duration += pulseIn(pinDustSensor, LOW);// Obtain current low pulse width and increment duration
    elapsedTime = millis() - sampleStart;
    if (elapsedTime > samplePeriod)
    {                               // If sampling period is attained then calculate concentration
      ratio = duration / (elapsedTime * 10.0);
                            // Equation proposed by by Chris Nafis after polynomial regression analysis
      concentration = (((1.1 * ratio - 3.8) * ratio) + 520.0) * ratio + 0.62;
      Serial.println(concentration);  // value only for serial plotter, please!
      sampling = false;               // current sampling phase is complete
    }
  }
  return;
}                                     // calcReading()

void loop()
{
  if ((millis() - observationStart) > observationPeriod)
  {
    calcReading();                    // best to use helper routine for modularity
    observationStart = millis();      // reset the counters for the next sampling period
  }
}                                     // loop()

serduino.py

Python
Read bytes on serial line at host computer and write corresponding characters to an output file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
#  serduino.py
#  read data and echo data on specified serial port
#  v0.1 2019-03-26 Initial DRAFT
#  Copyright 2019 El Doktor <baqwas@parkcircus.org>
#  
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#
#  This is a convoluted program for future extensions in logging exercises.
#
#  return codes
#  	0		normal completion code
#	-1		could not open log file to write data from serial port
#	-2		could not open serial port
#	-3		timeout occurred before data received from serial port
#	-4		other exception occurred
#

import serial							# https://pythonhosted.org/pyserial/
import sys								# https://docs.python.org/3/library/sys.html

def serduino(args):						# read data from serial port continuously

	try:
		filename = "output.txt"			# absolute or relative path to log file
		mode = "wt"						# processing modes for log file: write, text
		myLog = open(filename, mode)	# built-in function to open log file
	except exceptions.IOError:			# https://docs.python.org/3.3/library/exceptions.html#IOError
		return -1						# could NOT open log file
										# starts thread to read from the (internal) socket
	try:
		port = "/dev/ttyACM1"			# should match that selected in Arduino IDE
		timeout = 0						# non-blocking mode preferred; = None, 0 or n seconds
		baud = 115200					# other parameters are defaults:
										# 	PARITY_NONE
										# 	STOPBITS_ONE
										# 	EIGHTBITS
		myPort = serial.Serial(port=port, baudrate=baud, timeout=timeout)
	except serial.SerialException:		# raised on other serial port errors (derived from IOError)
		return -2						# print("Data not read")	# for debug purposes only	

	try:
		while myPort.is_open:			# get state of serial port
			myData = myPort.readline().decode("utf-8") # Reads & returns bytes until EOL as a string
			if len(myData) > 2:			# skip records containing only <CR><LF> even though the test Arduino code does not send any
										# \r\n = assumption for Arduino -> Ubuntu serial port
				if myLog.write(myData) > 0: # thread safe writing string (and returns number of chars written)
					myLog.flush()		# just being extra diligent (perhaps redundant)
	except serial.SerialTimeoutException:
		return -3						# timeout encountered
	except KeyboardInterrupt:
		pass							# a brute force method to stop the run
	except:
		return -4						# catch all other exceptions
	finally:
		myPort.close()					# exit serial port reader thread
	myLog.close()						# flush buffers, release allocated resources

	return 0							# serduino

if __name__ == '__main__':
	sys.exit(serduino(sys.argv))		# exit after calling primary module

Credits

Matha Goram

Matha Goram

13 projects • 2 followers
I'll do this later
Thanks to Chris Nafis.

Comments

Add projectSign up / Login