DHT22-Sensor mit ESP32 und ESP-IDF auslesen – ohne Bibliothek

WhatsApp Image 2026 06 06 At 12.44.35 E1780743493189

Inhaltsverzeichnis

In diesem Beispiel werden wir einen DHT22 Temperatur- und Luftfeuchtigkeitssensor mit dem ESP32 auslesen.

Zunächst wird die Funktionsweise des DHT22 Protokolls erläutert und anschließend per Bitbanging über einen GPIO des ESP32 umgesetzt. Eine externe Bibliothek wird dabei nicht verwendet.

Hinweis: Die Kommunikation mit dem DHT22 erfolgt zwar über eine Leitung und wird auch teilweise als 1-Wire-Kommunikation bezeichnet. Das Protokoll des DHT22 hat aber nichts mit dem standardisierten 1-Wire Protokoll der Firma Dallas Semiconductor zu tun.

Der DHT22 hat zwar:

  • nur eine Datenleitung
  • einen Pullup-Widerstand
  • bidirektionale Kommunikation über einen Pin

aber das eigentliche Protokoll ist komplett anders.

DHT22

Der DHT22 verwendet ein proprietäres Single-Wire-Protokoll, das speziell für diese Sensorfamilie entwickelt wurde. Im Gegensatz zum Dallas-1-Wire-Bus unterstützt das Protokoll keine Geräteadressen und keine Kommunikation mit mehreren Teilnehmern auf einer gemeinsamen Leitung.

  • Master zieht Leitung für mindestens 1 ms auf LOW.
  • Sensor antwortet mit einem festen Handshake (ca. 80 µs LOW, 80 µs HIGH).
  • Danach folgen 40 Datenbits.
  • Die Bits werden über die Länge des HIGH-Pegels codiert:
  • ~26–28 µs HIGH = 0
  • ~70 µs HIGH = 1
  • Die 40 Datenbits bestehen aus:
    • 16 Bit Luftfeuchtigkeit
    • 16 Bit Temperatur
    • 8 Bit Prüfsumme

Dallas 1-Wire

1-Wire ist ein vollständiger Busstandard, der viele verschiedene Gerätetypen unterstützt. Auf einem 1-Wire-Bus können mehrere Teilnehmer gleichzeitig angeschlossen sein. Jedes Gerät besitzt eine eindeutige 64-Bit-Adresse und kann dadurch eindeutig identifiziert werden.

Verwendet z. B. DS18B20.

  • Reset-Puls (~480 µs LOW)
  • Presence-Pulse
  • Datenübertragung in festen Timeslots
  • Geräteadressen (64 Bit ROM)
  • Mehrere Geräte auf einer Leitung möglich
  • Standardisiertes Busprotokoll für verschiedene Gerätetypen
  • Unterstützt viele verschiedene 1-Wire-Geräte (z. B. DS18B20, DS18S20, iButton, Speicherbausteine, etc.)

Gemeinsamkeiten

Nur das Grundprinzip:

  • eine Datenleitung
  • Open-Drain/Open-Collector ähnliches Verhalten
  • Pullup erforderlich

Fazit

Der DHT22 wird oft fälschlicherweise als „1-Wire-Sensor“ bezeichnet, weil nur eine Datenleitung verwendet wird. Technisch ist es aber ein proprietäres Single-Wire-Protokoll und nicht kompatibel zum Dallas 1-Wire-Bus. Ein 1-Wire-Treiber für einen DS18B20 funktioniert daher nicht mit einem DHT22.

Das DHT22 Protokoll

Im Wesentlichen ist das Protokoll wie folgt aufgebaut:

  1. Master zieht die Leitung für mindestens 1 ms auf LOW.
  2. Sensor antwortet mit einem Handshake.
  3. Anschließend sendet der Sensor 40 Datenbits.
  4. Jedes Bit beginnt mit einer LOW-Phase von etwa 50 µs.
  5. Die Länge der anschließenden HIGH-Phase bestimmt den Bitwert.
  6. Die 40 Bits bestehen aus:
    • 16 Bit Luftfeuchtigkeit
    • 16 Bit Temperatur
    • 8 Bit Prüfsumme
