Czujnik wilgotności DHT11 + Atmega8. Jak czytać noty katalogowe i na ich podstawie napisać bibliotekę.

Czujnik wilgotności DHT11 + Atmega8. Jak czytać noty katalogowe i na ich podstawie napisać bibliotekę.

Witam Wszystkich.

Jest to mój pierwszy artykuł na majsterkowie. Raczej byłem użytkownikiem typu read-only i postanowiłem to zmienić.
Bardzo dużo potrzebowałem czasu żeby ruszyć z elektroniką, podchodziłem już chyba 2 razy, dopiero za 3 razem udało mi się zrobić już coś praktycznego i z głową.
Chciałbym wam zaprezentować połączenie mikrokontrolera z rodziny AVR (Atmega8) z czujnikiem temperatury i wilgotności (DHT11).
W artkule bardziej bym chciał się skupić na programowaniu i napisaniu własnej biblioteki także wydaje mi się że artykuł będzie bardziej skierowany dla początkujących programistów układów.
Na co dzień pracuję jako programista c# (głównie aplikacje biznesowe) ale od zawsze mnie ciągnęło w stronę elektroniki i w końcu cieszę się że udało mi się ruszyć.

Wykaz części:

-Mikrokontroler Atmega8 (L)
-Czujnik temperatury i wilgotności DHT11
-Wyświetlacz HD44780
-Gold piny
-Rezystor 10k

Przygotowanie kodu:

Ważna informacja, pierwotny kod ściągnąłem od tego Pana – http://davidegironi.blogspot.com/2013/02/reading-temperature-and-humidity-on-avr.html
Stwierdziłem że chciałbym się zagłębić dokładnie w ten kod i zacząłem wszystko analizować co tam się dzieję, troszkę zrefaktoryzowałem i pomyślałem że się podzielę przemyśleniami.

Żeby stworzyć bibliotekę musimy utworzyć dwa pliki, plik nagłówkowy .h i plik z kodem .c.

W pliku nagłówkowym podajemy dwie funkcje z których skorzystamy:

Zadeklarujemy sobie również kilka pomocniczych dyrektyw preprocesora, dzięki temu możemy napisać uniwersalny kod a jedyne zmiany będą w tych właśnie dyrektywach.

W trakcie kompilacji wszystkie wystąpienie DHT11_DDR będą podmienione na DDRC itd.

Operacje logiczne:

Aby wymusić stan niski lub wysoki dla pinu mamy kilka możliwości. Aby rozwiązanie było w miarę uniwersalne można wykorzystać operacje bitowe OR i AND. Zajmijmy się najpierw ustawieniem stanu wysokiego, do rozważenia mamy dwa przypadki, albo na pinie jest stan niski albo wysoki. Nasz port PC5 w systemie dwójkowym wygląda tak – 00100000.

Jak widać, w tym wypadku OR rozwiązuje problem.

Jeśli byśmy chcieli ustawić stan niski to już się sprawa trochę komplikuje, żadne z powyższych operacji nie rozwiązuje naszego problemu. Można jednak wykorzystać kolejną operację jaką jest odwrócenie.

Teraz widać że wykonując zaprzeczenie i AND jesteśmy w stanie ustawić stan niski na żądanym pinie.

Implementacja:

Żeby poprawnie podłączyć czujnik DHT11 oraz napisać kod biblioteki, potrzebna nam jest dokumentacja: http://www.micropik.com/PDF/dht11.pdf

Z dokumentacji możemy wyczytać że czujnik zwraca dane w 40 bitach (czyli 5 bajtach).

8 bitów danych wilgotności
8 bitów danych wilgotności
8 bitów danych temperatury
8 bitów danych temperatury
8 bitów suma kontrolna

Ostatni bajt to suma kontrolna, tak żebyśmy byli w stanie potwierdzić czy dobrze odczytaliśmy dane.
Suma 4 pierwszych bajtów powinna być równa sumie kontrolnej.

Pierwsze linijki naszej funkcji to po prostu deklaracja, inicjalizacja zmiennych i przygotowanie portu:

