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ń