Autor: Michał Smalera
Redakcja: Dondu
Witam ponownie, zachęcony sukcesem mojego pierwszego artykułu o akcelerometrze LSI302 postanowiłem przybliżyć czytelnikom tani czujnik wilgotności i temperatury DHT11 (ang Humidity & Temperature Sensor).
DHT11 to czujnik temperatury i wilgotności względnej. Dostępny w obudowie z 4 wyjściami (single row) i może być zasilany napięciem od 3,5V do 5,5V. Może odczytywać temperaturę w zakresie 0-50 st.C z dokładnością +/- 2 st.C oraz względną wilgotność w zakresie 20-95% z dokładnością +/- 5%.
Dokumentacja: DHT11 (kopia)
Ponieważ sensor komunikuje się za pomocą "modyfikowanego" protokołu 1-wire, dlatego nie jest możliwa jego współpraca z innymi, urządzeniami 1-wire, które korzystają z "prawdziwego" 1-wire.
DHT11 - Wygląd czujnika |
Poniższy rysunek obrazuje przebieg impulsów wymagany do rozpoczęcia transmisji. Master (mikroprocesor) inicjuje przesył danych podając stan niski na linię DATA na co najmniej 18ms, a następnie stan wysoki na 20-40µs po czym zwalnia linię. Teraz sensor odpowiada na sygnał z mikroprocesora przez wymuszenie na linii stanu niskiego na 80µs, po czym stanu wysokiego na następne 80µs. To jest tak zwany presence pulse (sygnał obecności).
Presence Pulse |
Należy pamiętać, żeby zmienić kierunek PIN-u mikroprocesora sterującego linią DATA zaraz po wysłaniu sygnału startu.
Po odebraniu sygnału obecności od sensora nasz mikroprocesor musi być gotów na odbieranie danych z DHT11.
Sensor wyśle 40 bitów danych (5 bajtów) w jednym ciągu. Sensor wysyła dane z najważniejszym bitem (MSB - Most Significant Bit) na początku.
40-bitowy zestaw danych ma następującą strukturę (w nawiasie nazwy angielskie):
Dane (40-bitów) = Całkowity Bajt Wilgotności (Integer Byte of RH) +
Dziesiętny Bajt Wilgotności (Decimal Byte of RH) +
Całkowity Bajt Temperatury (Integer Byte of Temp) +
Dziesiętny Bajt Temperatury (Decimal Byte of Temp) +
Bajt Sumy Kontrolnej (Chacksum Byte)
Jeśli wszystkie pięć bajtów zostały przesłane z sukcesem, wtedy bajt sumy kontrolnej musi być równy ostatnim 8 bitom sumy pierwszych 4 bajtów, czyli:
Suma Kontrolna = 8 ostanich bitów (Integer Byte of RH + Decimal Byte of RH + Integer Byte of Temp. + Decimal Byte of Temp.)
Teraz ważna sprawa. Kiedy DHT wysyła 1 a kiedy 0?
Jeśli jest wysyłany bit danych, sensor wymusza stan niski na linii DATA na 50µs. Następnie wymusza stan wysoki na 26-28µs jeśli wysyła "0", lub na 70µs jeśli wysyła "1". Na rysunku poniżej pokazano szerokości impulsów w przypadku wysyłania 0 i 1.
Przebiegi dla zera i jedynki |
Początek transmisji razem z inicjacją transmisji i dwoma pierwszymi bitami danych.
Przebieg czasowy |
Po wysłaniu ostatniego bitu, sensor wymusza stan niski na 50µs a następnie zwalnia linię.
Sensor DHT11 wymaga podłączenia rezystora pomiędzy linią DATA a Vcc, w celu wymuszenia stanu wysokiego, kiedy linia jest "wolna" (żadne z podłączonych urządzeń nie nadaje).
Jest to tak zwany pull-up resistor. Po wysłaniu paczki danych i zwolnieniu linii DHT11 przechodzi do stanu niskiego poboru energii aż do momentu pojawienia się sygnału inicjującego transmisję (z mikroprocesora).
Poniżej schemat połączeń:
Podłączenie do mikrokontrolera |
Software
Ze względu na wymagane przebiegi czasowe najlepszym wyjściem będzie użycie przerwań. Użyjemy jednego aby określić czas (szerokość) impulsów. W zależności od tej szerokości będziemy w stanie określić, czy DHT wysłał 1 czy 0.
Będziemy obserwować stan na linii DATA. Jeśli zauważymy przejście ze stanu niski na wysoki, czyścimy daną time i zaczynamy zliczanie. Kiedy zauważymy, że DATA jest znowu w stanie niskim zatrzymujemy zliczanie. Wartość zmiennej timer mówi nam o szerokości impulsu. Użyjemy wartości 40µs, aby zdecydować, czy mikroprocesor odebrał 1 czy 0.
Jeśli zmienna timer jest większa niż 40, odebraliśmy 1, jeśli mniejsza - 0.
main.c
#define LED_DIR_SET_OUT DDRC|=(1<<PC0) #define LED_ON PORTC|=(1<<PC0) #define LED_OFF PORTC&=~(1<<PC0) #define LED_TOGGLE PORTC^=(1<<PC0) #define DHT_PORT PORTD #define DHT_PIN PD2 #define DHT_DIR DDRD #define DHT_PIN_STATE PIND #include <avr/io.h> #include <util/delay.h> void init_dht(void); uint8_t fetchData(uint8_t* arr); int main(void) { uint8_t data[4]; init_dht(); for(;;) { LED_OFF; _delay_ms(500); LED_ON; if (fetchData(data)) { //tutaj dostępne są dane w postaci tablicy data[] } } return 0; } void init_dht(void) //done { /* Set LED as output */ LED_DIR_SET_OUT; /* Zgodnie z datasheet DHT11 potrzebuje jakieś 1 do 2 sekund * po włączeniu zasilania dla osiągnięcia stanu stabilnego */ _delay_ms(2000); LED_ON; } uint8_t fetchData(uint8_t* arr) { uint8_t data[5]; uint8_t cnt, check; int8_t i, j; /******************* Inicjalizacja *******************/ /* Ustaw pin danych jako wyjście */ DHT_DIR |= (1 << DHT_PIN); /* Najpierw potrzebujemy opóźnienia 20 milisekund, więc preskaler clk/1024 */ TCCR0 = 0; //wyzerowanie TCCR0 TCCR0 |= (1 << CS02) | (1 << CS00); //prescaler 1024 TCNT0 = 0; //licz do 215 dla 20ms (przy clk=11059200Hz) /* stan niski na 20 ms */ DHT_PORT &= ~(1 << DHT_PIN); /* czekaj około 20 ms */ while (TCNT0 < 217) ; /* Teraz ustaw Timer0 z preskalerem clk/8 */ TCCR0=0; TCCR0 |= (1<<CS01); // clk/8 TCNT0 = 0; /* Stan wysoki */ DHT_PORT ^= (1 << DHT_PIN); /* Ustaw pin danych jako wejście */ DHT_DIR &= ~(1 << DHT_PIN); /* Czekaj na odpowiedź z sensora 20-40µs */ while (DHT_PIN_STATE & (1 << DHT_PIN)) { if (TCNT0 >= 82) return 0; // 60us --> TCNT0 musi liczyć do 82 z 11059200CPU_CLK } /************************* Rozpoczęcie transmisji *************************/ TCNT0 = 0; /* Czekamy aż sensor odpowie. Najpierw stan niski ~80µs */ while (!(DHT_PIN_STATE & (1 << DHT_PIN))) { if (TCNT0 >= 137) return 0; //jeśli więcej niż 100us, fail } TCNT0 = 0; /* Teraz stan wysoki: ~80µs */ while (DHT_PIN_STATE & (1 << DHT_PIN)) { if (TCNT0 >= 137) return 0; //if more than 100us - fail } /********************* Przesył danych **********************/ for (i = 0; i < 5; ++i) { for (j = 7; j >= 0; --j) { TCNT0 = 0; /* Najpierw 50µs stanu niskiego */ while (!(DHT_PIN_STATE & (1 << DHT_PIN))) { if (TCNT0 >= 96) return 0; //70us } TCNT0 = 0; /* Teraz dane. 26 to 28µs stanu wysokiego wskazuje zero, a około 70µs to wysłana jedynka */ while (DHT_PIN_STATE & (1 << DHT_PIN)) { if (TCNT0 >= 123) return 0; //90us } /* Żeby nie psuć zmiennej TCNT0, skopiujemy ją sobie */ cnt = TCNT0; if (cnt >= 27 && cnt <= 47) { //pomiędzy 20 i 35us data[i] &= ~(1 << j); //ustaw zero } else if (cnt >= 82 && cnt <= 110) { //pomiędzy 60 and 80us data[i] |= (1 << j); //jedynka } else return 0; } } /********************* Zakończenie komunikacji, CRC *********************/ check = (data[0] + data[1] + data[2] + data[3]) & 0xFF; if (check != data[4]) return 0; for (i = 0; i < 4; ++i) { arr[i] = data[i]; } return 1; }Do pobrania: main.c (kopia)
Wynik
Dokładność czujnika DHT11 nie jest zbyt wysoka, jednak zastosowanie takiego czujnika jest łatwe i stosunkowo tanie. Dzięki dostępności dwóch danych (wilgotność i temperatura) istnieje możliwość łatwego zastosowania do obliczenia np. punktu rosy.
Powodzenia w pomiarach!
:-)
Super artykuł, oby więcej takich. Krótko i na temat, do tego przykład kodu... Istne MIODZIO ;)
OdpowiedzUsuńW kontekście utrzymywania dobrej atmosfery w pokoju to jeszcze mi się marzy czujnik dwutlenu węgla i tlenu, ale obawiam się, że mało tanich chińczyków tego typu się znajdzie. Jakbyście jednak znaleźli to pochwalcie się :)
OdpowiedzUsuńMoże ktoś z czytelników ma dostęp?
UsuńPa katastrofie elektrowni w Japonii na różnorodnych stronach traktujących o elektronice pojawiło się całe mnóstwo detektorów promieniowania radioaktywnego. Efekt dobry, tylko nieco spóźniony (no, chyba że coś podobnego wydarzy się znowu - oby nie).
UsuńChciałbym zbudować urządzenie do detekcji innego "czynnika" zabijającego, a stanowiącego realne zagrożenie - dwutlenku siarki. Wspominam o tym ze względu na sporą aktywność wulkaniczną na Islandii. Dla zainteresowanych: polecam zapoznanie się z danymi dotyczącymi erupcji wulkanu Laki w 1783r.
Niestety, detektory SO2 są małodostępne, no i drogie :(
Niemniej z chęcią napiszę jakiś artykuł dot. czujnika CO2 lub tlenu, niech mi tylko coś w łapki wpadnie.
Fajny artykuł, krótko, treściwie :-)
OdpowiedzUsuńWitaj, czy takie odebranie danych jest poprawne, ponieważ nie mogę odczytać żadnych poprawnych danych:
OdpowiedzUsuńif (fetchData(data)) {
zm=data[2];
//tutaj dostępne są dane w postaci tablicy data[]
}
uart_putint(zm,10);
Super tego właśnie szukałem. Wielkie dzięki!!
OdpowiedzUsuń