requesteandresponse
Abb. 1 – Repuest und Response des DHT22 auf dem Logic Analyzer

Datenformat des DHT22

Nach dem Startsignal und der Antwort des Sensors überträgt der DHT22 insgesamt 40 Datenbits. Diese 40 Bits entsprechen 5 Bytes. Die ersten vier Bytes enthalten die eigentlichen Messwerte für Luftfeuchtigkeit und Temperatur. Das fünfte Byte enthält eine Prüfsumme.

Die Luftfeuchtigkeit wird aus zwei Bytes gebildet: einem High-Byte und einem Low-Byte. Beide Bytes werden zu einem 16-Bit-Wert zusammengesetzt. Der DHT22 überträgt die Luftfeuchtigkeit mit einer Nachkommastelle. Ein übertragener Wert von 555 entspricht daher 55,5 % relativer Luftfeuchtigkeit.

Auch die Temperatur wird aus zwei Bytes zusammengesetzt. Ein übertragener Wert von 234 entspricht 23,4 °C. Bei negativen Temperaturen ist zusätzlich das Vorzeichenbit im Temperaturwert zu beachten.

Das letzte Byte dient zur Prüfung der Datenübertragung. Die Prüfsumme wird aus der Summe der ersten vier Bytes gebildet. Stimmen berechnete und empfangene Prüfsumme überein, wurden die Daten korrekt übertragen.

Byte Inhalt Beschreibung
Byte 1 Luftfeuchtigkeit High-Byte Oberes Byte des 16-Bit-Werts für die relative Luftfeuchtigkeit
Byte 2 Luftfeuchtigkeit Low-Byte Unteres Byte des 16-Bit-Werts für die relative Luftfeuchtigkeit
Byte 3 Temperatur High-Byte Oberes Byte des 16-Bit-Werts für die Temperatur
Byte 4 Temperatur Low-Byte Unteres Byte des 16-Bit-Werts für die Temperatur
Byte 5 Prüfsumme Summe der ersten vier Bytes, reduziert auf 8 Bit

Die Prüfsumme wird berechnet, indem die ersten vier Bytes addiert werden. Da die Prüfsumme nur ein Byte groß ist, werden nur die unteren 8 Bit dieser Summe verwendet.

Anforderungen an das Timing

Zum Auslesen des DHT22 können wir nicht sinnvoll mit vTaskDelay() oder vTaskDelayUntil() arbeiten. Diese Funktionen arbeiten auf Basis des FreeRTOS-Ticks und sind daher für Zeitbereiche im Millisekundenbereich gedacht. Selbst bei einer höheren Tickrate bleibt das Verfahren für die genaue Auswertung der DHT22-Signale ungeeignet, da die einzelnen Signalabschnitte nur wenige Mikrosekunden lang sind.

Die eigentliche Kommunikation mit dem DHT22 erfolgt deshalb blockierend im Mikrosekundenbereich. Kurze Wartezeiten können mit esp_rom_delay_us() erzeugt werden. Für die Messung der Signalzeiten wird esp_timer_get_time() verwendet, das die aktuelle Zeit in Mikrosekunden liefert.

Damit lassen sich die HIGH- und LOW-Phasen des Sensors messen und anschließend auswerten.

Warum nicht vTaskDelay()

vTaskDelay() und vTaskDelayUntil() sind Funktionen des FreeRTOS-Schedulers. Während der Wartezeit wird die aktuelle Task blockiert und der Scheduler kann andere Tasks ausführen. Die tatsächliche Wartezeit hängt dabei von der eingestellten Tickrate des Systems ab.

Bei einer Tickrate von 1000 Hz beträgt ein Tick 1 ms. Kürzere Zeiten können mit vTaskDelay() nicht erzeugt werden. Für viele Aufgaben wie zyklische Messungen, LED-Blinken oder Kommunikationsintervalle im Millisekundenbereich ist das vollkommen ausreichend.

Warum esp_rom_delay_us()

