DHT22 po taniości

Powiem szczerze, nie jestem fanem czujników z rodziny DHT. Pewnie właśnie dlatego chyba nigdy nie było nic tutaj o tym sensorze. A mam wrażenie, że te czujniki ciągle gdzieś „latają” w świecie DIY. Gdzie się nie popatrzy, są w różnych zestawach startowych, na różnych stronach z tutorialami pełno jest przykładów użycia DHT.

Po dłuższym zastanowieniu się, widzę jedną, jedyną przewagę DHT nad sensorami typu SHT, BME, BMP i innymi. Specyficzny protokół komunikacji na jednym tylko pinie pozwala go podpiąć do systemów z brakującymi wolnymi IO. Co prawda, jeśli w projekcie już używasz I2C to już nie jest to przewaga, bo możesz łatwo do szyny dodać jakiś czujnik temperatury/wilgotności.

No, ale powiedzmy że przy użyciu np ESP01s które ma tylko 2 IO (no chyba że TX/RX rezygnujemy to są 4) użycie DHT może mieć sens.

Drugim powodem dla którego DHT znalazł się na moim radarze to był fakt to, że tak zwany worek KOPAJ! od naszego chińskiego brokera leżał na biurku i w nim też DHT… Co to za worek? Broker czasem nam podsyła różne sample i często są to funkcjonalne kopie różnych sensorów. Któregoś razu naklejka na woreczku była z literówką, że sensor z COPAY CHIP. I tak już zostało – KOPAJ u nas to generalnie klony.

Jak wspomniałem leżał tam już jakiś czas klon DHT22. Ma być funkcjonalnie i rozmiarowo identyczny, ale cena bardziej korzystna. Pokrótce zrobione testy pokazują, że istotnie, działa podobnie a może czasami nawet lepiej (O tempora, o mores! klony są lepsze?).

Porównanie wyników

Wizualna różnica między klonami a oryginałami to nadruk na plastikowej obudowie. Klony nie posiadają takowego.

Na początek wrzuciłem na płytkę stykową 4 DHT22 – dwa oryginały, dwa klony, potem dorzuciłem SHT31 jako „wzorcowy” czujnik. Całość prezentuje się jak wyżej na zdjęciu. Nie przedłużając – przykłady wyników:

Jak widać wyniki generalnie zbieżne i powtarzalne. Nie jest to żaden rozbudowany test, podobieństwo jest wystarczające. Dlatego od kilku dni jest już w naszej ofercie – moduł klon DHT22.

Jak podłączyć do Arduino/ESP

Podłączenie i korzystanie z DHT22 jest dość proste. Podłączenie – jak wspomniałem zaletą DHTxx jest korzystanie tylko z jednego pinu danych. Wystarczy go podłączyć do któregoś IO cyfrowego.

Ujęcie od zaplecza pokazuje wyraźnie jak podłączyć – niebieskie zworki podłączają masę a zielone zasilanie. Przewodami JW-75 podłączone są sygnały danych do wybranych pinów cyfrowych. Na wierzchu leży kabelek I2C do NAM, z przylutowanym SHT31. On jest podłączony do szyny I2C – (pomiarów temperatury za pomocą SHT w tym artykule nie opisuję).

Tutaj mała uwaga o napięciach. Dawno temu (w 2012) napisaliśmy artykuł o łączeniu urządzeń pracujących w na różnych poziomach napięć. Zasadniczo jest nadal aktualny, bo zasady nie zmieniły się, ale pamiętaj – moduł DHT22 możesz zasilić napięciem od 3 do 5V. Sygnały na wyjściu modułu sygnalizujące 1 będą równe napięciu zasilania. Jeśli masz mikrokontroler pracującego w 3.3V którego wejścia są wrażliwe na napięcie 5V pamiętaj by zasilić DHT22 też 3.3V, nie będzie problemu.

Jeśli przyjrzysz się uważnie poprzedniemu zdjęciu, zobaczysz, że popełniłem błąd podłączając zasilanie 5V do sensorów. To oznacza, że napięcie 5V było przekazywane na IO ESP8266. Wg karty katalogowej ESP8266 maksymalne napięcie wejściowe na pinie IO to 3.6V. Jak pokazuje praktyka ESP8266 zwykle wytrzymuje napięcie 5V (ten Wemos dalej działa), ale to że zwykle się udaje, nie znaczy że tak będzie zawsze. Projektując coś na ESP8266 pamiętaj o tym!

Biblioteki i odczytanie danych z DHT22 na Arduino/ESP

Gdy już mamy podłączony sensor, pozostaje użycie odpowiedniej biblioteki by dostać dane. Bibliotek jest sporo, ja do testów porównawczych użyłem tych od Adafruit. Czemu liczba mnoga? Bo Adafruit udostępnił bardzo dużo bibliotek dla Arduino na licencji open source i aby ułatwić/zestandaryzować pracę podzieli to na kilka poziomów abstrakcji. Dzięki temu część kodu jest wspólna dla wszystkich bibliotek, ale w praktyce to znaczy, że trzeba zainstalować:

lib_deps =
adafruit/Adafruit Unified Sensor@^1.1.15
adafruit/DHT sensor library@^1.4.6
adafruit/Adafruit DHT Unified@^1.0.0

Format jest z platformio.ini, bo jak czytelnicy bloga pewnie dobrze wiedzą, to jest środowisko z którego korzystam na co dzień. Jeśli korzystasz z Arduino IDE, musisz ręcznie wskazać które biblioteki zainstalować.

