Temperature & pressure monitoring con Arduino

Efficienza energetica

In questo esaustivo articolo ci occuperemo di configurare un sistema di misurazione della temperatura e della pressione ambientali e per farlo utilizzeremo un opportuno sensore ed Arduino. Ne vedremo specifiche, schematici, dimensionamenti, collegamenti, codice e risultati finali. E poi faremo il punto sul suo futuro. Siete pronti?

Per me è sempre stata una sfida interessante: misurare la temperatura all'interno di una stanza, vedere come vari nel tempo e cercare di caratterizzare tutte le stanze di un'abitazione in funzione del fatto che sia effettivamente comodo e possibile abitarle durante tutte le stagioni.
Credo sarebbe molto utile per tutti coloro che vogliono capire, per esempio, come migliorare l'efficienza energetica della propria casa, adibendo ciascuna stanza all'uso più opportuno in funzione proprio di queste caratteristiche, magari scoprendo che non è nemmeno necessario modificare le proprie abitudini. La distribuzione degli spazi, dei mobili, la vivibilità, più in generale, potrebbero trasformare la "casa di sempre" in una esperienza abitativa diversa, magari migliore.
Questa sfida, in realtà, si può affrontare con un sistema abbastanza più complesso di quello che vedremo oggi ma che, certamente, si può realizzare anche grazie a quello che vi faremo vedere, utilizzandolo come base.

Il sensore

Stiamo parlando di un sensore barometrico, il BMP085 della Bosch, che è un dispositivo di grande precisione ed a basso consumo di potenza. Il range di misura va da 300 fino a 1100 hPa con una precisione assoluta pari a 0.03 hPa. Per la massima chiarezza, ricordiamo che

1 hPa = 1 mbar

1 Pa = 1 N m^-2 = 1 kg m^-1 s^-2

La tecnologia sulla quale è basato permette una ottima linearità, elevata precisione ma, soprattutto resistenza alle interferenze elettromagnetiche. Altra caratteristica di grande interesse è la stabilità sul lungo periodo. Non viene, infatti, evidenziata alcuna sostanziale variazione dei parametri funzionali del dispositivo in funzione del tempo di utilizzo.
Il sensore può lavorare con tensioni di polarizzazione comprese tra 1.8 V e 3.6 V.
È stato progettato per essere connesso direttamente ad un microcontrollore che disponga di un ingresso I2C ed è, infatti, grazie a questo tipo di connessione che lo utilizzeremo per questo esperimento.

La scheda che viene impiegata permette di utilizzare un pin header da 6. VDDD e VDDA del sensore sono connesse insieme e portate ad un singolo pin; questa è una scelta che, in linea generale, potrebbe non essere il caso di fare ma che, per la nostra applicazione specifica, non dovrebbe creare particolari problematiche.
Se il sensore, invece, dovesse essere inserito all'interno di un sistema più complesso e che lavori con segnali misti, sarà certamente necessario distinguere l'alimentazione digitale da quella analogica.
Concordemente a quanto vedremo, viene utilizzato un resistore da 4.7 kΩ come semplice rete di pull-up.

Tornando, brevemente, alle caratteristiche, tra le più interessanti ci sono:

  •     interfaccia I2C;
  •     ampio range di pressione;
  •     diverse tensioni di alimentazione;
  •     basso consumo di potenza (5 uA @ 1 sample/s);
  •     basso rumore di misura;
  •     dimensioni estremamente contenute (16.5 x 16.5 mm).

Veniamo adesso alla configurazione; il datasheet dimostra subito il metodo di configurazione del sensore ed esso prevede l'utilizzo di semplice resistori e condensatori per poterlo rendere operativo. Ecco una figura esplicativa:

Rp, i resistori di pull-up, possono essere di vario valore, da 2.2 kΩ fino a 10 kΩ; valore tipico è 4.7 kΩ. Ecco, quindi, che, esattamente come riportato nel datasheet, sulla breakout board in uso, il valore della resistenza è lo stesso che vediamo nello schematico.

Configuriamolo

