Misurare Temperatura e Umidità relativa con il sensore DHT22 e un microcontrollore PIC

Il Sensore DHT-22

Si tratta di un sensore molto comune ed economico che restituisce i valori di Temperatura ed Umidità relativa con una discreta precisione nonostante il prezzo contenuto (si può trovare su Ebay a pochi spiccioli). E’ anche venduto con il nome di AM2302.

Il sensore DHT22 / AM2302
Il sensore DHT22 / AM2302

Viene venduto anche in una versione più grande dalla quale escono 3 fili, ideale da usare come sensore esterno. La versione da PCB ha 4 pin ma uno è inutilizzato (ho addirittura visto una breakout board assolutamente inutile che serviva soltanto per riportare i pin da 4 a 3… sono senza parole). I pin hanno la spaziatura standard da 2.54mm per cui lo potete usare cosi com’è anche sulla breadboard senza spendere denaro in breakout board assurde. Il sensore, sia in versione da pcb che da esterno, è bianco con un foro in cima.

Legge valori di umidità relativa compresi tra 0 e 99.9% e temperature da -40 a 80 °C. Nonostante fosse già economico, esiste una versione ancora più economica, il DHT-11, che però ha un’accuratezza minore, legge valori di umidità relativa a partire dal 20% e il range di temperatura è più ristretto: 0-50°C, quindi, dato che l’idea è farsi una piccola ed economica stazione meteo, non l’ho proprio preso in considerazione.

Il sensore DHT-11 è di colore blu e non ha il foro in cima, quindi non fatevi infinocchiare comprando un DHT11 per un DHT22. Il sistema di trasmissione dati dei due sensori è identico, così come la loro formattazione a meno del bit del segno della temperatura, per cui potete tranquillamente provare il codice anche con un DHT-11 se già lo avete.

Il sensore in questione è digitale e comunica con una sorta di bus 1-wire proprietario (non è compatibile con il bus 1-wire delle sonde Dallas tanto per intenderci): su un’ unica linea riceve e trasmette. Il microcontrollore quindi ha il compito di inviare un segnale di start e quindi attendere la risposta. E’ necessaria una resistenza di pull-up sulla linea.

I datasheet che si trovano in giro non sono tanto chiari e sono anche un po’ pasticciati per cui all’inizio ho trovato un po’ di difficoltà e ho messo mano all’analizzatore logico e ad un codice per Arduino sicuramente funzionante che mi ha permesso di controllare i tempi di comunicazione con precisione (difatti in fondo all’articolo è anche possibile scaricare il file in formato ALC per il software degli analizzatori logici Zeroplus Logic-Cube modello LAP-C).

Il sensore ha un buon range di tensioni di alimentazione (3.3 – 5.5V), per cui è possibile utilizzarlo senza problemi e senza adattamenti sia alle logiche TTL a 3.3V che quelle a 5V.

Come trasmette i dati il sensore DHT-22

Dicevo prima: il sensore comunica su una sola linea, bidirezionale, tenuta a livello logico alto (stato idle) da una resistenza di pull-up (può essere una resistenza esterna o una resistenza di pull-up integrata sul pin del microcontrollore qualora ne fosse dotato).

Il microcontrollore deve inviare una sequenza di start, attendere una risposta e quindi attendere il dato. Analizziamo la sequenza di start:

DHT22 - sequenza di start
DHT22 – sequenza di start. I livelli in rosso sono determinati dalla resistenza di pull-up, in blu dal microcontrollore e in verde dal sensore

Il microcontrollore deve impostare il pin di comunicazione come uscita e quindi portare la linea a livello logico basso per almeno 18mS, nel codice la porto a livello basso per 20mS (millisecondi, non micro, questo è l’unico tempo espresso come millisecondi). A questo punto sul datasheet c’è scritto che il micro deve portare la linea a livello alto: in realtà non è corretto, è la resistenza di pull-up che porta la linea a livello alto, per cui, passati i 20mS, il pin del micro può (DEVE!) benissimo essere configurato come ingresso e lasciare che la linea vada a livello alto da sè.

A questo punto passa un tempo variabile dai 20 ai 40µS dopo i quali il sensore risponde. La risposta (acknowledge) del sensore consiste nel portare la linea a livello logico basso per un tempo di circa 80µS e quindi rilasciarla per altri 80µS. Dopo di ciò vengono inviati i dati con un meccanismo tutto particolare.