Die Kommunikation mit dem DHT22 erfordert Zeitauflösungen im Mikrosekundenbereich. Einzelne Signalabschnitte sind nur etwa 26 µs oder 70 µs lang. Solche Zeiten können mit FreeRTOS-Ticks nicht zuverlässig abgebildet werden.

Für diese kurzen Wartezeiten wird esp_rom_delay_us() verwendet. Die Funktion hält die CPU für die angegebene Anzahl von Mikrosekunden an und liefert dadurch eine deutlich höhere Zeitauflösung als vTaskDelay().

Der Nachteil besteht darin, dass während dieser Zeit keine andere Arbeit von der aktuellen CPU ausgeführt werden kann. Da die Datenübertragung des DHT22 jedoch nur wenige Millisekunden dauert, ist dieses blockierende Verfahren für diesen Anwendungsfall unkritisch.

Vergleich

Funktion Auflösung Task gibt CPU frei Typischer Einsatz
vTaskDelay() Millisekunden Ja Zyklische Tasks, Zeitsteuerungen
vTaskDelayUntil() Millisekunden Ja Deterministische Task-Perioden
esp_rom_delay_us() Mikrosekunden Nein Bitbanging, Sensorprotokolle, kurze Timing-Sequenzen

Sensor auslesen

Bevor die eigentliche Auswertung der 40 Datenbits implementiert wird, überprüfen wir zunächst, ob der Sensor korrekt auf die Startsequenz reagiert und welche Signalzeiten tatsächlich auftreten.

So lässt sich kontrollieren, welche Zeiten der Sensor in der Praxis tatsächlich verwendet. In Datenblättern und Beschreibungen findet man typische Werte wie etwa 80 µs LOW und 80 µs HIGH für das Antwortsignal sowie etwa 50 µs LOW zwischen den einzelnen Datenbits.

Bei meiner Messung lagen die Zeiten jedoch nicht exakt auf diesen Werten. Das ist grundsätzlich nicht ungewöhnlich, da solche Angaben meist typische Zeitbereiche beschreiben. Für die Auswertung ist es deshalb sinnvoller, nicht starr auf exakte Zeiten zu prüfen, sondern die Signalphasen zu messen und mit sinnvollen Grenzwerten zu arbeiten.

Genau das ist ein wichtiger Punkt beim DHT22: Die Bits werden nicht durch feste Timeslots wie beim Dallas-1-Wire-Protokoll übertragen, sondern über die Länge des HIGH-Pegels unterschieden. Deshalb wird im folgenden Schritt zunächst das reale Signal gemessen, bevor die eigentliche Auswertung der 40 Datenbits umgesetzt wird.

 

whatsapp image 2026 06 07 at 14.42.55
Abb. 2 – Gemessene Signalphasen des DHT22-Protokolls und den erwarteten Zeitwerten

Die Zeitmarken t₀ bis t₇ dienen im folgenden Testprogramm als Referenzpunkte für die Messung der einzelnen Signalabschnitte.

Folgender Testcode wird verwendet:

