Introduction: System for Monitoring Air Quality for Particulates Pollution

INTRO :


1 In this project I show how to build a particle detector with data display, data backup on SD card and IOT. Visually a neopixels ring display indicates the air quality.


2 Air quality is an increasingly important concern today. there are systems to measure the dust rate but they are very expensive. There are low-cost, high-quality particle detectors on the market, as shown by some studies.

for example :

https://www.atmos-meas-tech.net/11/4823/2018/amt-1...

3 I therefore decided to build a device capable of measuring the number of particles by size classes (0.5µm to 10 µm), visually with a simple display of the result (neo pixel ring), a more detailed display on a TFT screen and a time-stamped backup on an SD card.

4 In addition I have added a bluetooth communication module to be able to communicate with an android application and thus publish the results on an IOT server.

5 The overall cost of the whole does not exceed 60 €

Supplies

-Arduino uno R3

-Arduino proto shield

-TFT screen ST7735

-Neopixel ring 24 led

-Plantower PMS5003

-HC-06 bluetooth module

Step 1: Connecting the Components

the different components are connected according to the diagram above

Step 2: Library and Arduino Program

1 the library

for the TFT screen

https://github.com/adafruit/Adafruit-GFX-Library

for the neo pixel ring

https://github.com/adafruit/Adafruit_NeoPixel

for the sd card

https://github.com/arduino-libraries/SD

2 the arduino sketch


#include <softwareserial.h><br>#include <wire.h>    // Bibliothèque pour l'I2C
#include "RTClib.h"  // Bibliothèque pour le module RTC
RTC_DS1307 RTC; 
#include <adafruit_neopixel.h></adafruit_neopixel.h></wire.h></softwareserial.h></p><p>// Which pin on the Arduino is connected to the NeoPixels?
#define PIN        6 // On Trinket or Gemma, suggest changing this to 1</p><p>// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 24 // Popular NeoPixel ring size
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
uint32_t vert = pixels.Color(0, 250, 0);
uint32_t orange = pixels.Color(250, 250, 0);
uint32_t rouge = pixels.Color(255, 0, 0);</p><p>SoftwareSerial pmsSerial(2, 3);
#define cs   10
#define dc   9
#define rst  8  // you can also connect this to the Arduino reset</p><p>#include <adafruit_gfx.h>    // Core graphics library
#include <adafruit_st7735.h> // Hardware-specific library
#include <spi.h>
#include<sd.h>
const int cs_sd=4;
int temps;                // temps d'acquisition
double tempsInit;         // initialisation du timer au démarrage du loop()</sd.h></spi.h></adafruit_st7735.h></adafruit_gfx.h></p><p>#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif</p><p>// Option 1: use any pins but a little slower
//Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, mosi, sclk, rst);</p><p>// Option 2: must use the hardware SPI pins
// (for UNO thats sclk = 13 and sid = 11) and pin 10 must be
// an output. This is much faster - also required if you want
// to use the microSD card (see the image drawing example)
Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);
 float nombre_leds=0;
void setup() {
Serial.begin(9600);
 // Initialise la liaison I2C  
  Wire.begin();
 
  // Initialise le module RTC
  RTC.begin();
  
Serial.print("init SD");
  delay(1000);
  if(!SD.begin(cs_sd))    //Condition vérifiant si la carte SD est présente dans l'appareil
  {
   Serial.print("Defaut SD");
    return;
  }
 Serial.print("Carte SD OK");</p><p> File data = SD.open("donnees.txt",FILE_WRITE);              // Ouvre le fichier "donnees.txt"
  data.println(""); data.println("Démarrage acquisition");    // Ecrit dans ce fichier
  data.close();
  
  tft.initR(INITR_GREENTAB);   // initialize a ST7735S chip, black tab
  Serial.println("init");
  // our debugging output
  
  
tft.fillScreen(ST7735_BLACK);
  // sensor baud rate is 9600
  pmsSerial.begin(9600);</p><p>  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.setBrightness(2);</p><p>  
}</p><p>struct pms5003data {
  uint16_t framelen;
  uint16_t pm10_standard, pm25_standard, pm100_standard;
  uint16_t pm10_env, pm25_env, pm100_env;
  uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
  uint16_t unused;
  uint16_t checksum;
};</p><p>  
  
