Misurare Temperatura e Umidità relativa con il sensore DHT22 e un microcontrollore PIC
Indice dei contenuti
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.
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:
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:
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:
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.
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).
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:
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\
- Datasheet DHT22 / AM2302 (983 download)
- Sorgente e Progetto per MPLAB-X Ide (746 download)
- Cattura segnali da analizzatore logico (richiede il software LAP-C) (503 download)