Kod do testów pisałem na szybko, teraz tak się przyglądam tym bibliotekom i możliwe, że wystarczy sama adafruit/DHT sensor library bo choć w opisie jest o zależności od Adafruit Unified to jak przejrzałem kod korzystanie z samego DHT.h chyba jest bez konieczności instalacji Unified.

Ja podłączyłem te wspomniane 4 sensory do Wemosa D1 mini, każdy do oddzielnego pinu. W kodzie wygląda to tak:

#include <DHT_U.h>

DHT_Unified dht_or_1(D2, DHT22);
DHT_Unified dht_or_2(D3, DHT22);
DHT_Unified dht_cl_1(D6, DHT22);
DHT_Unified dht_cl_2(D5, DHT22);

Nazewnictwo zmiennych jest chyba jasne – or to oryginalne DHT22, cl to klony. Pierwsza argument to nr pinu do którego podłączony jest DHT. Przy korzystaniu z ESP pamiętaj o używaniu etykiet Dx bo wartość numeryczna oznacz nr GPIO ESP8266, jeśli używasz Arduino wtedy podawaj po prostu numery pinów. Czyli D2 dla ESP8266, po prostu 2 dla Arduino.

Drugi argument to rodzaj sensora. Bo oprócz DHT22 mamy jeszcze DHT11 i jeszcze rzadziej spotykane odmiany jak DHT21.

W funkcji setup pracę z sensorem trzeba rozpocząć przez wywołania begin:

    dht_cl_1.begin();
    dht_cl_2.begin();
    dht_or_1.begin();
    dht_or_2.begin();

Chyba to oczywiste, że u Ciebie pewnie będzie to tylko jeden sensor.

Teraz natkniemy się na efekty tego utworzenia przez Adafruit wspólnych abstrakcji w obsłudze sensorów. „Normalnie” odczytuje się wartość przez jakieś read a tutaj mamy nieco bardziej skomplikowaną procedurę. Ale tylko trochę.

Abstrakcja wprowadza tak zwane zdarzenie (event). Chcąc odczytać temperaturę trzeba ściągnąć zdarzenie dla temperatury. Ponieważ w moim przypadku miałem 4 sensory, to napisałem pomocniczą funkcję, która jako argumenty bierze obiekt typu DHT_Unified oraz strukturę w której zapisze wyniki.

void readSensorVal(DHT_Unified dht, Readings &s) {
    sensors_event_t event;
    bool valid = true;
    dht.temperature().getEvent(&event);
    if (isnan(event.temperature)) {
        valid = false;
    } else {
        s.t = event.temperature;
    }
    // Get humidity event and print its value.
    dht.humidity().getEvent(&event);
    if (isnan(event.relative_humidity)) {
        valid = false;
    } else {
        s.h = event.relative_humidity;
    }
    s.valid = valid;
}

Czyli event to pomocnicza zmienna która przechowuje dane każdego zdarzenia (odczyt temperatury czy wilgotności. Temperaturę odczytujemy przez dht.temperature().getEvent(&event). Wywołując kolejno moją funkcję readSensorVal jako pierwszy argument podaję kolejno dht_cl_1, dht_cl_2, itd. Dzięki temu funkcja odczytuje dane z kolejnych sensorów.

Abstrakcja od Adafruit zakłada że „ogólny” sensor ma różne typy i może zwracać różne dane. Dlatego dla temperatury mamy dht.temperature().getEvent(&event) a by ściągnąć dane o wilgotności dht.humidity().getEvent(&event). Po wywołaniu możemy sprawdzić czy wartość odczytana jest liczbą (isnan zwraca true jeśli argument nie jest liczbą – IS Not A Number) – jeśli nie to cały odczyt jest oznaczany jako niepoprawny.

Readings to struktura mająca dwa pola typu float na temperaturę i wilgotność i flagę czy odczyt jest poprawny.

typedef struct {
    float t;
    float h;
    bool valid;

} Readings;

Nie będę tutaj wrzucał całego programu, który cyklicznie wyniki wrzuca do mojego roboczego Influx’a, ale sama budowa łańcucha do wysłania do bazy wygląda tak:

  if (millis() - last_send >= INTERVAL) {
        String postData;
        postData = String(MEASUREMENT_NAME) + String(F(",host=esp8266-")) + String(ESP.getChipId()) + F(" ");
        readSensorVal(dht_cl_1, s);
        if (s.valid)
            postData += F("cl1_t=") + String(s.t) + F(",cl1_h=") + String(s.h);
        readSensorVal(dht_cl_2, s);
        if (s.valid)
            postData += F(",cl2_t=") + String(s.t) + F(",cl2_h=") + String(s.h);
        readSensorVal(dht_or_1, s);
        if (s.valid)
            postData += F(",or1_t=") + String(s.t) + F(",or1_h=") + String(s.h);
        readSensorVal(dht_or_2, s);
        if (s.valid)
            postData += F(",or2_t=") + String(s.t) + F(",or2_h=") + String(s.h);
        
        sendToDB(postData);
        last_send = millis();
    } 

Jeszcze tylko taka uwaga – w definicji funkcji readSensorVal:

void readSensorVal(DHT_Unified dht, Readings &s) {....}

drugi argument jest przekazywany przez referencję (znak &) – dzięki temu dane wpisane do tej struktury wewnątrz funkcji są dostępne po powrocie.

Jeszcze o jednym sensorze by złączyć wszystkie, czyli Unified Sensor od AF

Aż mnie korci przepisać ten kod z użyciem tylko biblioteki AF/DHT bez tej abstrakcji „jednego” sensora. Mam wrażenie, że do takich prostych zadań jest to lepsze rozwiązanie i kod jest bardziej czytelny nawet dla początkującego.