struct pms5003data data;
    
void loop()
{
    
  pixels.clear(); // Set all pixel colors to 'off'
  
   DateTime now=RTC.now(); //Récupère l'heure et le date courante
  
 //affiche_date_heure(now);</p><p>  
    temps = ((millis() - tempsInit))/1000 ;  // Démarrage du chrono </p><p>    
  if (readPMSdata(&pmsSerial)) {
    
// tft.fillScreen(ST7735_BLACK);
    
     tft.setCursor(10, 5);
      tft.setTextColor(ST7735_WHITE);
     tft.println(" nbre parts/ 0.1 l");</p><p>     
    tft.setCursor(10, 17);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.setCursor(10, 17);
  tft.print("0.3 um  ");tft.print(data.particles_03um);tft.print("     ");</p><p>tft.setCursor(10, 29);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("0.5 um  ");tft.print(data.particles_05um);tft.print("     ");</p><p>tft.setCursor(10, 41);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("1.0 um  ");tft.print(data.particles_10um);tft.print("     ");</p><p>tft.setCursor(10, 53);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("2.5 um  ");tft.print(data.particles_25um);tft.print("     ");</p><p>tft.setCursor(10, 65);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("5.0 um  ");tft.print(data.particles_50um);tft.print("     ");</p><p>tft.setCursor(10, 77);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("10 um  ");tft.print(data.particles_100um);tft.print("     ");</p><p>tft.setCursor(2, 89);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("PM 1.0 ");tft.setTextColor(ST7735_YELLOW,ST7735_BLACK);tft.print(data.pm10_standard);tft.print(" ");tft.setTextColor(ST7735_GREEN,ST7735_BLACK);tft.print(" microg/m3   ");
  
  </p><p>  
  tft.setCursor(2, 100);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("PM 2.5 ");tft.setTextColor(ST7735_YELLOW,ST7735_BLACK);tft.print(data.pm25_standard);tft.setTextColor(ST7735_GREEN,ST7735_BLACK);tft.print(" microg/m3   ");</p><p>  tft.setCursor(2, 110);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("PM 10 ");tft.setTextColor(ST7735_YELLOW,ST7735_BLACK);tft.print(data.pm100_standard);tft.setTextColor(ST7735_GREEN,ST7735_BLACK);tft.print(" microg/m3   ");
  </p><p>  tft.setCursor(10, 5);
      tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
      tft.setTextSize(1);
     tft.println(" nbre parts/ 0.1 l");</p><p>    // Serial.print(temps);
 //   Serial.print (" ");
  Serial.print ("#");
   Serial.print ("03µm ");
   Serial.print(data.particles_03um);
   Serial.print (" ");
    Serial.print ("05µm ");
    Serial.print(data.particles_05um);
    Serial.print (" ");
    Serial.print ("1µm  ");
    Serial.print(data.particles_10um);
    Serial.print (" ");
    Serial.print ("25µm ");
    Serial.print(data.particles_25um);
    Serial.print (" ");
    Serial.print ("50µm ");
    Serial.print(data.particles_50um);
    Serial.print (" ");
    Serial.print ("100µm  ");
    Serial.print(data.particles_100um);
    Serial.println (" ");
    
nombre_leds =int (((float (data.particles_03um)/65535)*24));
//nombre_leds =(8);
Serial.println (nombre_leds);</p><p>     if ((nombre_leds<=8) and (nombre_leds>=1)){
  pixels.fill(vert , 0, nombre_leds);
    
    }
    
    else if ((nombre_leds<=16) and (nombre_leds>=8)) {
      pixels.fill(vert , 0, 8);
      pixels.fill(orange , 8, ((nombre_leds)-8));
      
    }
    else if  (nombre_leds>16) {</p><p>      
      pixels.fill(vert , 0, 8);
      pixels.fill(orange , 8, 8);
      pixels.fill(rouge , 16, ((nombre_leds)-16));
    }
    else  if (nombre_leds<=1) {
      pixels.fill(vert , 0, 1);
    }
    pixels.show();   // Send the updated pixel colors to the hardware.</p><p>    
    
  // Définition données
  String PM03=String(data.particles_03um);
  String PM05=String(data.particles_05um);
  String PM10=String(data.particles_10um);
  String PM25=String(data.particles_25um);
  String PM50=String(data.particles_50um);
  String PM100=String(data.particles_100um);
  String PMS10=String(data.pm10_standard);
  String PMS25=String(data.pm25_standard);
  String PMS100=String(data.pm100_standard);
 
  
  
  String Temps=String(temps);</p><p>   //Ecriture des données dans le fichier texte
  File data=SD.open("donnees.txt",FILE_WRITE);
  data.println( Temps + " " + PM03+ " " + PM05 +" " +PM10+" " +PM25+" "+PM50+" " +PM100+" "+PMS10+" "+PMS25+" "+PMS100+" "); 
  data.close();
  
  }</p><p>  
}</p><p>boolean readPMSdata(Stream *s) {
  if (! s->available()) {
    return false;
  }
  
  // Read a byte at a time until we get to the special '0x42' start-byte
  if (s->peek() != 0x42) {
    s->read();
    return false;
  }</p><p>  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);</p><p>  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }</p><p>  /* debugging
  for (uint8_t i=2; i<32; i++) {
    Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");
  }
  Serial.println();
  */
  
  // The data comes in endian'd, this solves it so it works on all platforms
  uint16_t buffer_u16[15];
  for (uint8_t i=0; i<15; i++) {
    buffer_u16[i] = buffer[2 + i*2 + 1];
    buffer_u16[i] += (buffer[2 + i*2] << 8);
  }</p><p>  // put it into a nice struct :)
  memcpy((void *)&data, (void *)buffer_u16, 30);</p><p>  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
    
  }
  // success!
  return true;
   
}</p><p>//Converti le numéro de jour en jour /!\ la semaine commence un dimanche
String donne_jour_semaine(uint8_t j){ 
  switch(j){
   case 0: return "DIM";
   case 1: return "LUN";
   case 2: return "MAR";
   case 3: return "MER";
   case 4: return "JEU";
   case 5: return "VEN";
   case 6: return "SAM";
   default: return "   ";
  }
}</p><p>// affiche la date et l'heure sur l'écran
void affiche_date_heure(DateTime datetime){
  
  // Date 
  String jour = donne_jour_semaine(datetime.dayOfTheWeek()) + " " + 
                Vers2Chiffres(datetime.day())+ "/" + 
                Vers2Chiffres(datetime.month())+ "/" + 
                String(datetime.year(),DEC);
  
  // heure
  String heure = "";
  heure  = Vers2Chiffres(datetime.hour())+ ":" + 
           Vers2Chiffres(datetime.minute())+ ":" + 
           Vers2Chiffres(datetime.second());</p><p>  
  Serial.print(jour);
  Serial.print("  ");
  Serial.print(heure);
  //Serial.print("  ");
  File data=SD.open("donnees.txt",FILE_WRITE);
  data.print(jour + " " + heure+" " ); 
  data.close();</p><p>  tft.setCursor(2, 120);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("date ");tft.setTextColor(ST7735_YELLOW);tft.print(jour);tft.setTextColor(ST7735_GREEN);tft.setCursor(2, 130);tft.print(" heure");tft.setTextColor(ST7735_YELLOW);tft.print(heure);</p><p> delay(500);
  
}</p><p>//permet d'afficher les nombres sur deux chiffres
String Vers2Chiffres(byte nombre) {
  String resultat = "";
  if(nombre < 10)
    resultat = "0";
  return resultat += String(nombre,DEC);  
}</p>

Step 3: MIT App Inventor 2 Program

this is the MIT app inventor code block

Step 4: THE RESULT

here is the video of the result