Zgodnie z dokumentacją, jeśli chcemy rozpocząć komunikację z DHT11 musimy podać stan niski na nóżkę danych czujnika. Między innymi dlatego w funkcji ResetPort ustawiamy stan wysoki, tak żeby czujnik nie zaczął nadawać.

When the communication between MCU and DHT11 begins, the programme of MCU will set Data Single-bus voltage level from high to low
and this process must take at least 18ms to ensure DHT’s detection of MCU’s signal, then MCU will pull up voltage and wait 20-40us for DHT’s response.

Aby rozpocząć komunikację z DHT11 musimy podać stan niski na nóżkę danych czujnika i odczekać co najmniej 18ms.

Następnie ustawiamy z powrotem stan wysoki oraz ustawiamy na naszym rejestrze DDR PIN aby działał jako wejście tak abyśmy mogli odczytać dane i czekamy 20-40us.

Once DHT detects the start signal, it will send out a low-voltage-level response signal, which lasts 80us.
Then the programme of DHT sets Data Single-bus voltage level from low to high and keeps it for 80us for DHT’s preparation for sending data.

Jeśli czujnik zrozumiał nasz sygnał powinien ustawić stan niski na nóżce który powinien trwać 80ms. Następnie ustawiany jest stan wysoki które również trwa 80us.
Sprawdźmy więc czy rzeczywiście proces odbywa się w ten sposób. W przypadku błędu możemy zwrócić jakąś liczbę (która nie będzie temperaturą zwróconą przez czujnik). Czujnik zwraca dane z zakresu 0-50 ℃, więc jeśli zwrócimy np. -1 będziemy wiedzieć że nie jest to wartość z czujnika. Gdy dodamy kolejną dyrektywę i w przyszłości zmienimy zdanie że chcemy zwracać inną wartość, wystarczy to zmienić tylko w jednym miejscu.

When DHT is sending data to MCU, every bit of data begins with the 50us low-voltage-level and the length of the following high-voltage-level signal determines whether data bit is “0” or “1”.

Gdy czujnik zaczyna wysyłać dane, ustawia stan niski na nóżce przez 50us, następnie jest ustawiany stan wysoki i w zależności od długości trwania otrzymujemy wartość 0 lub 1 (26-28ms oznacza 0).
Aby uprościć kod wystarczy sprawdzić czy stan wysoki się utrzymuje po 26-28us, jeśli tak to wtedy mamy wartość 1.

Przy odczycie chcemy wypełnić każdy bajt danych dlatego warto skorzystać z pętli for. Pierwsza pętla jest wykonywana 5 razy ponieważ DHT11 zwraca 5 bajtów.
Następnie próbujemy odczytać każdy bit po kolei dlatego musimy zrobić zagnieżdżoną pętlę która wykona się 8 razy (1 bajt – 8 bitów).

Następnie sprawdzamy sumę kontrolną, jeśli jest poprawna to wtedy zwracamy wartość, w innym przypadku zwracamy błąd.

Refaktoring:

To już jest takie małe zboczenie zawodowe ale czasami warto przejrzeć nasz kod i sprawdzić czy można coś w nim jeszcze poprawić.
Przykładowo linijka:

Jak tak patrzę na ten kod to jest dla mnie trochę mało czytelny. Można przenieść tą część kodu do dyrektywy preprocesora, np.

Dzięki temu nasz kod będzie wyglądał tak:

Według mnie teraz jest czytelniej i wiemy że pętla będzie się wykonywać dopóki jest stan wysoki. Dodatkowo pozbywamy się duplikacji kodu gdyż to samo wyrażenie jest wykorzystane w ifie:

które możemy zastąpić

Załączam pliki programu, dht11.h, dht11.c oraz main.c w ostatecznej wersji u mnie, po refaktoryzacji. Mam nadzieję że komuś się przyda ten artykuł, bardzo dobrze jest rozumieć kod z którego się korzysta.

Pozdrawiam wszystkich majsterkowiczów!  

Pliki załączone do artykułu:

Ocena: 5/5 (głosów: 9)

Podobne posty

