yvesmorele
Published © LGPL

Particles Detector for Air Quality

In this project I show how to build a particles detector with data display, data backup on SD card and IoT with Android application.

IntermediateProtip21,676
Particles Detector for Air Quality

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
Arduino Proto Shield
Arduino Proto Shield
×1
SainSmart 1.8″ Color TFT LCD Display for Arduino
×1
PMS 5003
×1
Adafruit neopixel ring
×1

Software apps and online services

Arduino IDE
Arduino IDE
MIT App Inventor 2
MIT App Inventor 2

Hand tools and fabrication machines

Solder Wire, Lead Free
Solder Wire, Lead Free
Soldering iron (generic)
Soldering iron (generic)
electronic project box

Story

Read more

Schematics

TFT connecting

SainSmart 1.8″ TFT Arduino Display connecting

PMS 5003

connecting PMS 5003

complete schematic

the full connecting

Code

PMS 5003 with ST7735 and neopixel ring bluetooth

Arduino
arduino code
#include <SoftwareSerial.h>
#include <Wire.h>    // Bibliothque pour l'I2C
#include "RTClib.h"  // Bibliothque pour le module RTC
RTC_DS1307 RTC; 
#include <Adafruit_NeoPixel.h>

// Which pin on the Arduino is connected to the NeoPixels?
#define PIN        6 // On Trinket or Gemma, suggest changing this to 1

// 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);


SoftwareSerial pmsSerial(2, 3);
#define cs   10
#define dc   9
#define rst  8  // you can also connect this to the Arduino reset

#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 dmarrage du loop()


#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif

// Option 1: use any pins but a little slower
//Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, mosi, sclk, rst);

// 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 vrifiant si la carte SD est prsente dans l'appareil
  {
   Serial.print("Defaut SD");
    return;
  }
 Serial.print("Carte SD OK");

 File data = SD.open("donnees.txt",FILE_WRITE);              // Ouvre le fichier "donnees.txt"
  data.println(""); data.println("Dmarrage acquisition");    // Ecrit dans ce fichier
  data.close();
  
  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
  Serial.println("init");
  // our debugging output
  
  
tft.fillScreen(ST7735_BLACK);
  // sensor baud rate is 9600
  pmsSerial.begin(9600);

  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.setBrightness(2);

  
}

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;
};

  
  
struct pms5003data data;
    
