Diese Seite ist Teil des Projekts Automatische Gewächshauslüftung.
Das folgende Testprogramm benutzt Interrupts zur Kontrolle des Einlesevorgangs. Im diesem Programm ist der Programm-Rahmen (main.cpp) für einen Arduino geschrieben. Das vereinfacht die Datenausgebe ein wenig, da das Objekt Serial hierzu zur Verfügung steht. Das Einlesen der Daten erfolgt jedoch über ein Standard-C-Programm (DHT11.c), das bei der Gewächshaus-Lüftung verwendet werden soll. Die allen zugänglichen Funktionen und Variablen werden in DHT11.h veröffentlicht.
Das zugehörige Atmel Studio-Projekt zum Download.
Zunächst eine Übersicht zum Vorgang des Datenauslesens. Das Messprinzip ist relativ einfach: In der Funktion Dht11BeginRead() wird der Messvorgang eingeleitet, d.h. das Startsignal ausgegeben und auf die sichere Übernahme der Datenleitung durch den DHT11 gewartet. Danach werden die Zeit zwischen zwei fallenden Flanken über den Input Capture-Mechanismus des Timer1 detektiert. Ist die Zeitdauer ca. 70 µs wird eine 0 erkannt, ist sie etwa 120 µs liegt eine 1 vor. Die Bereitschaftsmeldung des DHT11 wird ausgeblendet. Ein Timeout, d.h. Aussetzer bei den fallenden Flanken, wird per Output Compare A Match erkannt. Während des Messvorgangs wird der aktuelle Status des Vorgangs in der Variablen Dht11Valid präsentiert. Das Messergebnis nach einem erfolgreichen Auslesen wird in den Variablen Temperature und Humidity abgelegt.
DHT11.h:
#ifdef __cplusplus extern "C"{ #endif extern volatile uint8_t Temperature; // Letzter Messwert Temperatur extern volatile uint8_t Humidity; // Letzter Messwert Luftfeuchtigkeit extern volatile int8_t Dht11Valid; // Status des Messvorgangs // 0: Messung aktiv; // 1: Gültige Messung; // -1: Timeout; // -2: Zeitfehler, Bit-Zeit zu klein; // -3: Zeitfehler, Bit-Zeit zu groß; // -4: Prüfsumme stimmt nicht void Dht11BeginRead(); // Startet eine Messung #ifdef __cplusplus } // extern "C" #endif
Die Einfassung der Deklaration in einen extern "C"-Block stellt sicher, dass der CPP-Compiler des Hauptprogramms die korrekte Namenskonventionen benutzt. Die Messwert-Routine ist in C geschrieben.
Dht11Valid liefert Informationen über den Status des aktuellen bzw. letzten Messvorgang.
In den Fehlerfällen -1 .. -4 sind die Werte in den Variablen Temperature und Humidity ungültig. Sie enthalten den Wert der letzten gültigen Datenübertragung. Nach einem Fehler empfiehlt es sich, mindestens 10 ms bis zur nächsten Datenübertragung zu warten, damit der DHT11 eine unvollständige Übertragung beenden kann.
Der Prescaler des Timer ist auf 64 eingestellt. Bei einer Taktfrequenz von 16 MHz ist die Taktdauer damit 4 µs. Dies ist zum einem kurz genug, dass die einzelnen Periodenzeiten selbst einer erheblichen Ungenauigkeit noch sauber voneinander getrennt werden können. Zum anderen sind alle Zeiten deutlich kleiner als 256 Takte und können in einem Byte abgelegt werden.
// Counter-Periode = 4µs = Taktdauer // Bei Beobachtung der fallenden Flanke // Start: 160µs (80µs + 80µs) = 40 Takte (wird ausgeblendet) // 0-Bit: 80µs (50µs + 28µs) ca. 20 Takte (BIT_MIN .. BIT_0_1) // 1 Bit: 120µs (50µs + 70µs) = 30 Takte (BIT_0_1 .. BIT_MAX) // Overflow: 60 Takte #define BIT_MIN 15 // Bit-Zeiten mit kleineren Taktzahlen als BIT_MIN, sind zu kurz => Fehler #define BIT_0_1 25 // Bit-Zeiten mit kleineren Taktzahlen als BIT_0_1 stellen 0 dar, längere 1 #define BIT_MAX 35 // Bit-Zeiten mit größeren Taktzahlen als BIT_MAX, sind zu lang => Fehler #define TIMEOUT 60 // Taktzahl, nach deren Verstreichen ohne erneute fallende Flanke auf Timeout erkannt wird
// Öffentliche Variablen volatile uint8_t Temperature; // Ergebnis der Temperaturmessung volatile uint8_t Humidity; // Ergebnis der Feuchtemessung volatile int8_t Dht11Valid; // Status des Messvorgangs // 0: Messung aktiv; // 1: Gültige Messung; // -1: Timeout; // -2: Zeitfehler, Bit-Zeit zu klein; // -3: Zeitfehler, Bit-Zeit zu groß; // -4: Prüfsumme stimmt nicht // Interne Variablen static uint16_t FallingEdge; // Zählerstand bei vorhergehender Flanke static int8_t BitCount; // Nummer des Bits, das aktuell eingelesen wird (0-basiert) static uint8_t Data[5]; // Speicher für die 5 zu übertragenden Bytes
#define PORT_IC PORTB // Port des benutzen ICP #define DDR_IC DDRB // zugeh. DDR #define P_IC (1 << PB0) // Bitmaske für ICP // Datenübertragung vom DHT11 einleiten void Dht11BeginRead() { Dht11Valid = 0; // Status: Messvorgang gestartet BitCount = -1; // -1: Erste Flanke is kein gültiges Bit, sondern Bereischaftsmeldung // Datenleitung für ca. 20 ms auf low legen DDR_IC |= P_IC; // P_IC als Output konfigurieren PORT_IC &= ~P_IC; // Ausgang auf Low _delay_ms(20); // 20 ms dauerndes Startsignal PORT_IC |= P_IC; // Ausgang auf High _delay_us(40); // dem DHT11 die Kontrolle der Datenleitung übergeben DDR_IC &= ~P_IC; // P_IC als Input konfigurieren _delay_us(40); // sicherstellen, dass der DHT11 die Kontrolle übernommen hat // Timer1 konfigurieren & aktivieren TCCR1A = 0; // Arduino modifiziert den Timer, deshalb komplett bestücken TCCR1C = 0; TCCR1B = (1<<CS11) | (1 << CS10) | (0 << ICES1); // Prescaler ist 64 => Counter-Takt = 4µs // Input Capture mit fallender Flanke OCR1A = TIMEOUT; // OCRA für Timeout-Bestimmung vorbereiten TCNT1 = 0; // Zähler zurücksetzen TIFR1 = (1<<ICF1) |(1<<OCF1A); // Input Capture Flag & Output Compare A Match Flag löschen TIMSK1 |= (1<<ICIE1)|(1<<OCIE1A); // Input Capture Interrupt (ICP) & Output Compare A Match Interrupt freigeben }
Die fallenden Flanken lösen einen Input Capture Interrupt aus. Je nach Zeitdauer zwischen zwei Flanke wird auf ein 0-Bit, ein 1-Bit oder auf Fehler erkannt. In BitCount wird die lfd. Nummer des aktuellen Bits mitgezählt (beginnend mit 0). BitCount wird mit -1 vorbelegt. Der erste Interrupt wird nämlich durch die fallende Flanke der Bereitschaftsmeldung ausgelöst.
Zum Schluss wird die Prüfsumme getestet und das Ergebnis zurückgemeldet.
Damit sich das Programm nicht aufhängt, wenn bei der Übertagung etwas schiefgeht, ist ein Timeout-Mechanismus eingebaut. Hierzu wird wegen der einstellbaren der Output Compare A Match benutzt. Die Zeitschwelle wird beim jeweils beim Eintreffen einer fallenden Flanke vorgestellt.
// ISR für den Input Capture Interrupt ISR(TIMER1_CAPT_vect) { uint16_t ICR = ICR1; // Zwischenspeicher vermeidet wiederholte Zugriffe auf das Register uint8_t time = ICR - FallingEdge; // Zeit messen FallingEdge = ICR; // Neue Startzeit OCR1A = ICR + TIMEOUT; // Timeout-Erkennung anpassen, TIMEOUT-Takte bis zur nächsten Flanke // Bereitschaftsmeldung ignorieren if (BitCount < 0) { BitCount = 0; return; } // Zeit auswerten if (time < BIT_MIN) // Periode zu kurz { Dht11Valid = -2; TIMSK1 &= ~((1<<ICIE1)|(1<<OCIE1A)); // Fehler: Interrupts sperren return; } if (time > BIT_MAX) // Periode zu lang { Dht11Valid = -3; TIMSK1 &= ~((1<<ICIE1)|(1<<OCIE1A)); // Fehler: Interrupts sperren; return; } // Die eingelesenen Bits speichern Data[BitCount/8] <<= 1; // Bereits eingelesene Bits nach links schieben if (time > BIT_0_1) // 0 oder 1? Data[BitCount/8] |= 1; BitCount++; if (BitCount == 40) // alle Bits eingelesen { if (Data[4] != ((Data[0] + Data[1] + Data[2] + Data[3]) & 0xFF)) // stimmt die Prüfsumme? { Dht11Valid = -4; } else { Humidity = Data[0]; // Ausgabe-Werte speichern Temperature = Data[2]; Dht11Valid = 1; // Ergebnis ist gültig } TIMSK1 &= ~((1<<ICIE1)|(1<<OCIE1A)); // Fertig: Interrupts sperren } } // ISR für den Output Compare A Match Interrupt // Dies bedeutet Timeout ISR(TIMER1_COMPA_vect) { TIMSK1 &= ~((1<<ICIE1)|(1<<OCIE1A)); // Interrupts sperren Dht11Valid = -1; // Ergebnis ist Timeout }
#include "Arduino.h" #include "AppVersion.h" #include "DHT11.h" // the setup routine runs once when you press reset: void setup() { Serial.begin(9600); Serial.println(F("DHT11 Test, Version " VERSIONSTRING)); } // the loop routine runs over and over again forever: void loop() { Dht11BeginRead(); // Mesuung starten while(Dht11Valid == 0); // Warten, bis die Messung abgeschlossen ist Serial.println(F("----------")); // Ergebnis ausgeben if (Dht11Valid == 1) { Serial.print(F("Hum: ")); Serial.print(Humidity); Serial.print(F(" Temp: ")); Serial.println(Temperature); } else { Serial.print(F("Invalid: ")); Serial.println(Dht11Valid); } delay(2000); }
Der Trick mit AppVersion ist übrigens hier erklärt: Build-Nummer: Immer Eins drauf!