5 komentarzy do “Czujnik wilgotności DHT11 + Atmega8. Jak czytać noty katalogowe i na ich podstawie napisać bibliotekę.

  • Oj, widać że kolega rzadko coś w C robi…

    #define DHT11_High DHT11_PIN & (1<< DHT11_INPUTPIN)

    i to napisał zawodowy programista???
    Popraw szybko bo oczy bolą, a jeszcze ktoś podobnej konstrukcji będzie chciał w innym kontekście użyć i będzie szukać błędu.
    Nie czepiałbym się, ale chciałeś pokazać jak się pisze biblioteki ;)

    Odpowiedz
    • Witam,

      Przyznam szczerzę że z programowaniem niskoobietkowym nie miałem za dużo styczności. Jedyne co mi przychodzi do głowy w tym przypadku to brakuje nawiasów wokół całego wyrażenia, może to powodować potencjalne problemy. Proszę o informację zwrotną, co tutaj jest źle, w imieniu moim i przyszłych użytkowników. Bardzo dziękuję za odpowiedź i zainteresowanie moim artykułem, pozdrawiam.

      Odpowiedz
      • Przede wszystkim, przez użycie definicji do odkreślania używanych pinów, ta biblioteka nie jest abstrakcyjna, co eliminuje wiele przypadków użycia, na przykład uniemożliwia używanie wielu czujników czy rekonfigurację w trakcie działania programu.

        Dalej, tak jak już zauważyłeś nawiasy w definicjach są wskazane. Tworzenia definicji a’la DHT11_Low niepotrzebnie zaciemnia kod. Większość standardów kodowania w C nakazuje minimalizację używania definicji (a szczególnie złożonych wyrażeń). Nazwa DHT11_Low jest myląca – sugeruje, że to jest coś w stylu 4 mniej znaczących bitów. Jeżeli chciałbyś to wyrażenie uprościć, to lepiej jest zrobić coś w stylu “while(! (DHT11_PIN & B_DHT11_INPUTPIN))” gdzie B_DHT11_INPUTPIN to (1 << DHT11_INPUTPIN)

        Odnośnie sposobu definiowania rejestrów, to polecam schemat przyjęty przez Intela. Przykładowy plik do znalezienia: PchRegsSpi.h. W tym projekcie to trochę na wyrost ale dobrze się używa.

        Odpowiedz
  • Dokładnie o to – poprawiłeś i masz zaległą piątkę.

    Na wypadek gdyby ktoś miał jakieś wątpliwości czemu się czepiam:

    Otóż kod makra jest (po rozwinięciu) wstawiany bezpośrednio do kodu źródłowego programu w miejscu wywołania. Czyli np. konstrukcja typu:

    #define sum(a,b) a+b
    c=sum(1,2);

    będzie po przejściu przez preprocesor wyglądać po prostu:
    c=1+2;

    Czyli pozornie wszystko jest w porządku…

    Ale co będzie w przypadku:

    c=sum(1,2) * sum(3,4);

    No będzie fatalnie – zostanie to przerobione na:

    c= 1 + 2 * 3 + 4;

    czyli zamiast oczekiwanego wyniku 21 dostaniemy 11…

    Również należy wewnątrz makra stosować nawiasy wokół parametrów dokładnie z tych samych powodów – czyli taka prosta definicja powinna wyglądać tak:

    #define sum(a, b) ((a) + (b))

    Ogólnie – w wielu przypadkach zamiast makr warto stosować funkcje a atrybutem inline, np.:

    int inline sum(int a, int b) {return a + b;}

    Niestety – w przypadku C ten sposób nie zdaje egzaminu w przypadku wywołań z różnymi typami argumentów (w C++ jest to możliwe).

    Odpowiedz
  • DHT11 jest b. kiepskim czujnikiem, stosowałem go przez jakiś czas w łazience i wyglądało to jakby wyniki były przypadkowe i nie zmieniały się w czasie, lepiej zastosować DHT22. Niewiele droższy a o niebo lepszy. Na zdjęciu porównanie.

    Odpowiedz

Odpowiedz

anuluj

Masz uwagi?