void loop()
{
    
  pixels.clear(); // Set all pixel colors to 'off'
  
   DateTime now=RTC.now(); //Rcupre l'heure et le date courante
  
 //affiche_date_heure(now);

  
    temps = ((millis() - tempsInit))/1000 ;  // Dmarrage du chrono 



    
  if (readPMSdata(&pmsSerial)) {
    
 tft.fillScreen(ST7735_BLACK);
    
     tft.setCursor(10, 5);
      tft.setTextColor(ST7735_WHITE);
     tft.println(" nbre parts/ 0.1 l");

     
    tft.setCursor(10, 17);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("0.3 um  ");tft.print(data.particles_03um);


tft.setCursor(10, 29);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("0.5 um  ");tft.print(data.particles_05um);

tft.setCursor(10, 41);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("1.0 um  ");tft.print(data.particles_10um);


tft.setCursor(10, 53);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("2.5 um  ");tft.print(data.particles_25um);

tft.setCursor(10, 65);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("5.0 um  ");tft.print(data.particles_50um);

tft.setCursor(10, 77);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("10 um  ");tft.print(data.particles_100um);

tft.setCursor(2, 89);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("PM 1.0 ");tft.setTextColor(ST7735_YELLOW);tft.print(data.pm10_standard);tft.setTextColor(ST7735_GREEN);tft.print(" microg/m3");

  tft.setCursor(2, 100);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("PM 2.5 ");tft.setTextColor(ST7735_YELLOW);tft.print(data.pm25_standard);tft.setTextColor(ST7735_GREEN);tft.print(" microg/m3");


  tft.setCursor(2, 110);
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(1);
  tft.print("PM 10 ");tft.setTextColor(ST7735_YELLOW);tft.print(data.pm100_standard);tft.setTextColor(ST7735_GREEN);tft.print(" microg/m3");
  


  tft.setCursor(10, 5);
      tft.setTextColor(ST7735_WHITE);
      tft.setTextSize(1);
     tft.println(" nbre parts/ 0.1 l");

    // Serial.print(temps);
 //   Serial.print (" ");
  Serial.print ("#");
   Serial.print ("03m ");
   Serial.print(data.particles_03um);
   Serial.print (" ");
    Serial.print ("05m ");
    Serial.print(data.particles_05um);
    Serial.print (" ");
    Serial.print ("1m  ");
    Serial.print(data.particles_10um);
    Serial.print (" ");
    Serial.print ("25m ");
    Serial.print(data.particles_25um);
    Serial.print (" ");
    Serial.print ("50m ");
    Serial.print(data.particles_50um);
    Serial.print (" ");
    Serial.print ("100m  ");
    Serial.print(data.particles_100um);
    Serial.println (" ");
    
nombre_leds =int (((float (data.particles_03um)/65535)*24));
//nombre_leds =(8);
Serial.println (nombre_leds);

     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) {

      
      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.

    
    
  // Dfinition donnes
  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);

   //Ecriture des donnes 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();
  
  }

  
}

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;
  }

  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);

  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }

  /* 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);
  }

  // put it into a nice struct :)
  memcpy((void *)&data, (void *)buffer_u16, 30);

  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
    
  }
  // success!
  return true;
   
}

//Converti le numro 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 "   ";
  }
}

// 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());

           

  
 // Serial.print(jour);
//  Serial.print("  ");
 // Serial.print(heure);
  //Serial.print("  ");
  File data=SD.open("donnees.txt",FILE_WRITE);
  data.print(jour + " " + heure+" " ); 
  data.close();

  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);

 delay(500);
  
}

//permet d'afficher les nombres sur deux chiffres
String Vers2Chiffres(byte nombre) {
  String resultat = "";
  if(nombre < 10)
    resultat = "0";
  return resultat += String(nombre,DEC);  
}

monitoring PMS 5003

Java
mit app inventor application
No preview (download only).

new software with real time curve

Arduino
display improvement
#include <SoftwareSerial.h>
#include <Wire.h>    // Bibliothque pour l'I2C
#include "RTClib.h"  // Bibliothque pour le module RTC
RTC_DS1307 RTC; 
#include <Adafruit_NeoPixel.h>

// Which pin on the Arduino is connected to the NeoPixels?
#define PIN        6 // On Trinket or Gemma, suggest changing this to 1

// 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);


SoftwareSerial pmsSerial(2, 3);
#define cs   10
#define dc   9
#define rst  8  // you can also connect this to the Arduino reset

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library resolution 128X160

#include <SPI.h>
#include<SD.h>
const int cs_sd=4;
int temps;                // temps d'acquisition
double tempsInit;         // initialisation du timer au dmarrage du loop()


#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif

// Option 1: use any pins but a little slower
//Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, mosi, sclk, rst);

// 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;
 int x=2;
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 vrifiant si la carte SD est prsente dans l'appareil
  {
   Serial.print("Defaut SD");
    return;
  }
 Serial.print("Carte SD OK");

 File data = SD.open("donnees.txt",FILE_WRITE);              // Ouvre le fichier "donnees.txt"
  data.println(""); data.println("Dmarrage 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);

  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.setBrightness(2);

  
}

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;
};

  
  
struct pms5003data data;
    
void loop()
{
    
  pixels.clear(); // Set all pixel colors to 'off'
  
   DateTime now=RTC.now(); //Rcupre l'heure et le date courante
  
 affiche_date_heure(now);

  
    temps = ((millis() - tempsInit))/1000 ;  // Dmarrage du chrono 



    
  if (readPMSdata(&pmsSerial)) {
     
     
    tft.setCursor(10, 17);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.setCursor(10, 29);
  tft.print("0.3 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_03um);
  courbe();

tft.setCursor(10, 41);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("0.5 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_05um);

tft.setCursor(10, 53);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("1.0 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_10um);


tft.setCursor(10, 65);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("2.5 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_25um);

tft.setCursor(10, 77);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("5.0 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_50um);

tft.setCursor(10, 89);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("10.0 um  ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.particles_100um);




  tft.setCursor(10, 101);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("PM 10 ");tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(data.pm100_standard);tft.setTextColor(ST7735_GREEN,ST7735_BLACK);tft.print(" mic/m3 ");
  
  tft.drawLine(0,113,0,158,ST7735_RED);
  tft.drawLine(0,158,128,158,ST7735_RED);
 
  
    
nombre_leds =int (((float (data.particles_03um)/65535)*24));
//nombre_leds =(8);
Serial.println (nombre_leds);

     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) {

      
      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.

    
    
  // Dfinition donnes
  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);

   //Ecriture des donnes 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();
  
  }

  
}

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;
  }

  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);

  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }

  
  
  // 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);
  }

  // put it into a nice struct :)
  memcpy((void *)&data, (void *)buffer_u16, 30);

  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
    
  }
  // success!
  return true;
   
}

//Converti le numro 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 "   ";
  }
}

// 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());

           

  
  
  File data=SD.open("donnees.txt",FILE_WRITE);
  data.print(jour + " " + heure+" " ); 
  data.close();

  tft.setCursor(18, 5);
 tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
 tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(jour);
  tft.setCursor(30, 17);tft.setTextColor(ST7735_CYAN,ST7735_BLACK);tft.print(heure);

  
}

//permet d'afficher les nombres sur deux chiffres
String Vers2Chiffres(byte nombre) {
  String resultat = "";
  if(nombre < 10)
    resultat = "0";
  return resultat += String(nombre,DEC);  
}

void courbe() {


  int nouvelleValeur03;
  int nouvelleValeur05;
  int nouvelleValeur10;
  int nouvelleValeur25;
  int nouvelleValeur50;
  
  
  nouvelleValeur03 = map((data.particles_03um), 2, 65535, 158, 113); 
  nouvelleValeur05 = map((data.particles_05um), 2, 65535, 158, 113);
  nouvelleValeur10 = map((data.particles_10um), 2, 65535, 158, 113);
  nouvelleValeur25 = map((data.particles_25um), 2, 65535, 158, 113);
  nouvelleValeur50 = map((data.particles_50um), 2, 65535, 158, 113);
  
  x++;
  
  tft.drawPixel(x,nouvelleValeur03,ST7735_CYAN);
  tft.drawPixel(x,nouvelleValeur05,ST7735_YELLOW);
  tft.drawPixel(x,nouvelleValeur10,ST7735_RED);
    tft.drawPixel(x,nouvelleValeur25,ST7735_WHITE);
    tft.drawPixel(x,nouvelleValeur50,ST7735_BLUE);
  
  if (x>123) {
    x=2;
    tft.fillRect(2,113,126,45,ST7735_BLACK);
    
  }
}

library GFX

library for gfx

SD card library

library for SD card

neopixel ring library

library for use neopixel ring

Credits

yvesmorele

yvesmorele

9 projects • 45 followers
chemical scientist

Comments