void DhtReadSensor()                                        
{

    gpio_set_pull_mode(DTH22Data, GPIO_PULLUP_ONLY);

    while(1)
    {    
        uint64_t HauptschleifeStart = esp_timer_get_time();    
        // Initalisierung, Request
        gpio_set_direction(DTH22Data, GPIO_MODE_OUTPUT);
        gpio_set_level(DTH22Data, 1);                       // 10 ms warten von Startsequenz
        vTaskDelay(10 / portTICK_PERIOD_MS);                
        
        gpio_set_level(DTH22Data, LOW);                     // Startsequenz, mindestens eine Millisekunde aktiv auf Low ziehen. Ich beginne mit 2 ms.
        esp_rom_delay_us(1200);                             

        gpio_set_direction(DTH22Data, GPIO_MODE_INPUT);     // Leitung frei geben und direkt Zeit ab hier messen, wie lange es dauert bis der Sensor auf Die Leitung auf Low zieht.
        int64_t t0 = esp_timer_get_time();                 

        wait_for_level(0, 100);                             
        int64_t t1 = esp_timer_get_time();                  // t0 bis t1, Zeit bis Sensor auf Low zieht

        wait_for_level(1, 100);                             
        int64_t t2 = esp_timer_get_time();                  // t1 bis t2, Zeit die der Sensor auf Low zieht -> also Antwort

        wait_for_level(0, 100);
        int64_t t3 = esp_timer_get_time();                  // t2 bis t3, Zeit die der Sensor auf High zieht -> Antwort   

        wait_for_level(1, 100);                             // t3 bis t4, Erstes Low der Datenübertragung
        int64_t t4 = esp_timer_get_time(); 

        wait_for_level(0, 100);                             // t4 bis t5, Erstes Bit von den 40 Bits der Datenübertragung
        int64_t t5 = esp_timer_get_time(); 

        wait_for_level(1, 100);                             // t5 bis t6, Zweites Low der Datenübertragung
        int64_t t6 = esp_timer_get_time(); 

        wait_for_level(0, 100);                             // t6 bis t7, Zweites Bit von den 40 Bits der Datenübertragung
        int64_t t7 = esp_timer_get_time(); 

        ESP_LOGI(TAG, "HIGH nach Release: %lld", t1 - t0);
        ESP_LOGI(TAG, "Erstes LOW: %lld", t2 - t1);
        ESP_LOGI(TAG, "Erstes HIGH: %lld", t3 - t2);
        ESP_LOGI(TAG, "Erstes Low der Datenübertragung: %lld", t4 - t3);  
        ESP_LOGI(TAG, "Erstes Datenbit: %lld", t5 - t4); 
        ESP_LOGI(TAG, "Zweites Low der Datenübertragung: %lld", t6 - t5);
        ESP_LOGI(TAG, "Zweites Datenbit: %lld", t7 - t6);

        vTaskDelay(2500 / portTICK_PERIOD_MS);
        int64_t HauptschleifeDauer = esp_timer_get_time() - HauptschleifeStart;
        ESP_LOGI(TAG, "Schleife durchgelaufen, dauer: %lld", HauptschleifeDauer);
    }   
}

Im Foldenden sieht man die Ausgabe im ESP-IDF Monitor.

I (40763) DHT22: Schleife durchgelaufen, dauer: 2529838
I (40773) DHT22: HIGH nach Release:                18
I (40773) DHT22: Erstes LOW:                       70
I (40773) DHT22: Erstes HIGH:                      71
I (40773) DHT22: Erstes Low der Datenübertragung:  48
I (40783) DHT22: Erstes Datenbit:                  23
I (40783) DHT22: Zweites Low der Datenübertragung: 47
I (40793) DHT22: Zweites Datenbit:                 23
I (43293) DHT22: Schleife durchgelaufen, dauer: 2529838
I (43303) DHT22: HIGH nach Release:                21
I (43303) DHT22: Erstes LOW:                       69
I (43303) DHT22: Erstes HIGH:                      71
I (43303) DHT22: Erstes Low der Datenübertragung:  48
I (43313) DHT22: Erstes Datenbit:                  23
I (43313) DHT22: Zweites Low der Datenübertragung: 48
I (43323) DHT22: Zweites Datenbit:                 23
I (45823) DHT22: Schleife durchgelaufen, dauer: 2529838
I (45833) DHT22: HIGH nach Release:                19
I (45833) DHT22: Erstes LOW:                       69
I (45833) DHT22: Erstes HIGH:                      71
I (45833) DHT22: Erstes Low der Datenübertragung:  48
I (45843) DHT22: Erstes Datenbit:                  23
I (45843) DHT22: Zweites Low der Datenübertragung: 48
I (45853) DHT22: Zweites Datenbit:                 23
I (48353) DHT22: Schleife durchgelaufen, dauer: 2529838

Die Messungen zeigen, dass die tatsächlichen Zeiten geringfügig von den typischen Datenblattwerten abweichen. Für die spätere Auswertung werden deshalb nicht feste Zeiten verwendet, sondern Zeitbereiche beziehungsweise Schwellwerte.

