Qualità dell'aria

Progettiamo una stazione per la qualità dell’aria con Arduino Nano e un display TFT da 2.8″

Il progetto che presentiamo oggi è la nostra prima stazione per monitorare la qualità dell’aria, un sistema in grado di fornire valori su gas nocivi per la salute e avvisare l’utente tramite un segnale di allarme. Vista la quantità di collegamenti da fare, abbiamo optato per la realizzazione di un PCB su cui montare tutte le nostre componenti, grazie alla collaborazione con PCBWay.

Progetto

Monitorare la qualità dell’aria è molto importante, soprattutto se si vive in città, nei pressi di zone molto trafficate o di poli industriali. Tutti i nostri dati appariranno su un display touch-screen in modo organizzato, con le relative progress-bar con 3 livelli: verde per indicare valori nella norma, gialla per indicare valori limite, rossa per indicare valori pericolosi.

  • qualità dell'aria

I gasi rilevati saranno il Monossido di carbonio, l’ammoniaca, il biossido di azoto, l’anidride carbonica e le particelle organiche volatili. Il display ci permetterà anche di attivare o disattivare l’allarme, programmato per suonare quando i valori superano i limiti consentiti, e di attivare o disattivare l’illuminazione del display e consentire un risparmio energetico.

Componenti

Per la stazione della qualità dell’aria partiamo come sempre dalla dev-board utilizzata, ovvero Arduino Nano, scelto principalmente per le ridotte dimensioni. Ad esso abbiamo connesso, tramite interfaccia SPI, un display TFT da 2.8″ con touch-screen con chip ILI9341. Adoperando invece l’interfaccia I2C, abbiamo connesso il sensore CCS811, per monitorare CO2 e TVOC, mentre per monitorare CO, NH3 e NO2 abbiamo adoperato il sensore MiCS-6814.

  • qualità dell'aria

Arduino Nano controllerà i valori rilevati dai sensori per poi attivare un buzzer attivo se essi superano le loro soglie limite. Per attivare o disattivare la luminosità del display ci siamo affidati ad un transistor NPN 2n2222. Tutto il sistema va alimentato con i 5V, quindi possiamo adoperare sia il connettore dati di Arduino Nano che gli appositi fori predisposti sul PCB che ci ha fornitore PCBWay su cui saldare un terminale a vite.

  • qualità dell'aria

Il PCB sarà possibile realizzarlo recandosi sul sito https://www.pcbway.com/ e caricando il file Gerber che forniamo in questo articolo.

Lista componenti su Amazon:



Collegamenti

Come detto prima i collegamenti avverranno tutti su un PCB su due layer, che potete scaricare nel formato Gerber, insieme allo sketch, da questo LINK.

Il display TFT richiede 9 resistenze da 10K Ω,per far lavorare il display senza problemi con la tensione da 5V. Vediamo nel dettaglio come collegare i 14 pin per sfruttare sia l’output del display che l’input del touch-screen.

TFT Dispaly 2.4" SPIArduino UNO
VCC5 V
GNDGND
CS10
RESET8
DC9
SDI/MOSI11
SCK13
LED5 V
SDD/MISO12
T_CLK3
T_CS4
T_DIN5
T_DO6
T_IRQ7

Il transistor NPN, collegato al pin digitale 2, che richiederà invece una resistenza da 220 Ω, permetterà il passaggio della corrente tra il pin Vcc del display (collettore) al pin LED (emettitore), mentre il buzzer lo collegheremo al pin analogico A3 e lo adopereremo come se fosse un pin digitale.

Qualità aria

Come detto già in precedenza, per il sensore CCS811 adoperiamo l’interfaccia I2C, ciò significa che i pin SDA e SCL saranno rispettivamente A4 e A5, I pin analogici A0, A1 e A2 serviranno per i collegamenti dati con il sensore MiCS-6814. Infine, per resettare il sistema, se necessario, abbiamo installato un pulsante.

Codice

Nel codice adopereremo le libreri Adafruit_ILI9341 e Adafruit_GFX per gestire il display, la libreria URTouch per il touch-screen e la libreria Adafruit_CCS811 per l’omonimo sensore.


#include <Adafruit_ILI9341.h>
#include "Adafruit_GFX.h"
#include <URTouch.h>
#include <SPI.h>
#include "Adafruit_CCS811.h"

Definiamo i pin per i collegamenti del display e del touch-screen, creiamo gli oggetti per gestire sempre il display, il touch-screen e il sensore CCS811 e creiamo una serie di variabili che ci serviranno per monitorare i cambi di stato dei pulsati che creeremo sul display touch-screen ed un’ulteriore serie per ospitare i valori che rileviamo dai sensori. Troviamo anche il codice per inserire due immagini.


#define TFT_DC 9
#define TFT_CS 10
#define TFT_CLK 13
#define TFT_MISO 12
#define TFT_MOSI 11
#define TFT_RST 8

