Arduino-based air quality monitor - new version

In this video I show you an extended version of the previous (https://youtu.be/FahmF_p4xJc) air quality monitor. I added a new sensor, so now I can measure pressure and humidity ("weather features"). I cleaned up the previous code and I added an OLED display to the ccircuit, so the data can be monitored without having a computer connected to the Arduino.



Wiring diagram

This project contains three i2C devices: OLED display, CCS811 air quality sensor, BME280 temperature/air pressure/humidity sensor. They are all connected to the same pins: A4 (SDA) and A5 (SCL). The dust sensor is connected to the Arduino in two way…

This project contains three i2C devices: OLED display, CCS811 air quality sensor, BME280 temperature/air pressure/humidity sensor. They are all connected to the same pins: A4 (SDA) and A5 (SCL). The dust sensor is connected to the Arduino in two ways. One digital pin is switching the infrared LED on and off, and an analog pin is measuring the output voltage which is proportional to the amount of dust in the dust sensor.



Arduino source code

//Info: GP2Y1010AU0F Dust sensor
//The dust sensor reaches the maximum output pulse 0.28 ms after the LED was turned on
//This sensor is based on voltage measurement
//CCS811 is a thermometer-VOC meter and uses a premade library -I2C
//BME280 is a thermometer, humidity and pressure sensor - I2C
//OLED: any OLED display can be used, this particular tutorial uses the 128x32 type - I2C

//OLED Libraries
#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"
#define I2C_ADDRESS 0x3C //Address
#define RST_PIN -1 //For OLED with no reset pin
SSD1306AsciiAvrI2c display;

#include <Wire.h>

//VOC sensor
#include "Adafruit_CCS811.h" //We load the library for the gas sensor
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
Adafruit_CCS811 ccs;

//BME280
Adafruit_BME280 bme; // I2C
//Use these #define lines if you want to use it with SPI
//#define BME_SCK 13
//#define BME_MISO 12
//#define BME_MOSI 11
//#define BME_CS 10
#define SEALEVELPRESSURE_HPA (1013.25)

//-------------------------------------------
//variables
float co2Amount; //Amount (ug/m^3) of CO2
float CCSTemp; //temperature measured by the CCS811

//Dust sensor
int dustmeasurePin = A3; //The output of the dust sensor is connected to A3 (AD converter pin)
int dustLEDPin = 2; //The IR pin inside the dust sensor is connected to D2 (digital output pin)
float outBits = 0; //AD-converter raw output
float dustDensity = 0; //dust density, based on the formula

//BME280  - Temperature, humidity and pressure sensor.
float BME280Temp;
float BME280Humidity;
float BME280Pressure;
float BME280Altitude;

unsigned long delayTime; //delaytime for BME280 library

//String for storing the formatted output data (serial port)
String outputData;

void setup() 
{
	Serial.begin(9600); //Start serial

	Serial.println("*Dust and VOC sensor"); //Print message. I use '*' to tell the receiver software that this is not a measurement data

  //CCS
	if(!ccs.begin()) //if we are not able to start the VOC sensor, print the following message
	{
		Serial.println("* Failed to start sensor! Please check your wiring.");
		while(1); //And hold the code here
	}
	
	while(!ccs.available()); 
  //endofCCS

	float temp = ccs.calculateTemperature(); //calculate the temperature
	ccs.setTempOffset(temp - 25.0); //set the offset using the 	
  //BME280
   unsigned status;
   status = bme.begin(0x76);
   if (!status) {
        Serial.println("*Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
        Serial.print("*SensorID was: 0x"); Serial.println(bme.sensorID(),16);
        Serial.print("*        ID of 0xFF probably means a bad address, a BMP 180 or BMP 085\n");
        Serial.print("*   ID of 0x56-0x58 represents a BMP 280,\n");
        Serial.print("*        ID of 0x60 represents a BME 280.\n");
        Serial.print("*        ID of 0x61 represents a BME 680.\n");
        while (1) delay(10);
    }    
    delayTime = 1000;
   //endofBME280

  //OLED part
  #if RST_PIN >= 0
  display.begin(&Adafruit128x32, I2C_ADDRESS, RST_PIN);
  #else // RST_PIN >= 0
  display.begin(&Adafruit128x32, I2C_ADDRESS);
  #endif // RST_PIN >= 0
  //Call oled.setI2cClock(frequency) to change from the default frequency.

  display.setFont(System5x7);
  display.set1X();
  display.clear();
  //--endofOLED----

  pinMode(dustLEDPin,OUTPUT); //the pin for the dust sensor's LED is set as an output
}

void loop() 
{
  //we do this part outside of the serial as well, since we need to measure anyway if we want to see the info on the OLED
       measureDust();
       delay(500);
       measureVOCs();
       delay(500);
       MeasureBME280();
       delay(500);
       //Printing the data on the OLED
       refreshOLED();
       delay(1000);
  //

  //We only enter this part, if we sent a character "S" to the serial
  //The printing can be stopped by sending an "N"
	if (Serial.available() > 0) //if there's something on the serial
	{
    char commandCharacter = Serial.read(); //we use characters (letters) for controlling the switch-case

		switch (commandCharacter) 
		{
		  case 'S': //S: start

		  while(Serial.read() != 'N') //while we don't send N through the serial, the following functions are looping:
		  {			
       measureDust();
       delay(500);
       measureVOCs();
       delay(500);
       MeasureBME280();
       delay(500);
       //Printing the data on the OLED
       refreshOLED();
       delay(1000);
			 printFormattedData();     //prints the pre-formatted line on the serial port
       
		  }
		  break;

		  default:
			  //
		  break;
		  }     
  }
}

void measureDust()
{
  digitalWrite(dustLEDPin,LOW); //turn ON the LED
  
  delayMicroseconds(280); // wait 0.28 ms = 280 us

  outBits = analogRead(dustmeasurePin); //measure the peak of the output pulse  
  
  digitalWrite(dustLEDPin,HIGH); //turn OFF the LED     

  /*
  If you want to get the converted data on the Arduino terminal, 
  //uncomment this part and replace the outbits to dustDensity in printFormattedData()
  
  dustDensity = 1000* ( 0.17 * ((5.0 / 1024) * outBits) - 0.1); //dust density in ug/m^3
  */
  
}

void measureVOCs()
{
	if(ccs.available()) //If we can communicate with the VOC sensor
	{
		if(!ccs.readData()) //if we are not reading (i.e. the devide is available)
		{
			co2Amount = ccs.geteCO2(); //read CO2
			ccs.getTVOC(); //read temperature
			CCSTemp = ccs.calculateTemperature(); //calculate temperature
		}
		else
		{
			Serial.println("ERROR!"); //Print error message
			while(1); //wait here
		}
	}
}

void printFormattedData() //Formatting the output so the receiver software can process it
{  
                           
  outputData = (String)CCSTemp +  '\t' + (String)BME280Temp + '\t' + (String)bme.readHumidity() + '\t' + (String)BME280Pressure + '\t' 
  + (String)outBits + '\t' + (String)co2Amount; //for the output
  Serial.println(outputData); //print the formatted line

  //Format on serial output: Temp1 (CCS) /tab/ Temp2 (BME) /tab/ Humidity /tab/ Pressure /tab/ Dust /tab/ CO2 /newline/
  
  //outBits can be replaced to dustDensity to print the converted data instead of the raw data.  
  //dustDensity = 1000* ( 0.17 * ((5.0 / 1024) * outBits) - 0.1); //dust density in ug/m^3
}

void MeasureBME280()
{
//Reading all the available values from the BME280 chip
BME280Temp = bme.readTemperature();
BME280Humidity = bme.readHumidity();
BME280Pressure = bme.readPressure() / 100.0F;
BME280Altitude = bme.readAltitude(SEALEVELPRESSURE_HPA);  
}

void refreshOLED()
{
  //128x32 OLED
  //1st line of the OLED
  display.clear();  
  display.setCursor(0, 0); 
  display.print("T: ");
  display.print(BME280Temp);
  display.print("C  H: ");
  display.print(BME280Humidity);
  display.println("%");

  //2nd line
  display.setCursor(0, 1); 
  display.print("P: ");
  display.print(BME280Pressure);
  display.print(" A: ");
  display.print(BME280Altitude);
  display.println("m"); 
   
  //3rd line
  display.setCursor(0, 2); 
  display.print("CO2: ");
  display.print(co2Amount);  
  display.print(" ppm");
  
  //4th line
  display.setCursor(0, 3); 
  display.print("D: ");
  display.print(1000* ( 0.17 * ((5.0 / 1024) * outBits) - 0.1));
  display.println(" ug/m3");  
  
}

Previous
Previous

Testing and comparing different Peltier coolers - Part 1- Introduction

Next
Next

BCC Schmid factor from Euler angles using the orientation matrix