Signalphase Datenblatt / typische Angabe Gemessen
Antwort LOW ca. 80 µs 68–69 µs
Antwort HIGH ca. 80 µs 72 µs
LOW vor Datenbit ca. 50 µs 47 µs
HIGH für Bit 0 ca. 26–28 µs 23-24 µs

Die bisherigen Messungen erfassen lediglich die ersten Signalabschnitte des Handshakes sowie das erste und zweite Datenbit. Da der DHT22 an dieser Stelle zufällig eine logische 0 übertragen hat, wurde die HIGH-Phase eines logischen 1-Bits in dieser Messung nicht erfasst.

Zur Bestimmung der Bitzeiten eines logischen 1-Signals wurde deshalb zusätzlich ein Logic Analyzer verwendet. Dort lassen sich die HIGH-Pegel der einzelnen Datenbits direkt messen und mit den Angaben aus dem Datenblatt vergleichen.

low
Abb. 3 – Übertragung eines logischen 0-Bits (HIGH-Phase: 24 µs)
high
Abb. 4 – Übertragung eines logischen 1-Bits (HIGH-Phase: 65 µs)

Die 40 Datenbits auslesen

Schauen wir uns noch einmal Die Datenbits an. Wir erhalten z.B. folgende Bitfolge, direkt vom Sensor:

datenvomdht
Abb. 5 – Daten vom DHT22

00000010 01001010 00000000 11111010 01000110

  • Die ersten zwei Bytes ergeben die Luftfeuchtigkeit: 0000 0010 0100 1010 = 586
  • Die nächsten zwei Bytes ergeben die Temperatur: 0000 0000 1111 1010 = 250

Das letzte Byte ist die Prüfsumme, sie ergibt sich aus der Addition der ersten vier Bytes, nämlich:

0000 0010 + 0100 1010 + 0000 0000 + 1111 1010 = 1 0100 0110 => 0100 0110

Der Übertrag wird abgeschnitten, da der DHT nur 1 Byte als Prüfsumme sendet.

Die Temperatur und die Luftfeuchtigkeit werden mit einer Nachkommastelle gesendet. Somit können wir das Ergebnis wie folgt interpretieren.

  • Temperatur: 25,0 °C
  • Luftfeuchtigkeit: 58,6 %

Negative Temperaturen werden mit einem High des höchstwertigen Bits angezeigt.

1000 0010 0100 1010 wäre demnach -25,0 °C.

Hinweis: Der Schwerpunkt dieses Artikels liegt auf dem Verständnis des DHT22-Protokolls und der Auswertung der übertragenen Datenbits. Der gezeigte Code wurde deshalb bewusst einfach gehalten, um die einzelnen Kommunikationsschritte nachvollziehbar darzustellen.

Eine weiterentwickelte Treiberimplementierung mit strukturierter API und zusätzlicher Fehlerbehandlung wird im zugehörigen GitHub-Repository verfügbar sein.

Im folgenden kommt der Code zum Auslesen des Sensors:

void DhtReadSensor()                                        
{    
    gpio_set_pull_mode(DTH22Data, GPIO_PULLUP_ONLY);
    while(1)
    {    
        uint8_t data[5] = {0};
        uint64_t HauptschleifeStart = esp_timer_get_time();    
        // Initalisierung, Request
        gpio_set_direction(DTH22Data, GPIO_MODE_OUTPUT);
        gpio_set_level(DTH22Data, 1);
        vTaskDelay(10 / portTICK_PERIOD_MS);                
        
        // Startsequenz, Datenleitung auf Low ziehen für mindestens 1 ms
        gpio_set_level(DTH22Data, LOW);
        esp_rom_delay_us(1200);      

        // Leitung frei geben
        gpio_set_direction(DTH22Data, GPIO_MODE_INPUT);                

        // Abwarten des Handshake
        wait_for_level(0, 100);                     
        wait_for_level(1, 100);
        wait_for_level(0, 100);   

        // Erst nach dem Handshake in die Schleife und die 40 Datenbits auswerten
        for (int i = 0; i < 40; i++)
        {   
            // 1. Warten auf Beginn des Bit-HIGHs
            // Jedes Bit startet mit LOW-Phase ca. 50 µs
            if (!wait_for_level(1, 100))
            {
                ESP_LOGE(TAG, "Timeout LOW vor Bit %d", i);
                return;
            }

            int64_t high_start = esp_timer_get_time();

            // 2. Warten bis HIGH wieder endet
            if (!wait_for_level(0, 100))
            {
                ESP_LOGE(TAG, "Timeout HIGH bei Bit %d", i);
                return;
            }

            int64_t high_time = esp_timer_get_time() - high_start;

            // 3. Bitwert bestimmen
            uint8_t bit = (high_time > 40) ? 1 : 0;

            // 4. Bit in Byte schieben
            data[i / 8] <<= 1;
            data[i / 8] |= bit;
        }

        uint8_t checksum = data[0] + data[1] + data[2] + data[3];

        if (checksum != data[4])
        {
            ESP_LOGE(TAG, "Pruefsumme falsch: berechnet 0x%02X, empfangen 0x%02X",
            checksum, data[4]);
            continue;
        }

        uint16_t humidity_raw = (data[0] << 8) | data[1];
        uint16_t temperature_raw = (data[2] << 8) | data[3];

        float humidity = humidity_raw / 10.0f;

        float temperature;
        if (temperature_raw & 0x8000)
        {
            temperature_raw &= 0x7FFF;
            temperature = -(temperature_raw / 10.0f);
        }
        else
        {
            temperature = temperature_raw / 10.0f;
        }
        
        ESP_LOGI(TAG, "Feuchtigkeit High Byte: 0x%02X", data[0]); 
        ESP_LOGI(TAG, "Feuchtigkeit Low Byte: 0x%02X", data[1]); 
        ESP_LOGI(TAG, "Temperatur High Byte: 0x%02X", data[2]); 
        ESP_LOGI(TAG, "Temperatur Low Byte: 0x%02X", data[3]);  
        ESP_LOGI(TAG, "Prüfsumme: 0x%02X", data[4]);
        ESP_LOGI(TAG, "Feuchtigkeit: %.1f %%", humidity);
        ESP_LOGI(TAG, "Temperatur: %.1f °C", temperature);

        vTaskDelay(2500 / portTICK_PERIOD_MS);
        int64_t HauptschleifeDauer = esp_timer_get_time() - HauptschleifeStart;
        ESP_LOGI(TAG, "Schleife durchgelaufen, dauer: %lld", HauptschleifeDauer);
    }   
}

Die Ausgabe im ESP-IDF Monitor sieht wie folgt aus:

I (232973) DHT22: Feuchtigkeit High Byte: 0x02
I (232973) DHT22: Feuchtigkeit Low Byte: 0x02
I (232973) DHT22: Temperatur High Byte: 0x00
I (232973) DHT22: Temperatur Low Byte: 0xF3
I (232983) DHT22: Prüfsumme: 0xF7
I (232983) DHT22: Feuchtigkeit: 51.4 %
I (232983) DHT22: Temperatur: 24.3 °C
I (235493) DHT22: Schleife durchgelaufen, dauer: 2529857
I (235503) DHT22: Feuchtigkeit High Byte: 0x02
I (235503) DHT22: Feuchtigkeit Low Byte: 0x01
I (235503) DHT22: Temperatur High Byte: 0x00
I (235503) DHT22: Temperatur Low Byte: 0xF3
I (235513) DHT22: Prüfsumme: 0xF6
I (235513) DHT22: Feuchtigkeit: 51.3 %
I (235513) DHT22: Temperatur: 24.3 °C
I (238023) DHT22: Schleife durchgelaufen, dauer: 2529853

Die Daten sind:

  • Temperatur: 24,3 °C
  • Luftfeuchtigkeit: 51,3 %

Und die Prüfsumme wird auch korrekt berechnet.

Weiterführende Links

Blog Artikel

Projekte

 

 

Schreibe einen Kommentar