Guardiamo prima come sono strutturati i dati:

DHT22 - struttura dati
DHT22 – struttura dati

Il sensore invia sulla linea 5 bytes (40 bit) a partire dal più significativo verso il meno significativo in questo ordine:

  • Byte alto umidità relativa
  • Byte basso umidità  relativa
  • Byte alto temperatura
  • Byte basso temperatura
  • Checksum

I dati di umidità e temperatura vengono inviati come dato a 16 bit (2 bytes) moltiplicato 10.

Per cui se i primi 2 bytes (valore di umidità) valgono insieme, ad esempio, 594, (ovvero il primo byte è 0000 0010 e il secondo è 01010010), il valore di umidità relativa è 59.4 %.

Lo stesso meccanismo vale per la temperatura, il cui valore è contenuto nel terzo e quarto byte, con la sola differenza che sul DHT-22 (questo non vale per il DHT-11) il 16° bit rappresenta il segno: se vale 1, la temperatura è negativa (siamo sotto zero!), se vale 0… la temperatura è positiva. Nel codice quindi mi controllo il 16° bit della temperatura e faccio gli opportuni aggiustamenti impostandomi a 1 un bit (t_negative) che controllerò in seguito per aggiungere il segno meno:

hr=(DHT22_data[0]<<8) | DHT22_data[1]; // entire humidity value
te=(DHT22_data[2]<<8) | DHT22_data[3]; // entire Temperature value
 
 // check if temperature is below 0 celsius
 if (te & 0x8000) // bit 16 is 1 ?
 {
 t_negative=true;
 te &= 0x7FFF; // reset bit 16
 }

Dato che poi utilizzo printf, faccio uso di due segnaposti per ogni parametro scrivendo prima la parte intera (valore diviso 10, che verrà troncato della parte decimale) e poi quella decimale ricavata per differenza dal valore ottenuto dal sensore e la parte intera (moltiplicata 10) già stampata:

printf("R. Humidity: %u.%u %%\n\r", hr/10, (hr-((hr/10)*10)));

Spiego quello che faccio in questa parte per ottenere la parte decimale:
mettiamo che il valore di umidità restituito dal sensore sia 594. Sappiamo che va diviso per 10, quindi il valore è in realtà 59.4. Devo stampare prima la parte intera, che otterrò con 594/10 (utilizzando segnaposti per numeri interi, la parte decimale verrà troncata). Dopodichè per ottenere il 4 dopo la virgola, mi prendo il 59 ottenuto prima, lo moltiplico per 10, ottenendo 590 e quindi faccio 594-590. Anche potendo utilizzare segnaposto per decimali come %f, preferisco questo secondo metodo perchè  quello più semplice da utilizzare anche nel caso in cui ci è necessario riportare i dati su un display per il quale magari non abbiamo librerie che consentono numeri float.

L’ultimo byte, infine, è il checksum: bisogna sommare i 4 bytes precedenti, e prendersi i primi 8 bit (basta semplicemente sommare i 4 bytes e riportarli in una variabile intera senza segno ad 8 bit) e quindi confrontarli con il valore di checksum inviato: se sono diversi, il dato è corrotto.

Vediamo ora come vengono trasmessi i singoli bit:

DHT22 - trasmissione dati
DHT22 – trasmissione dati

Come vedete, ogni singolo bit è composto da due transizioni: una dal livello logico alto al basso, in cui il livello basso viene tenuto per circa 50µS e una dal livello logico basso a quello alto in cui la durata del livello alto determina se il bit vale zero (il livello viene tenuto alto per circa 24µS) o uno (il livello logico viene tenuto alto per circa 70µS).

Con l’analizzatore logico ho potuto notare una cosa che sul datasheet non è scritta: tra un byte e l’altro (in pratica alla fine di ogni 8 bit), il livello logico basso viene mantenuto per più di 50µS, ho misurato una media di 67µS.

Approccio per la lettura dei dati

Ho detto prima che tra una lettura e l’altra devono passare almeno 2 secondi. In realtà questo tempo deve passare anche subito dopo l’accensione: non si può interrogare subito il sensore non appena gli si fornisce alimentazione: questo è il primo errore che ho commesso durante la sperimentazione. Nel codice comunque non ho implementato questo ritardo iniziale perchè ho previsto un sistema più efficiente che mi consente di mantenere il codice funzionante in svariate situazioni, senza rimanere bloccati in loop, anche in caso di guasto del sensore o del suo scollegamento/ricollegamento. Questo è l’approccio che si dovrebbe normalmente avere in tutti quei casi in cui nel codice ci mettiamo in loop per attendere la transizione di un livello logico.