Abbiamo deciso di lavorare con Arduino, versione UNO rev.3. Questa scheda la conoscete molto bene (ok, alzi la mano chi non l'ha mai vista...) per cui certamente non vi servono presentazioni (qualcuno non ha alzato la mano?). Tuttavia vale la pena di specificare che l'interfaccia di comunicazione I2C è possibile che venga utilizzata solo grazie ai pin A4 e A5.
Per farlo, i collegamenti dovranno essere quelli che vedete in figura (non fatevi impressionare, lo schield che ho usato NON era necessario ma lo trovo molto comodo per sperimentare al volo i prototipi!):

Possiamo utilizzare, per questo esperimento, anche un monitor LCD 20x4. Vedremo che sarà molto utile impiegare il Monitor Seriale con funzione di debug per verificare il corretto invio dei dati ma, naturalmente, scopo del progetto è quello di proporre i dati all'utente ed il monitor a cristalli liquidi rappresenta certamente il metodo più semplice per farlo.

Dal momento che disponiamo di un LCD backpack (che vi consiglio caldamente!) che consente il collegamento del monitor stesso tramite interfaccia I2C, la versione della libreria in uso sarà modificata e permetterà all'utilizzo delle classiche funzioni anche con questo metodo di collegamento.

Vi rimandiamo, per completezza, all'indirizzo Internet presso il quale potrete reperire il datasheet del componente e la libreria modificata per le comunicazioni su I2C del display. Qui di seguito, invece, un'immagine riassuntiva del protocollo di comunicazione utilizzato.

Il codice

Dal momento che si tratta soltanto di una piccola dimostrazione e non già di un progetto completo, il codice che vi riportiamo qui di seguito non contiene una vera e propria libreria del componente ma soltanto le funzioni fondamentali che ci servono in questo caso.

Ecco come fare a farlo funzionare:

#include 
#include 
#define BMP085_ADDRESS 0x77  // Indirizzo I2C del sensore
#define LCD_ADDRESS 0x20  // Indirizzo I2C del monitor

// Variabili blobali
const unsigned char OSS = 0;  // Oversampling

// Valori di calibrazione
int ac1;
int ac2;
int ac3;
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1;
int b2;
int mb;
int mc;
int md;

long b5;

// variabili da misurare
short temperature;
long pressure;

// Dichiaro lo schermo LCD
LiquidCrystal_I2C lcd(LCD_ADDRESS, 20,4);

// Con questa funzione memorizzo tutti i valori nelle variabili globali
// I valori di calibrazione servono per calcolare temperature e pressioni
// La funzione va richiamata all'inizio del programma
void bmp085Calibration()
{
  ac1 = bmp085ReadInt(0xAA);
  ac2 = bmp085ReadInt(0xAC);
  ac3 = bmp085ReadInt(0xAE);
  ac4 = bmp085ReadInt(0xB0);
  ac5 = bmp085ReadInt(0xB2);
  ac6 = bmp085ReadInt(0xB4);
  b1 = bmp085ReadInt(0xB6);
  b2 = bmp085ReadInt(0xB8);
  mb = bmp085ReadInt(0xBA);
  mc = bmp085ReadInt(0xBC);
  md = bmp085ReadInt(0xBE);
}

// Calcolo della temperatura
// Valori misurati in decimi di °C (vedi datasheet)
short bmp085GetTemperature(unsigned int ut)
{
  long x1, x2;
 
  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);
  b5 = x1 + x2;

  return ((b5 + 8)>>4);  
}

// Calcolo della pressione
// Qui vengono utilizzati i valori di calibrazione
// I valori sono misurati in Pa
long bmp085GetPressure(unsigned long up)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
 
  b6 = b5 - 4000;
  // Calcolo di B3
  x1 = (b2 * (b6 * b6)>>12)>>11;
  x2 = (ac2 * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)ac1)*4 + x3)<<oss)>>2;
 
  // Calcolo di B4
  x1 = (ac3 * b6)>>13;
  x2 = (b1 * ((b6 * b6)>>12))>>16;
  x3 = ((x1 + x2) + 2)>>2;
  b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;
 
  b7 = ((unsigned long)(up - b3) * (50000>>OSS));
  if (b7 < 0x80000000)
    p = (b7<<1)/b4;
  else
    p = (b7/b4)<<1;
    
  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;
 
  return p;
}

// Lettura di 1 byte del sensore (indirizzo)
char bmp085Read(unsigned char address)
{
  unsigned char data;
 
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
 
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available());
    
  return Wire.read();
}

// Lettura di 2 bytes del sensore, il primo è l'inidirizzo il secondo è indirizzo+1
int bmp085ReadInt(unsigned char address)
{
  unsigned char msb, lsb;
 
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
 
  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2);
  msb = Wire.read();
  lsb = Wire.read();
 
  return (int) msb<<8 | lsb;
}

// Lettura dei valori di temperatura (non compensati)
unsigned int bmp085ReadUT()
{
  unsigned int ut;
 
  // La scrittura di 0x2E nel registro 0xF4 è una lettura del dato
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();
 
  delay(5); // pausa di 5 ms a fronte dei 4.5 richiesti
 
  // Lettura da 0xF6 e 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}

// Lettura dei valori di pressione
unsigned long bmp085ReadUP()
{
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;
 
  // Scrivere 0x34+(OSS<<6) nel registro 0xF4 vuol dire leggere la pressione
  // ATTENZIONE al sovracampionamento!
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS<<6));
  Wire.endTransmission();
 
  delay(2 + (3<<oss)); ...="" 0xf6="" 0xf7="" 0xf8="" attesa="" conversione="" dati="" dei="" devo="" di="" e="" essere="" i="" leggiamo="" lsb="Wire.read();" msb="Wire.read();" registri="" ritardo="" unsigned="" up="(((unsigned" xlsb="Wire.read();">> (8-OSS);
 
  return up;
}

