Rilevazione umidita´ e temperatura con Arduino con i sensori DS18S20 e HIH-40307

Vediamo in questo articolo come effettuare la lettura dei valori ambientali di temperatura e di umidità relativa con Arduino. Questa può essere una buona base di partenza per la realizzazione di una semplice stazione meteo.

Attenzione: se sei arrivato a questo articolo perchè cercavi il progetto della stazione meteo Wi-Fi con il NodeMCU / ESP8266, si trova in quest’altra pagina.

La sonda di temperatura utilizzata è una Dallas DS18S20, il sensore di umidità è un Honeywell HIH-4030 che abbiamo avuto modo di conoscere nel precedente articolo.

Funzionamento protocollo 1-Wire

La sonda di temperatura DS1820 è digitale ed utilizza il protocollo di comunicazione 1-Wire: su un unico filo viaggiano tutti i segnali (comunicazione half-duplex, asincrona). Tale sonda ha  una modalità di funzionamento parassita che le permette di essere alimentata dallo stesso filo su cui viaggiano i segnali, riducendo così solo a 2 (segnale+massa) i fili necessari per il collegamento.

Una trattazione più approfondita del bus 1wire si trova in questo nostro articolo.

La modalità di funzionamento parassita è ottenuta tramite un condensatore, interno al dispositivo, che si carica durante la transizione a livello logico alto della linea dati e quindi fornisce energia alla circuiteria interna per tutte le fasi successive. Questa funzione è molto comoda e in alcuni casi permette addirittura di eludere ancor più i disturbi dato che questi non possono essere captati dalla linea di alimentazione (la linea di alimentazione non c’è!).

Il funzionamento del protocollo 1-wire concettualmente è abbastanza semplice: la linea dati è collegata ad I/O open-drain (ottenuti con mosfet), per cui c’è una resistenza di pull-up, comune a tutti i dispositivi, che tiene la linea dati normalmente a livello logico alto. In una linea di comunicazione 1-wire viene identificato un dispositivo Master (che gestisce le comunicazioni) e uno o più dispositivi Slave (che rispondono solo se interrogati). Non essendoci un clock i dispositivi dovranno comunicare in tempi prestabiliti. La restrizione su tali tempi è più forte nel caso in cui si utilizzi la modalità di alimentazione parassita in quanto non possiamo correre il rischio che la sonda si trovi col condensatore scarico e quindi non alimentata.

Prima di cominciare una trasmissione, il dispositivo master emette un segnale di reset: la linea dati viene tenuta a livello logico basso per almeno 480μS.

Quando dovrà essere trasmesso uno “0” la linea dati verrà mantenuta a livello basso per 60μS, quando dovrà essere trasmesso un “1” la linea dati verrà mantenuta sempre a livello basso ma per un tempo più breve (massimo 15μS).

Ricordo che, essendo gli I/O open drain, i dispositivi possono solo “tirare a massa” la linea, la quale è normalmente tenuta a livello alto dalla resistenza di pull-up.

Questo sistema viene definito dal protocollo 1-wire come scrittura per Time Slots. Tutti i time slots devono avere una durata minima di 60μS (per cui nel caso dell’invio di un 1 logico, la linea si troverà a livello basso per massimo 15μS e a livello alto per 45μS, nel caso dell’invio di uno zero la linea si troverà a livello basso per tutta la durata del time slot). Tra un time slot e il successivo c’è un “tempo di recupero” minimo di 1μS.

I dispositivi presenti sul bus, quando devono effettuare la lettura, campionano la linea dati ogni 30μS a partire dal fronte di discesa del segnale. I dati sono inviati in gruppi di 8 bit. I dispositivi Slave sono identificati da un indirizzo a 64bit (8 bytes), per cui il Master, quando vorrà comunicare, dovrà specificare con chi. A tal proposito il Master dovrà conoscere a priori gli indirizzi di ogni dispositivo sulla linea. Nel protocollo 1-Wire sono definiti alcuni comandi chiamati  ROM commands che servono appunto al riconoscimento/rilevazione degli indirizzi dei dispositivi presenti sul bus.