Mettiamo il caso in cui dobbiamo attendere la transizione dal livello logico alto a quello basso e ci mettiamo in un while (linea=livello alto). Se in questo caso scolleghiamo il sensore, o si guasta, il livello rimane alto vita natural durante dal momento che c’è la resistenza di pull-up: il programma rimane bloccato in questo punto e non funzionerà più nulla.

Per ovviare a questo e per leggere i tempi in maniera precisa ho utilizzato il Timer1 configurato come semplice timer. Ho impostato il tutto per fare in modo che il timer1 incrementi di 1 unità ogni microsecondo. Il timer1 andrà quindi in overflow un microsecondo dopo aver raggiunto il valore 255. Dal momento che nessun tempo di risposta raggiunge quel valore, mi è sembrato ragionevole azzerare il timer1 all’inizio di ogni controllo e verificare che almeno una delle due condizioni sia raggiunta: transizione avvenuta o overflow del timer. In questo modo se il sensore decide di prendersi una pausa, il timer1 mi fa uscire dal loop. Controllo difatti il flag di interrupt TMR1IF che sia settato o meno.

E’ lo stesso principio di funzionamento del Watchdog Timer, soltanto che utilizzo il Timer 1 per il motivo che sto per dire

Il vantaggio di usare il Timer1 è dato dal fatto che posso anche misurare con precisione i tempi del livello logico alto quando viene trasmesso il dato , la cui durata abbiamo visto determina se il bit vale 0 o 1. Prima di ogni controllo azzero il timer, se avviene la transizione entro il tempo di overflow del timer, mi leggo il valore a cui è arrivato e imposto il valore del bit nel buffer di ricezione.

Schema per il tutorial

Per la comunicazione con il sensore e la lettura dei dati ho utilizzato un PIC12F1822. Abbiamo visto in un tutorial precedente come impostare la UART proprio su questo picmicro.

Quel tutorial vi sarà utile più che altro se utilizzate il Pickit2, dal momento che nel nuovo MPLAB-X Ide non è consentito l’utilizzo del Pickit2 per il 12F1822 nonostante abbiamo già visto che è possibile utilizzarlo. Quindi se usate il Pickit2, non potete programmarlo da dentro MPLAB-X ide ma dovete ricorrere alla vecchia applicazione stand-alone (leggete quel tutorial se la vostra applicazione del Pickit non riconosce il PIC12F1822), altrimenti modificate il progetto per utilizzare il Pickit3.

Schema utilizzato per il tutorial. Il disegno è stato realizzato con ManoCad versione 2.0 Deluxe BIC powered edition
Schema utilizzato per il tutorial. Il disegno è stato realizzato con ManoCad versione 2.0 Deluxe BIC powered edition

 

Realizzazione su millefori: a sinistra il convertitore USB-seriale/TTL e a destra il connettore ICSP. Occhio che di default i pin TX/RX combaciano con i pin ICSPDAT/ICSPCLK del pic, per cui quando tenete collegato il convertitore, staccate il pickit e viceversa
Realizzazione su millefori: a sinistra il convertitore USB-seriale/TTL e a destra il connettore ICSP. Occhio che di default i pin TX/RX combaciano con i pin ICSPDAT/ICSPCLK dell’ICSP, per cui quando tenete collegato il convertitore, staccate il pickit e viceversa

Il pic legge i dati dal sensore e li trasmette sulla UART a 9600pippobaud da cui potete leggerli utilizzando un adattatore TTL/USB-seriale e un programma come il buon vecchio Hyperterminal (io mi sto trovando bene anche con 232 Analyzer sotto Windows da 7 in su).

Output a terminale
Output a terminale

Se provate a staccare il sensore a runtime e poi riattaccarlo, il programma continua a funzionare grazie all’artifizio che vi dicevo prima del Timer1, e viene segnalato un errore di timeout:

Segnalazione timeout
Segnalazione timeout

Downloads

Mi scuso se i commenti all’interno del programma di esempio sono in inglese ma devo adeguarmi alla comunità estera che è sempre più folta. Se volete caricare direttamente il mio Hex sul pic basta che lo cercate in \DHT22_test.X\dist\default\production\

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