#define SCK_PIN 3 
#define CS_PIN 4 
#define MOSI_PIN 5 
#define MISO_PIN 6
#define TIRQ_PIN 7

URTouch ts(SCK_PIN, CS_PIN, MOSI_PIN, MISO_PIN, TIRQ_PIN);

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);

Adafruit_CCS811 ccs;

bool state1 = 0;
bool touch1 = 0;
bool state2 = 0;
bool touch2 = 0;
bool buzzer = 0;

unsigned long tempo;
unsigned long tempoattesa = 1000;

const float S_analog = 1023.0;

int x, y, co, nh3, co2, tvoc;
float no2;

Nel VOID SETUP indichiamo la direzione dei pin a cui abbiamo collegato il transistor e il buzzer attivo, avviamo monitor seriale, sensore CCS811 e touch-screen e settiamo la precisione. Diamo segnale alto al pin 2 per accendere il display e settiamone rotazione e stampiamo la prima parte dell’interfaccia.


pinMode(2, OUTPUT);
pinMode(17, OUTPUT);

Serial.begin(9600); 
ccs.begin();
while(!ccs.available()){
Serial.print(".");
delay (1000);
}

ts.InitTouch();
ts.setPrecision(PREC_MEDIUM);
Serial.println("\nTouch inizializzato!");
delay(500);

digitalWrite(2, HIGH);

tft.begin();
tft.setRotation(3);
tft.fillScreen(ILI9341_BLACK);
homepage();



La funzione homepage() creerà una serie di zone in cui appariranno i dati, i pulsanti e le relative scritte.


tft.setCursor(85, 18);
tft.setTextSize(2);
tft.setTextColor(ILI9341_CYAN);
tft.println("AIR-QUALITY");

tft.drawBitmap(215, 6, windicon, 50, 38,ILI9341_CYAN);
tft.drawBitmap(7, 7, lighticon, 58, 34,ILI9341_LIGHTGREY);

tft.drawRoundRect(0, 0, 320, 240, 7, ILI9341_WHITE);
tft.drawCircle(294, 25, 18, ILI9341_WHITE);
tft.drawRect (6, 6, 60, 36,ILI9341_WHITE);

tft.drawRect(5, 50, 310, 10, ILI9341_WHITE);
tft.setCursor(5, 68);
tft.setTextSize(1);
tft.setTextColor(ILI9341_YELLOW);
tft.println("Anidride Carbonica:");

tft.drawRect(5, 90, 310, 10, ILI9341_WHITE);
tft.setCursor(5, 108);
tft.setTextSize(1);
tft.setTextColor(ILI9341_YELLOW);
tft.println("Monossido di Carbonio:");

tft.drawRect(5, 130, 310, 10, ILI9341_WHITE);
tft.setCursor(5, 148);
tft.setTextSize(1);
tft.setTextColor(ILI9341_YELLOW);
tft.println("Ammoniaca:");

tft.drawRect(5, 170, 310, 10, ILI9341_WHITE);
tft.setCursor(5, 188);
tft.setTextSize(1);
tft.setTextColor(ILI9341_YELLOW);
tft.println("Diossido di Azoto:");

tft.drawRect(5, 210, 310, 10, ILI9341_WHITE); 
tft.setCursor(5, 228);
tft.setTextSize(1);
tft.setTextColor(ILI9341_YELLOW);
tft.println("TVOC:");

Nel VOID LOOP verifichiamo se avviene un evento tocco sul touch-screen e in tala caso, entro determinate coordinate sul display, si verifica un evento, come ad esempio il cambio di stato di uno dei pulsanti. Il primo gestirà il transistor NPN per accendere o spegnere il display, l’altro gestisce l’attivazione o meno del buzzer per la segnalazione di allarmi.


if (ts.dataAvailable()) {
ts.read();
x=ts.getX();
y=ts.getY();
Serial.print("x = ");
Serial.print(x);
Serial.print("\ty = ");
Serial.println(y);


if (x > 12 && x < 64 && y > 10 && y < 36) {
state1 = 0;
touch1 = !touch1; 
}

if (x > 285 && x < 316 && y > 14 && y < 40) {
state2 = 0;
touch2 = !touch2; 
}
}
// NPN</pre>
if (state1 == 0 && touch1 == 1) {
/*tft.fillRect(7, 7, 58, 34,ILI9341_RED);
tft.setCursor(17, 20);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.println("Display");*/
state1 = 1;
digitalWrite(2, LOW);
delay(200);
}

if (state1 == 0 && touch1 == 0) {
/*tft.fillRect(7, 7, 58, 34,ILI9341_BLUE);
tft.setCursor(17, 20);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.println("Display");*/
state1 = 1;
digitalWrite(2, HIGH);
delay(200);
}
// Buzzer
if (state2 == 0 && touch2 == 1) {
tft.fillCircle(294, 25, 17, ILI9341_RED);
tft.setCursor(290, 17);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.println("A");
state2 = 1;
buzzer = 1;
delay(200);
}