// Inizia il programma
void setup()
{
  Serial.begin(9600);
  Wire.begin();
  lcd.begin();
  bmp085Calibration();
}

void loop()
{
  temperature = bmp085GetTemperature(bmp085ReadUT())/10;
  pressure = bmp085GetPressure(bmp085ReadUP());

  //Iniziamo a visualizzare i dati
  Serial.print("Temperature: ");
  Serial.print(temperature, DEC);
  Serial.println(" °C");
  Serial.print("Pressure: ");
  Serial.print(pressure, DEC);
  Serial.println(" Pa");
  Serial.println("\r");
  lcd.setBacklight(HIGH);
  lcd.print("Temperature: ");
  lcd.print(temperature, DEC);
  lcd.println(" °C");
  lcd.print("Pressure: ");
  lcd.print(pressure, DEC);
  lcd.println(" Pa");
  cd.println(" m");
  delay(1000);
  lcd.clear();
}

Il codice è abbondantemente commentato (come avrete sicuramente notato) ma è importante farvi comprendere come queste funzioni siano, almeno in questo caso, figlie di una documentazione che dire completa è dir poco.
Volendo, inoltre, espandere le possibilità di questo codice una delle prime cose che si può certamente fare è quella di implementare il calcolo dell'altitudine, che può risultare dalla seguente formula:

Viene qui, anche, tiportato che una variazione di 1 hPa corrisponde ad una variazione di altitudine di 8.43 m.
Come potete vedere dai collegamenti, in pratica monitor e sensore sono in parallelo, distinti solamente dal loro indirizzo così come si è visto nel codice.

Conclusioni

L'esperimento che vi abbiamo fatto vedere quest'oggi è molto semplice. Si tratta di configurare il sensore ed effettuare una lettura periodica.

A tal proposito, trattandosi di temperatura, il periodo è da impostare ma naturalmente fare la lettura ogni secondo è altamente inutile: la temperatura è una variabile che varia molto lentamente nel tempo e che ha escursioni abbastanza contenute quindi anche una lettura ogni 6 ore è più che sufficiente!

La parte più complicata, evidentemente, è la realizzazione e l'implementazione delle funzioni utili per effettuare l'interfacciamento.

Per realizzare quel sistema più complesso del quale avevamo accennato all'inizio saranno necessari almeno ancora un RTC, in modo tale da collegare le letture all'orario e quindi al periodo della giornata così come alla data, e quindi al periodo dell'anno.

Inoltre il sensore potrebbe non essere unico ma potremmo prelevare dati dall'interno e dall'esterno dell'appartamento, in maniera tale da correlare anche la "reazione" dell'ambiente all'esterno. Ovviamente per le misure esterne avrebbero bisogno di sensori più complessi perché dovrebbero essere, magari, wireless e quindi serve anche che siano alimentati e c'è da risolvere la questione del "come?". Evidentemente i sensori potrebbero diventare anche di più di due e quindi si potremmo dover gestire contemporaneamente dati provenienti da n elementi sensibili distinti, identificati tutti univocamente dal loro indirizzo.

Tutto questo non può essere completo se non si analizzano anche i venti cui l'edificio è esposto, che dipendono soprattutto dall'altezza dell'abitato.
Tutti questi dati, una volta raccolti, potrebbero essere memorizzati su una memoria locale ed ancora inviati ad un webserver.

Insomma, se si comincia davvero a pensare a quali possono essere gli sviluppi futuri per questo genere di sistema, arriviamo ad una stazione metereologica non più tanto in miniatura.
Un gran bel progetto partendo da così poco, vero?

 

Quello che hai appena letto è un Articolo Premium reso disponibile affinché potessi valutare la qualità dei nostri contenuti!

 

Gli Articoli Tecnici Premium sono infatti riservati agli abbonati e vengono raccolti mensilmente nella nostra rivista digitale EOS-Book in PDF, ePub e mobi.
volantino eos-book1
Vorresti accedere a tutti gli altri Articoli Premium e fare il download degli EOS-Book? Allora valuta la possibilità di sottoscrivere un abbonamento a partire da € 2,95!
Scopri di più

5 Comments

  1. Emanuele Emanuele 10 luglio 2013
  2. Giorgio B. Giorgio B. 11 luglio 2013
  3. Piero Boccadoro Piero Boccadoro 14 luglio 2013
  4. Lorenzo Ferrucci 28 dicembre 2014
    • Emanuele Emanuele 28 dicembre 2014

Leave a Reply

Flip&Click Arduino e Python compatibile

Fai un abbonamento Platinum (EOS-Book + Firmware), ricevi in OMAGGIO la nuova Flip&Click, inviaci il tuo progetto e OTTIENI IL RIMBORSO