La sonda, così come tutti gli altri dispositivi 1-wire, ha uno scratchpad (in italiano: blocchetto degli appunti), che è in pratica una memoria RAM con funzione di  cache: i dati vengono trasferiti attraverso questa memoria.  Lo scratchpad è formato da più registri ad 8 bit dedicati a funzioni particolari. Sulla DS18S20 lo scratchpad è costituito da 9 registri come vedremo in seguito.

Non mi dilungherò ulteriormente sulla spiegazione del protocollo 1-Wire dal momento che per Arduino è già presente una libreria completa che fa tutto questo lavoro.

D’altronde in molti preferiscono Arduino proprio per questo motivo: essendoci una comunità molto aperta è possibile trovare librerie per qualsiasi cosa passi per la testa (o quasi) e questo lo rende un sistema adatto anche a chi l’elettronica digitale non l’ha mai masticata in vita sua… ma nemmeno vista di sfuggita in un fumetto! E’ richiesta comunque una certa elasticità mentale in ambito di programmazione e una conoscenza minima del linguaggio C, per tale motivo Arduino è molto apprezzato soprattutto dai programmatori.

Ulteriori informazioni possono essere trovate nella pagina di Arduino che spiega il funzionamento di tale protocollo. Le specifiche complete del protocollo 1-Wire possono essere trovate ai seguenti links:

http://www.maxim-ic.com/appnotes10.cfm/ac_pk/1

http://www.maxim-ic.com/products/ibutton/ibuttons/standard.pdf

Una fonte più semplice di informazioni è costituita dallo stesso datasheet della sonda DS18S20, scaricabile in fondo all’articolo.

Trovare l’indirizzo della sonda DS18S20

Abbiamo detto che i dispositivi 1-wire hanno un indirizzo che deve essere noto al microcontrollore per fare in modo che questi possa comunicarvi. In questa prima fase ci appresteremo quindi a trovare l’indirizzo della nostra sonda DS18S20.

Per prima cosa scarichiamo la libreria OneWire per Arduino a questo indirizzo:

http://www.pjrc.com/teensy/td_libs_OneWire.html

Una volta estratto il file zip, troveremo una cartella nominata “OneWire” all’interno della quale ci sono i files OneWire.cpp, OneWire.h e un’ulteriore cartella “Examples”.

Trasportiamo la cartella OneWire, con tutto il suo contenuto, nella cartella di Arduino destinata alle librerie (libraries), in maniera da averla disponibile nell’IDE di Arduino.

Colleghiamo quindi il pin dati (pin 1 avendo la sonda di fronte e cioè con la faccia piatta con su stampato il nome) della  DS18S20 alla porta n°10 di Arduino mettendovi una resistenza di pull-up da 4,7KΩ verso la 5Volt fornita dallo stesso Arduino. Possiamo tranquillamente lasciare scollegato il pin di alimentazione (il terzo) della sonda in quanto andremo a sfruttare appunto la modalità di alimentazione parassita, Arduino sarà alimentato dalla porta USB (faccio riferimento ad Arduino Duemilanove):

Collegamento sonda DS18S20 ad Arduino con modalità di alimentazione parassita

Il vostro Arduino Duemilanove potrebbe differire leggermente da quello rappresentato in figura, il mio, ad esempio, non ha il jumper di selezione alimentazione ma per questo non vado certo nel panico.