if (state2 == 0 && touch2 == 0) {
tft.fillCircle(294, 25, 17, ILI9341_GREEN);
tft.setCursor(290, 17);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.println("A");
state2 = 1;
buzzer = 0;
delay(200);
}
<pre>

Al termine del VOID LOOP viene richiamata la funzione dati(), fondamentale non solo per ricavare i dati e stamparli al display, ma essenziale per la costruzione delle barre progresso dei relativi valori.


if (millis() &gt; tempo) {
ccs.readData();

co2 = ccs.geteCO2();
tvoc = ccs.getTVOC();

co = map (analogRead(A0), 0, S_analog, 1, 1000); // Calcolo Monossido di Carbonio
nh3 = map (analogRead(A1), 0, S_analog, 1, 500); // Calcolo Ammoniaca
no2 = (map (analogRead(A2), 0, S_analog, 5, 1000)) / 100.0 ; // Calcolo Diossido di Azoto

alarm();


Serial.print("CO2: ");
Serial.print(co2);
Serial.print(" ppm \tTVOC: ");
Serial.print(tvoc);
Serial.println(" ppb");
Serial.print("CO: ");
Serial.print(co);
Serial.print(" ppm\t");
Serial.print("NH3: ");
Serial.print(nh3);
Serial.print("ppm\t");
Serial.print("NO2: ");
Serial.print(no2);
Serial.println("ppm");

//CO2 Display
tft.setCursor(125, 68);
tft.setTextSize(1);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.print(co2);
tft.print(" ");
tft.print("ppm"); tft.println(" ");

//CO Display
tft.setCursor(145, 108);
tft.setTextSize(1);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.print(co);
tft.print(" ");
tft.print("ppm"); tft.println(" ");

//NH3 Display
tft.setCursor(75, 148);
tft.setTextSize(1);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.print(nh3);
tft.print(" ");
tft.print("ppm"); tft.println(" ");

//NO2 Display
tft.setCursor(120, 188);
tft.setTextSize(1);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.print(no2);
tft.print(" ");
tft.print("ppm"); tft.println(" ");

//TVOC Display
tft.setCursor(40, 228);
tft.setTextSize(1);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.print(tvoc);
tft.print(" ");
tft.print("ppb"); tft.println(" ");

Per disegnare le barre progresso ci interessa mappare i valori rilevabili con le dimensioni della barra progresso, poi disegnare la parte verde, gialla e rossa, che abbiamo visto rappresentano i livelli dei rilevamenti, che vanno dal sicuro al pericoloso.

</pre>
//Grafici
int g_co2 = map (co2, 400, 8192, 25, 309);
int g_co = map (co, 1, 1000, 25, 309);
int g_nh3 = map (nh3, 1, 500, 25, 309);
int g_no2 = map (no2, 0, 10, 25, 309);
int g_tvoc = map (tvoc, 0 , 1187, 25, 309);

// CO2 Grafico
if (co2 < 1400){
tft.fillRect(6, 51, g_co2, 8, ILI9341_GREEN);
tft.fillRect(g_co2, 51, 309-g_co2, 8, ILI9341_BLACK);
}
else if (co2 >= 1400 && co2 < 2000){
tft.fillRect(6, 51, g_co2, 8,ILI9341_YELLOW);
tft.fillRect(g_co2, 51, 309-g_co2, 8, ILI9341_BLACK);
}
else if (co2 >= 2000 && co2 <8192){
tft.fillRect(6, 51, g_co2, 8, ILI9341_RED);
tft.fillRect(g_co2, 51, 309-g_co2, 8, ILI9341_BLACK);
}
<pre>

Le ultime funzioni che analizziamo riguardano l’utilizzo del buzzer attivo. Quando sul display premiamo il pulsante dedicato all’attivazione o disattivazione dell’allarme, viene cambiato uno stato; è in base ad esso se il buzzer suonerà o meno al superamento delle soglie limite dei gas.


void alarm(){
if (co2 &gt;= 1400){
if (buzzer == 0){
buzzer_on();
}
}

if (co &gt;= 9){
if (buzzer == 0){
buzzer_on();
}
}

if (no2 &gt;= 4){
if (buzzer == 0){
buzzer_on();
}
}

if (nh3 &gt;= 25){
if (buzzer == 0){
buzzer_on();
}
}

if (tvoc &gt;= 80){
if (buzzer == 0){
buzzer_on();
}
}
}

void buzzer_on(){

digitalWrite(17, HIGH);
delay(100);
digitalWrite(17, LOW);
delay(100);
}

All’interno del codice abbiamo lasciato alcune linee sotto forma di commento, perché potrebbero essere utili qualora, invece dell’immagine adoperata per attivare il display si preferisse creare un pulsante più semplice dotato di scritta.