Apriamo quindi l’IDE di Arduino e incolliamo brutalmente il seguente codice (è comunque possibile scaricare lo sketch già pronto in fondo all’articolo):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*
Trova indirizzo delle sonde DS18x20
Originale by TutorialPedia : http://tutorialpedia.org/tutorials/Working+with+Dallas+DS18S20+and+DS18B20+temperature+sensors+from+Arduino.html
Modifiche minori e traduzione in italiano
a cura di Bernardo Giovanni (https://www.settorezero.com)
*/
 
#include 
 
// inizializza il bus onewire sulla porta n°10
OneWire ow(10);
 
void setup(void)
  {
  Serial.begin(9600); // inizializza la porta seriale a 9600
  lookUpSensors(); // avvia la ricerca delle sonde di temperatura
  }
 
void lookUpSensors()
  {
  byte address[8]; // questo array conterrà l'indirizzo delle sonde
  int i=0;
  byte ok = 0, tmp = 0;
 
  // avvia la ricerca
  Serial.println("--Ricerca avviata--");
 
  while (ow.search(address))
    {
    tmp = 0;
 
    // Se il primo byte dell'indirizzo è 0x10, si tratta di una sonda DS18S20
    if (address[0] == 0x10)
      {
      Serial.println("A questo indirizzo si trova una DS18S20 : ");
      tmp = 1; // ricerca andata a buon fine
      }
    else
      {
      // Se il primo byte dell'indirizzo è 0x28, si tratta di una sonda DS18B20
      if (address[0] == 0x28)
        {
        Serial.println("A questo indirizzo si trova una DS18B20 : ");
        tmp = 1; // ricerca andata a buon fine
        }
      }
 
    // Se il flag tmp si trova a 1, mostro l'indirizzo
    if (tmp == 1)
      {
      if (OneWire::crc8(address, 7) != address[7]) // faccio il controllo del CRC8
        {
        Serial.println(" (ma CRC8 non valido)");
        }
      else
        {
        // tutto ok, mostro l'indirizzo in formato esadecimale
        for (i=0;i<8;i++)
          {
          Serial.print("0x");
            if (address[i] < 9)
            {
            Serial.print("0");
            }
          Serial.print(address[i],HEX);
          if (i<7)
            {
            Serial.print(",");
            }
          }
          Serial.println("");
          ok = 1;
        }
      } //end if tmp
  }//end while
 
    if (ok == 0)
      {
      Serial.println("Non ho trovato sonde di temperatura");
      }
  Serial.println("--Ricerca terminata--");
  }
 
void loop(void)
  {
  // non faccio niente... ho fatto tutto nel setup!
  }

Il codice ci tengo a precisare non è opera mia ma l’ho trovato su TutorialPedia a questo indirizzo, l’ho solo tradotto in italiano e modificato leggermente per avere l’indirizzo della sonda già pronto da copiare e incollare. Caricato questo programma su Arduino, avviamo il monitor seriale (Tools->Serial Monitor) e attendiamo un paio di secondi. Se abbiamo fatto i collegamenti in maniera corretta, dovremo avere una schermata del genere:

Se non avete questa schermata avete commesso qualche errore, provate pure a premere il pulsante di reset su Arduino. Anche se non ci sarebbe bisogno di dirlo: a voi ovviamente l’indirizzo mostrato sarà diverso, inizierà comunque con 0x10 per le sonde DS18S20 e con 0x28 per le sonde DS18B20.

La DS18B20, rispetto alla DS18S20, ha la risoluzione programmabile da 9 a 12bit. Io comunque ho utilizzato la DS18S20 che pare sia più diffusa per cui non vi so dire se tali esempi possono funzionare correttamente anche con la DS18B20.

Dal terminale seriale, col mouse, selezioniamo quindi l’indirizzo e copiamolo (tasto destro -> copia).

Spunti di partenza per una semplice stazione meteo

Al circuito precedente aggiungiamo anche la sonda di umidità HIH-4030: dovremo alimentarla a 5Volt e l’uscita del sensore di umidità la andremo a collegare alla porta analogica n°0:

Come vedete il collegamento è semplicissimo, non mi dite che avete difficoltà a realizzarlo! Andiamo quindi a vedere come è strutturato il codice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
Temperatura e Umidità con Arduino, DS18S20 e HIH4030
spunti per stazione meteo
by Bernardo Giovanni (https://www.settorezero.com)
 
Funzioni per lettura DS18S20 prese da tutorialpedia:
http://tutorialpedia.org/tutorials/Working+with+Dallas+DS18S20+and+DS18B20+temperature+sensors+from+Arduino.html
*/
 
#include <OneWire.h>
 
int RHAnalogValue;  // Lettura in uscita dal sensore HIH-4030
float RH;  // Valore di Umidità Relativa espresso in percentuale
float T; // Valore di temperatura in °C dalla sonda DS18S20
 
#define RH_SENSOR 0 // Sensore umidità collegato su porta analogica n°0
OneWire  ONE_WIRE_BUS(10);  // Bus One-Wire (sonda DS18S20) sul pin 10
 
// Indirizzo della sonda DS18S20
// Nota: trova l'indirizzo della tua sonda col programma precedente e incollalo al posto di questo
byte T_SENSOR[8] = {0x10, 0xB5, 0x68, 0x01, 0x02, 0x08, 0x00, 0x8E};
 
void setup()
  {
  Serial.begin(9600); // Inizializzo la porta seriale
  }
 
void writeTimeToScratchpad(byte* address)
  {
  ONE_WIRE_BUS.reset(); // Resetto il bus
  ONE_WIRE_BUS.select(address);   // Seleziono la mia sonda DS18S20
  ONE_WIRE_BUS.write(0x44,1);   // Richiamo la funzione di conversione temperatura (44h)
  // che posiziona il valore di temperatura nello scratchpad (comando Convert T)
  delay(1000);   // Attendo un secondo che la scrittura sia completa
  }
 
void readTimeFromScratchpad(byte* address, byte* data)
  {
  ONE_WIRE_BUS.reset();   // Resetto il bus
  ONE_WIRE_BUS.select(address);   // Seleziono la mia sonda DS18S20
  ONE_WIRE_BUS.write(0xBE); // Comando di lettura dello scratchpad (comando Read Scratchpad)
  for (byte i=0;i<9;i++)
    {
    data[i] = ONE_WIRE_BUS.read(); // Leggo i 9 bytes che compongono lo scratchpad
    }
  }
 
float getTemperature(byte* address)
  {
  int tr;
  byte data[12];
  writeTimeToScratchpad(address); // Richiamo conversione temperatura
  readTimeFromScratchpad(address,data); // effettuo la lettura dello Scratchpad
  tr = data[0];   // Posiziono in TR il byte meno significativo
  // il valore di temperatura è contenuto nel byte 0 e nel byte 1
 
  // Il byte 1 contiene il segno
  if (data[1] > 0x80) // Se il valore è >128 allora la temperatura è negativa
    {
    tr = !tr + 1; // Correzione per complemento a due
    tr = tr * -1; // Ottengo il valore negativo
    }
 
  int cpc = data[7];   // Byte 7 dello scratchpad: COUNT PER °C (10h)
  int cr = data[6];   // Byte 6 dello scratchpad: COUNT REMAIN (0Ch)
  tr = tr >> 1;   // Rilascio il bit 0 come specificato dal datasheet per avere una risoluzione > di 9bit
 
  // Calcolo la temperatura secondo la formula fornita sul datasheet:
  // TEMPERATURA = LETTURA - 0.25 + (COUNT PER °C - COUNT REMAIN)/(COUNT PER °C)
 
  return tr - (float)0.25 + (cpc - cr)/(float)cpc;
  }
 
// Conversione da Fahrenheit a Celsius
float f2c(float val)
  {
  float aux = val - 32;
  return (aux * 5 / 9);
  }
 
// COnversione da Celsius a Fahrenheit
float c2f(float val)
  {
  float aux = (val * 9 / 5);
  return (aux + 32);
  }
 
void loop()
  {
   // Lettura temperatura (la DS18S20 fornisce il valore in °C)
  T = getTemperature(T_SENSOR);
 
  // Lettura del valore analogico dalla sonda di umidità
  RHAnalogValue = analogRead(RH_SENSOR);
 
  // La funzione analogRead restituisce un valore tra 0 e 1023
  // proporzionale alla tensione applicata sul pin
  // per cui il valore di tensione è dato da:
  // Vout=(analogRead/1023)*5
  // Il valore di umidità relativa è invece dato da:
  // RH = (Vout-0.8)/0.031
  // come già spiegato nell'articolo: https://www.settorezero.com/wordpress/sensore-di-umidita-honeywell-hih-4030/
  RH = ((((float)RHAnalogValue/1023)*5)-0.8)/0.031;
 
  // Dato che abbiamo anche il valore di temperatura, effettuiamo anche
  // la compensazione per avere un valore di umidità relativo preciso
  RH = RH/(1.0546-(0.00216*T));
 
  // Stampo i risultati su terminale seriale
  Serial.print("Umidita relativa: ");
  Serial.print(RH,1); // Scrivo il valore di umidità con un decimale
  Serial.println("%");
 
  Serial.print("Temperatura: ");
  Serial.print(T,1); // Scrivo il valore di temperatura con un decimale
  Serial.println(" C");
 
  // Attendo 10 secondi per ricominciare
  delay(10000);
  }

In questo codice faccio uso di funzioni prese sempre dal link precedente di TutorialPedia. Vengono in pratica sfruttate due funzioni previste dal datasheet della sonda DS18S20 e che sono in pratica la Convert T (comando 0x44) che serve per avviare sulla sonda il processo di  conversione; alla fine di tale processo il valore di temperatura verrà memorizzato nei primi due bytes dello scratchpad.

L’altra funzione utilizzata è Read Scratchpad (comando 0xBE) che serve appunto per effettuare la lettura dei 9 bytes che compongono lo Scratchpad. Come detto in precedenza lo Scratchpad è costituito da9 registri da un byte ciascuno, ognuno dei quali ha una funzione specifica (questa immagine è presa dal datasheet e mostra lo stato dei registri all’accensione):

Il valore di temperatura è espresso in °C ed è memorizzato nei bytes 0 e 1 in come numero in complemento a due a 16 bit: il byte più significativo contiene il segno. Ulteriori informazioni possono essere trovate in questa pagina di Wikipedia. La temperatura verrà quindi normalmente letta con una risoluzione di 9 bits ovvero mezzo grado. I bytes 2 e 3 servono per memorizzare un eventuale valore di temperatura di allarme, che non andremo a sfruttare. I bytes 6 e 7 servono invece per avere una risoluzione maggiore di 9 bit e vengono sfruttati tramite una formula fornita nel datasheet che difatti andremo ad applicare.

Se vi interessa, il codice fornito presenta anche delle funzioni per la conversione di temperatura da °Celsius a °Fahrenheit e viceversa.

Il funzionamento della sonda HIH-4930 l’abbiamo visto nell’ articolo precedente.  Avendo a diposizione il valore di temperatura, andremo ad effettuare anche la compensazione del valore di umidità relativa. I valori letti vengono inviati su seriale. Una volta caricato il programma su Arduino e lanciato il terminale seriale possiamo leggere nella nostra stanza quanta umidità è presente e a che temperatura ci troviamo:

Anche se non me lo diceva Arduino, lo sentivo già da me che oggi, 3 Agosto, l’umidità si sta sbizzarrendo!

Downloads

Sketch Arduino per spunti su stazione meteo (578 download) Datasheet DS18S20 (2017 download) Datasheet DS18B20 (1829 download)

Links

Pagina di Arduino sul protocollo 1-Wire

Download libreria OneWire per Arduino

Esempi di utilizzo libreria OneWire con sonda DS18S20

Specifiche del protocollo 1-Wire

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)