In Anlehnung an das ONE-WIRE-Protokoll von MAXXIM geht es hier um einen Sensor, der Feuchtigkeit und Temperatur in einem Abwasch erfasst und anschließend ebenfalls über eine einzige Leitung zum Controller überträgt.
Die Hardware
Know How - Das One-Wire-Protokoll von AOSONGReset- und Device-Present-Puls
Das Einlesen der 5 Datenbytes
Das neue Hauptprogramm für den ATMEGA88 & Co.
Delay-Routinen refurbished
Das DHT11-Modul besteht aus wenigen Routinen
Abrufen der Werte im Hauptprogramm
DHT11 an ATMega8 und ATMega88
Die erweiterte Bibilothek dht.asm
Im Gegensatz zum DS18B20 von MAXXIM verwendet AOSONG mit dem DHT11 ein eigenes Protokoll. Die Suche nach mehreren Bausteinen am "Bus" entfällt, ebenso deren Adressierung, weil nur ein Sensor an der Leitung liegen kann - der Begriff "Bus" ist deshalb hier etwas irreführend, Datenleitung trifft eher zu.
Der DHT11 ist in einem blauen Kunststoff Gitterkästchen untergebracht und besteht aus mehreren Komponenten. Von den vier Anschlüssen, die am unteren Ende herausgeführt sind, haben drei eine Bedeutung. Der linke Anschluss liegt an +Vcc. Die Spannung kann 3,3V bis 5,5V betragen, das steht auf dem Baustein und auch im Datenblatt von AOSONG. Das Chinenglisch im PDF-File ist allerdings etwas gewöhnungsbedürftig. Der zweite Anschluss ist die bidirektionale Datenleitung. Der dritte Anschluss hat keine Verbindung zur Innerei. den Abschluss bildet der Masseanschluss ganz rechts. Der etwas größere DHT22 besitzt die gleiche Beschaltung, bringt aber im Vergleich zum DHT11 eine höhere Auflösung mit. Die Register und das Protokoll auf dem Bus sind bei beiden Bausteinen die gleichen.
Abb. 1: DHT11 auf Lochaster und ATTiny4313 Testplatine, DHT22
Die Datenleitung wird mit einem 4,7kOhm Widerstand auf Vcc gezogen, damit die Leitung auf High liegt, wenn keiner der Partner sie gegen Masse zieht.
Als Controller habe ich, weil der ATTiny2313 mit seinen 2KB Flash zu mikrig ist, dessen größeren Bruder ATTiny4313 mit der doppelten Kapazität an Flashspeicher getestet und die Biliothek schließlich auch noch auf den ATMega8 und den ATMega88 portiert. Mit geringfügigen Anpassungen lief das Modul auf allen AVRs. Beim ATMega8 wurden außerdem Taktraten von 4MHz bis 16MHz erfolgreich getestet. Im dargestellten Testaufbau läuft der ATTiny4313 mit 8 MHz. Der DHT11 liegt an PortD.7, die LED an PortD.6 und PortD.5 ist der Triggerausgang, dessen Funktion weiter unten erläutert wird. Auf eine eigene Testplatine wurde verzichtet, das wäre den Aufwand nicht wert. Statt dessen habe ich die Platine aus dem Kapitel Bestücken, Löten und Testen wieder ausgegraben, zusammen mit dem RS232-Interface und der alten Spannungsversorgung. Für die Versuche mit den ATMegas diente eine schmale Vorgängerplatine von dem, was später als Arduino bekannt werden sollte.
Das Protokoll von AOSONG für die Datenübertragung unterscheidet sich wie bereits erwähnt von dem ONE-WIRE-Protokoll von MAXXIM grundlegend. Weil es nur ein Device an der Datenleitung gibt, braucht man keine Auswahl treffen. Der ganze Overhead an Vorlauf, wie viele Einheiten gibt es am Bus, welche Kennungen haben diese, wie wählt man ein bestimmtes Device aus... entfällt. Es gibt nur einen Startimpuls, welcher die Sendung der zuletzt gesampelten Werte veranlasst. Beim DHT11 werden rel. Feuchtigkeit und Temperatur mit 8 Bit Genauigkeit erfasst. Der Temperaturabgleich für die Feuchtemesung erfolgt bereits auf dem Chip und kann nicht beeinflusst werden.
Die Datenübtertragung beginnt beim DHT11 mit dem Highbyte der relativen Feuchtigkeit in % und ist ganzzahlig, es folgt das Lowbyte, welches stets den Wert 0 hat. Das Highbyte der Temperatur folgt auch als ganzzahliger Wert gefolgt vom Lowbyte mit dem Wert 0. Addiert man die vier Bytes ohne berücksichtigung des Carry-Bits, dann sollte sich der Wert des fünften Bytes, der Prüfsumme, ergeben.
Der DHT22 misst die Feuchte und die Temperatur auf 0,1% bzw °C. Der 16-Bitwert wird nach dem Einlesen einfach durch 10 dividiert. Der ganzzahlige Anteil steht dann vor dem Komma, der Rest dahinter. Sonst ist das Prozedere bei beiden Bausteinen das gleiche.
Hinweis:
Beim DHT22 werden nicht, wie im chinenglischen Datenblatt angegeben im jeweils
ersten Datenbyte der ganzzahlige und im folgenden der Nachkommaanteil an den
AVR übertragen, sondern beide Werte zusammen bilden als 16-Bit-Zahl jeweils
das Zehnfache von Temperatur und Hygrowert. Weil der DHT11 nur ganzzahlig auflöst,
enthält das erste Byte den Feuchte- oder Temperaturwert, das folgende Byte
ist beim DHT11 stets Null.
Die Tabelle zeigt die Zusammenhänge im Überblick. Bei allen Bytes wird das MSBit zuerst gesendet. Das erleichtert auch das Decodieren aus dem Oszillogramm - schmaler Impuls=0", breiter Impuls="1" (siehe weiter unten).
Baustein
|
Byte 1
|
Byte 2
|
Byte 3
|
Byte 4
|
Byte 5
|
DHT11
|
rel. Feuchte
|
0
|
Temperatur
|
0
|
Prüfsumme
|
DHT22
|
10 RH Highbyte
|
10 RH Lowbyte
|
10 T Highbyte
|
10 T Lowbyte
|
Prüfsumme
|
Die Prüfsumme wird einfach als arithmetische Summe der ersten vier Bytes gebildet, also
Prüfsumme = Byte1+Byte2+Byte3+Byte4
Beim DHT22 gilt
(Byte1:Byte2) = 10 x RH also int(RH) = (Byte1:Byte2)
div 10 und frac(RH) = (Byte1:Byte2) mod 10
(Byte3:Byte4) = 10 x T also int(T)
= (Byte3:Byte4) div 10 und frac(T) = (Byte3:Byte4)
mod 10
Diese Berechnung erledigt die Routine "bdiv10" aus dem Bibliotheksmodul "arithmetik.asm". Dazu muss dem UP der 16-Bitwert in den unteren beiden Bytes des Akkumulators B übergeben werden, das MSByte von AkkuB wird auf 0 gesetzt. Nach dem Funktionsaufruf steht in AkkuB der ganzzahlige Anteil der Division, das LSByte von AkkuA enthält den Zehnerrest.
Mit Hilfe eines DSO kann man die gesendeten Impulse sehr schön sichtbar machen. Das hilft auch enorm bei der Fehlersuche. Bei diesem, wie auch schon in anderen Projekten, habe ich eine zusätzliche Hilfe eingebaut und zielgerichtet verwenden können, einen Triggerausgang am AVR. Das ist einfach ein I/O-Port, der zu kritischen Zeitpunkten im Programmablauf ein- oder ausgeschaltet wird und dadurch den Triggereingang am DSO genau steuern kann. Man kann so die Zeitspanne punktgenau darstellen in der etwas wichtiges passiert oder passieren sollte. Die folgende Abb. zeigt den Rest des Startimpulses, den Antwortimpuls des DHT11 und die ersten beiden Datenbytes 0x28 und 0x00, das entspricht 40% Luftfeuchte. Auf Kanal2 kann man den Triggerimpuls erkennen.
Abb. 2: Start einer Datensequenz vom DHT11
Reset- und Device-Present-Puls
Gehen wir jetzt das Protokoll einmal genau durch. Wie beim One-Wire-Protokoll gibt es auch hier Abweichungen zwischen den Zeitangaben von AOSONG und meinen tatsächlichen Messungen am Objekt. Ich stelle zunächst die Impulsdiagramme vor.
Abb. 3: Impulsdiagramme von AOSONG und real gemessen ("1"- und
"0"-Impulsfolge beispielhaft )
Speziell für diese Aufgabe habe ich meine Bibliothek "delay.asm" überarbeitet und erweitert. Sie ist jetzt nach wie vor abwärtskompatibel, wurde aber durch neue Funktionen ergänzt, alte Routinen wurden aufgefrischt. Die neu ergänzten Verzögerungsroutinen arbeiten unabhängig vom Quarztakt und werden nur durch Parameter aufgerufen, in welchen die Anpassung an die Geschwindigkeit steckt, die Routine selbst muss nicht mehr verändert werden. Diese gesteigerte Performance und Genauigkeit gilt natürlich nur für den AVR-Controller und nicht, wie hier, für den DHT11. Die Zeitabweichungen, die Exemplarstreuung wurde noch nicht untersucht, machten es schwierig ein Programm für verschiedenste Quarzfrequenzen zu entwerfen. Unabhängig davon, mein Startimpuls ist exact 18ms lang, +/- einige Promille interssieren nicht.
Nachdem der AVR den Startschuss gegeben hat, gibt er die Leitung frei, sein Ausgang wird Eingang und der Pullupwiderstand zieht die Leitung auf +5V. Dieser Zustand bleibt hier genau12,80 µs erhalten (Abb.4 links), AOSONG macht hierzu keine Angaben, dann muss der DHT11 die Steuerung übernehmen und die Leitung für ca. 80µs gegen Masse ziehen. Danach wird die Leitung wieder frei gegeben (Abb. 4 Mitte und rechts).
Abb. 4: Die "ich bin da"-Rückmeldung eines DHT11 auf der Datenleitung.
Der Idle-Zustand nach dem Startimpuls ist von AOSONG nicht spezifiziert.
Nun folgen in gleicher Funktionalität aber natürlich nicht zwingend gleicher Signallänge die 40 Datenbits. Mit einem abschließenden Nullpegel nach dem letzten Bit wird die Übertragung beendet. Die einzelnen Bits erscheinen wie folgt auf dem DSO. Man beachte die von den ersten beiden abweichende zeitliche Sklalierung im letzten Bild.
Abb. 5: Die Startimpulse sind für alle Bits gleich 54,40µs, "0"-Bit
der Länge 24,00µs und "1"-Bit der Länge 72,00µs
Durch die unterschiedliche Länge der HIGH-Pegel bei der Bitübertragung
ergeben sich unterschiedlich lange Bitintervalle von 78,4µs und 128,4µs.
Das macht eine Abfrage in zeitlich gleichen Abständen wie bei der Übertragung
nach V24 (RS232) unmöglich. Vielmehr muss man auf Flanken reagieren und
die anfallenden Zeiträume irgendwie messen, nach Möglichkeit auch
für verschiedene Taktgeschwindigkeiten.
Na, das ist ja alles ganz einfach, haben wir gleich!
Denkste!!!
Vor einiger Zeit hatte ich mir mal die Zeit genommen und alle Module, die ich bisher verfasst hatte, nach Controllergruppen zu sortieren und gesammelt abzulegen. Zeitgleich entstand eine neue Vorlage für ein Hauptprogramm, in dem alle bisher ausgelagerten Einstellungen für die AVR-I/O-Module wie Timer, RS232, IRQs usw. gesammelt wurden. Das Teil ist zwar sehr umfangreich, aber auch sehr übersichtlich. Man braucht so nicht mehr die Header der einzelnen Module aufrufen um Anpassungen vorzunehmen. Ausserdem kann jedes Modul die Marker abfragen, die ein anderes beim Einbinden gesetzt hat. Das ganze ist somit transparenter geworden. Dieses Mainprogram ist jetzt die Grundlage aller Anwendungen mit der Familie ATMega48...328. Für die Tinies und die Mega8-16-32-Familie muss die alte Vorlage noch herhalten.
Weil es in diesem Projekt um zeitgenaue Erfassung von Taktflanken geht, hatte ich, bevor ich damit begann, das Modul "delay.asm" mit den Softwareverzögerungen überarbeitet. Die Verzögerungen waren bislang nur für einen Systemtakt von 8MHz brauchbar. Die neu hinzugefügten Routinen werden teilweise direkt mit der Anzahl zu verzögernder µs oder ms aufgerufen oder mit Parametern, die vorab für eine bestimmte Zeit taktabhängig berechnet werden. Das geschieht, durch Formeln bei der Assemblierung oder in EXCEL, wodurch eine noch optimalere Modellierung der Verzögerungszeiten möglich ist. Das entsprechende Tabellenblatt kann man hier herunterladen.
Ausschlaggebend für das Delaymodul war, dass die Vorgänge, die dadurch gesteuert werden sollen, pingelig zeitabhängig sind. Das hat zur Folge, dass Interrupts während der Laufzeit auszuschalten sind. Teilweise machen das die Routinen selbst, sonst muss der Programmierer das tun. Für die Zeitsteuerung scheidet der Einsatz der Timer also aus.
Abb. 6: Ausschnitt auf dem Excel-Blatt zur Optimierung von zeitlichen Verzögerungen
Im Fall der 10µs-Verzögerung kann der Assembler mit Hilfe der Formel für die Anzahl der Schleifen (NumberOfLOOPS= noloops) diesen Wert in Abhängigkeit der Taktfrequenz selbst berechnen und für die meisten gängigen Taktfrequenzen eine exakte Verzögerung von 10µs einstellen. Natürlich kann man manuell durch das Variieren der Rahmenbedingungen (führende NOPs, Sicherung von Registern auf den Stapel) die Abweichung auch für schräge Taktfrequenzen optimieren und den Wert dem Register R16 selbst als Konstante zuweisen.
Die Routinen der Bibliothek sind sehr umfangreich mit Bemerkungen versehen, die alle Einzelheiten erklären.
Trotz dieser zeitraubenden Beschäftigung mit der Zeitverzögerung und der erreichten Genauigkeit, war nur der Einsatz der Millisekundenverzögerung dieses Moduls brauchbar. Der DHT11 arbeitete zwar wie oben beschrieben, aber es war dennoch durch verschiedene, mir bislang noch nicht unter gekommene mysteriöse Umstände, nicht möglich, die Pulsfolge des DHT11 korrekt zu interpretieren. Durch das nun vorgestellte Workaround funktioniert die Anwendung zwar, aber eben unter teilweise nicht erklärlichen Umständen, auf die ich an gegebener Stelle näher eingehen werde.
Listing1: Routinensyntax und Speicherdefinitionen
Weil nicht zwischen diversen Befehlen auf dem DHT11 differenziert werden muss, kommt das Modul mit zwei öffentlichen Routinen aus.
dht_init:
Es wird eine Sekunde gewartet, bis der Baustein gebootet hat, DDR-Register und PORT-Register werden initiiert, ein Startimpuls abgesetzt und die Übertragung der ersten Daten einfach ignoriert.
dht_read:
Nachdem Startimpuls von 18ms Dauer wartet der AVR auf die Antwort vom dht11 und wertet sie aus. Für jeden Teilimpuls gibt es eine Laufzeitüberprüfung. Dauert das Eintreffen des nächsten Ereignisses (Flanke) zu lang, wird die weitere Abarbeitung der Sequenz abgebrochen und die Routine mit einer Fehlermeldung verlassen. Die Nummer des Fehlers ist in R16 enthalten. Die Fehlermeldung besteht in jedem Fall aus einer Folge von Blinkimpulsen der LED, die durch drei weitere interne Routinen gesteuert wird. Der Fehlercode in R16 kann auch vom Hauptprogramm zusätzlich ausgewertet werden. Ist der Fehlercode 0, dann wurden Feuchte- und Temperaturwert korrekt eingelesen und können angezeigt oder weiter verarbeitet werden. Diese Werte stehen im Ergebnisarray im SDRAM an den Adressen dht_HH (Feuchte) und dht_TH (Temperatur) bereit, dht_CSum enthält den Wert der Prüfsumme.
Listing2: Port- und Konstantendefinition
Zu den privaten Deklarationen muss nichts besonderes gesagt werden.
Kommen wir zum Inhalt der Modulroutinen. Zur Initialisierungsroutine muss man auch nicht die Story vom Pferd erzählen, sie ist kurz und bündig und selbsterklärend.
Listing3: Initialisierung
des DHT11
Listing4: Übertragungsvorgang starten
Nach dem Absetzen des Startimpulses lauscht der AVR nur noch auf der Datenleitung gemäß des oben skizzierten Protokolls. Die Leseroutine hat am meisten zu tun, daher ist sie auch "etwas" umfangreicher. Bevor wir die Routine näher ansehen, ein kurzer Blick auf die Fehlerroutinen, weil die im Verlauf der Leseroutine mehrfach aufgerufen werden kann.
Listing5: Die Fehlermeldungen zu den jeweiligen Stationen im Protokoll
Listing6: Umsetzen der Blinksignale
Wird eine Fehlersituation von der Einleseroutine erkannt, dann geht es mit der Fehlernummer zu dht_error. Dort wird decodiert, was zu tun ist, es wird der Blinker aufgerufen und danach geht es zurück zum Ende der Leseroutine. Dort werden die Register restauriert, und fertig.
Jetzt zu den Stationen beim Einlesen der Pulsfolge vom DHT11.
Listing 7: Register sichern, Pointer auf Datenarray setzen, Startimpuls absetzen
und auf Antwort vom DHT11 warten
Als erstes werden die IRQs gesperrt, damit es zu keinen nicht kalkulierbaren Verzögerungen kommen kann. Register sichern kann gegf. unterlassen werden, wenn keine wichtigen Daten darin enthalten sind. Pointer auf Datenbereich setzen und Bitzähler initiieren. Startimpuls, Timeoutzähler rücksetzen, Triggerleitung fürs DSO auf 5V setzen - warten. Das Hauptprogramm sagt mir, nachdem die Routine durchgelaufen ist, Feuchte, Temperatur und die Anzahl der Durchläufe der Warteschleife: 7. Das bedeutet: Wartezeit = 7µs + 7*7 C + 5C =(bei 8MHz) 7µs + 6,125µs + 0,625µs = 13,75µs. Die Datenleitung muss also kurz nach dem 6. Durchgang auf LOW gefallen sein. So weit passt alles. Bei langsamen Quarzfrequenzen unter 8MHz sollte der Aufruf der 1µs Verzögerung herausgenommen werden.
Die Datenleitung führt also jetzt LOW-Pegel, das ist die "ich-bin-da"-Antwort vom DHT11, die nach Datenblatt 80µs dauern sollte, gemessen wurden 83µs und gezählt wurden 43 Durchläufe. Dauer des Lowimpulses = 43 * 7µs + 43 * 7C + 5C = 43µs + 37,625µs + 0,625µs = 81,25µs - passt auch soweit.
Die Datenleitung führt jetzt HIGH-Pegel, was für wiederum 80µs beibehalten werden soll. Auch das geht rechnerisch in etwa auf.
Listing8: Warten auf den LOW-Impuls für das erste Datenbit in ca. 80µs
Am Beginn dieses Abschnitts wird der Zählerstand des Timeoutzählers in R1 umgelagert und dort durch 2 dividiert. Das hat eine besondere Bewandtnis. Für die folgenden 40 Bits braucht man zur Unterscheidung zwischen "0" und "1" eine Zeit, die zwischen 28µs und 70µs liegt. Da aber die Zeit für die 7 Taktzyklen in der Schleife für unterschiedliche Taktfrequenzen erheblich variiert (7C@2MHz = 3,5µs und 7C@20MHz = 0,35µs), ist es nicht möglich, einen festen Vergleichswert für die Schleifendurchläufe zur Unterscheidung zwischen "0"- und "1"-HIGH-Impulsen anzugeben.
Stattdessen nutze ich die bereits gemessene Zeit für den 80µs Präsenzimpuls. Wenn man den Wert des Timeoutzählers für diese rund 80µs halbiert, bekommt man einen Vergleichswert für ca. 40µs. Das reicht, um die wesentlich kürzere Zeit von 24-28µs einerseits und die 70µs andererseits eindeutig detektieren zu können - unabhängig von der Quarzfrequenz. Durch das Weglassen der 1µs-Verzögerung wird die Schleife schneller und kann damit auch die Zeit besser auflösen. Andererseits dauern 255 Durchläufe stets 1785 Zyklen, was selbst bei 20MHz einer Zeit von 89,25µs entspricht und deshalb ohne Probleme auch die lange Zeit von 70µs für eine "1" ohne Timeout problemlos detektiert werden kann. Für niedrigere Frequenzen gibt es ohnehin keine Probleme.
Listing9: Einlesen der 40 Bit = 5 Byte Daten vom DHT11
dht_read6:
Das Einlesen eines Bytes wird vorbereitet.
dht_read_byte00:
Der LOW-Impuls für ein Bit wird registriert und vermessen, wenn es zu lang
dauert, Fehler durch Timeout
dht_read_byte01:
Jetzt kommt der entscheidende Moment. Der HIGH-Impuls wird registriert und vermessen.
Wenn kein Timeout-Fehler passiert, wird durch Vergleich mit dem Inhalt von R1
(=Dauer eines 40µs-Impulses) entschieden ob es eine "0" oder
eine "1" war. Im ersten Fall wird das Carrybit gelöscht, im zweiten
Fall gesetzt und von rechts nach links in das Empfangsbyte in R17 rotiert. Das
geschieht noch weitere 7 mal, dann ist das Byte voll und R21 auf Null heruntergezählt.
Auch R20 wird jedes Mal reduziert und ist nach insgesamt 5 Byte-Durchgängen
ebenfalls auf Null angekommen.
dht_read07:
Über den Z-Pointer wird das Byte in das Array geschrieben und der Pointer
automatisch erhöht. Solange R20 noch nicht auf Null ist, geht's noch einmal
in die Runde:
brne dht_read06. Sind alle Bytes angekommen, wird die Prüfsumme berechnet
und mit der empfangenen Prüfsumme verglichen. Stimmt alles, wird R16 gelöscht
(kein Fehler) und das war's. Ein Fehler wird gemeldet (4 mal schnell blinken),
wenn der Vergleich negativ ausgefallen ist.
Listing10: Das Hauptprogramm sendet die Werte an den PC via RS232
Das Hauptprogramm ist unspektakulär. Hier nicht abgebildet sind das Setzen des Stackpointers und die Initialisierung der seriellen Schnittstelle. Abb. 7 zeigt die durch include eingebundenen Files, die für Zahlenausgabe, Softwareverzögerung, das Auslesen des DHT11, die Triggerbefehle, die serielle Schnittstelle und die Typdefinitionen des ATTiny4313 zuständig sind.
Abb. 7: Diese Dateien wurden als Includes verwendet
rcall dht_read
Der DHT11 wird aufgefordert Daten zu senden
tst r16
Der Inhalt von R16 wird auf =0 untersucht
brne fehler_melden
Feuchte- und Temperaturwert werden verworfen, falls ein Fehler vorliegt, statt
dessen wird die Fehlernummer an den PC gemeldet
Wenn kein Fehler vorliegt, werden Feuchte- und Temperaturwert zum PC geschickt.
Abschließend wird 5 Sekunden gewartet bevor die nächsten Messwerte vom DHT11 abgerufen werden.
So weit funktioniert alles ganz gut. Seltsame Dinge ereigneten sich dann bereits bei der Portierung des Programms auf den ATMega8. Aber der Reihe nach.
Die Hauptschleife wurde schicht und einfach in das Template für den Mega transferiert, das DHT11-Modul hinzugebunden und der Assembler lief ohne Mucken mit 0 Errors durch. Nachdem ich dann auch noch die Taktfrequenz von 8 auf 16MHz im Programm geändert hatte, bekam ich auch Werte im Hyperteminal angezeigt - aber weder Feuchte noch Temperatur sondern FehlerNr. 4 = falsche Prüfsumme. Mist!
Also DSO angeworfen, im Programm den Trigger an das Ende des 18ms Pulses vom AVR gesetzt und die Bitfolge vom DHT11 von Hand decodiert - Werte OK! Am DSO aber nicht im Terminal. Um zu sehen, welche Werte denn überhaupt vom AVR erfasst wurden, habe ich die Verzweigung zur Fehleranzeige im Hauptprogramm gezwickt und dazu noch die übermittelte Prüfsumme und meine selbst berechnete ausgeben lassen. Meine Prüfsumme passte zu den empfangenen Werten, aber nicht zur Prüfsumme vom DHT11. Außerdem waren Feuchte und Temperatur reine Phantasiewerte. Während die von Hand dekodierten Werte durchaus stimmen konnten, waren die vom AVR ermittelten völliger Blödsinn (die relative Luftfeuchte kann nicht größer als 100% sein!).
Man müsste wissen, ob der Mega8 die Flanken richtig erkennt, vielleicht war ja auch die doppelte Taktrate am Misserfolg schuld. Also benutzte ich den Triggerausgang am AVR (PORTD.5) um immer dann, wenn eine Flanke auf der DHT11-Leitung vom Programm festgestellt wurde, auch den Trigger in der gleichen Weise auf HIGH oder LOW zu setzen. Am DSO stimmten die Signale von DHT und Trigger genau überein. Was lief also falsch? - Nun, auf einen Schlag waren auch die an den PC gesendeten Werte völlig im Einklang mit den von Hand dekodierten am DSO.
Das Hinzufügen der triggeran- und triggeraus-Befehle (SBI trigger_port, trigger_bit und CBI trigger_port, trigger_bi) schien die Sache repariert zu haben - aber wie und warum? Nach und nach entfernte ich die Befehle vom Ende her wieder aus dem Modul um festzustellen an welcher Stelle der Fehler lag. Fündig wurde ich gemäß Murphy's Law, ganz am Anfang des DHT11-Moduls, nämlich dort, wo der Startimpuls vom AVR gesendet wurde und das Programm dann den Port von Ausgang auf Eingang umschaltet, um auf den ersten LOW-Puls vom DHT11 zu warten. Entfernte ich auch dort den Triggerbefehl, bekam ich wieder die vogelwilden Werte. Aber es kommt noch dicker.
Listing11: Mysterium zwischen den +++++++++++++++++++++ Zeilen
Die ersten drei Zeilen zwischen den mit +-Zeichen markierten Zeilen tun genau das gleiche wie der unmittelbar nachfolgende cbi-Befehl, den ich anstelle des triggeran-Befehls gesetzt hatte. Lässt man den weg, wird in der Folge nur noch Mist "erkannt". Lässt man statt dessen den cbr-Befehl weg, wird auch nur der gleiche Müll erkannt, obwohl der DHT11 die korrekten Werte sendet und auch das Datenrichtungsregister den korrekten Inhalt aufweist.
Ist das vielleicht ein zeitliches Problem, das den zusätzlichen Conrollertakt erfordert? Denn wird der cbi-Befehl einfach durch ein nop ersetzt, ist auch der Fehler wieder da.
Der Abschuss ist aber die Erkenntnis, dass selbst dann normale Werte angezeigt werden, wenn man ein sbi oder cbi auf einen ganz anderen Port, z. B. PORTB.2 statt dem dht_ddr angibt. Das gleiche Problem trtitt in gleicher Weise und an der gleichen Stelle auch beim Mega88 auf und kann durch das gleiche merkwürdige Workaround beseitigt werden. Darüber hinaus reagieren die Mega8- und Mega88-Version negativ auf die 1µs-Verzögerung in den Warteschleifen. Wird dieses Delay, das ja ausschließlich nop enthält, eingebaut, liest in der Folge der AVR nur noch HIGH-Pegel, was Bytes mit dem Wert 255 ergibt.
Für mich schien für dieses merkwürdige Verhalten zunächtrs keine rationale Lösung in Sicht. Aber manchmal hilft der Zufall ein gutes Stück weiter. Als ich nämlich einen DHT22 in meine Wetterstation statt des bis dahin verwendeten HH10D intgriert habe, hatte ich auch das geschilderte Problem wieder - trotz des zusätzlichen CBI-Befehls. Die Lösung fand ich schließlich wieder unter Verwendung des DSO. Ich wollte sehen, wie lang die einzelnen Phasen beim Einlesen dauern. Und tatsächlich wurde ich dieses Mal gleich am Anfang fündig, nämlich dort, wo der AVR den Bus an den DHT22 übergibt und die Leitung von Ausgang auf Eingang umschaltet. Der DHT22 sollte jetzt nach ca. 12 µs die Leitung auf 0V ziehen. Eben so lang müsste dann auch die Triggerleitung 5V führen. Tat sie aber nicht. Der Triggerpuls war so schmal, dass ich ihn erst angezeigt bakam, als ich die Zeitauflösung am DSO von 20µs/div auf 2µs/div verringert hatte. Und noch etwas konnte ich jetzt sehen, die Datenleitung führte noch annähernd Nullpegel, als der Triggerpuls abfiel. Das war's also. Der AVR war zu schnell. Oder anders herum, der Pullupwiderstand zog die Busleitung zu langsam hoch.
Abb.11.1: Der Triggerimpuls offenbart den Fehler
Das bedeutet, man muss den AVR bremsen, durch ein kurzes Delay. Erst danach kann man in der Schleife darauf warten, dass jetzt der DHT22 den Pegel nach 0V zieht. Und jetzt stimmte auch die Länge des Triggerimpulses mit der am Tiny4313 gemessenen Zeit überein.
Abb.11.2: Die zusätzliche Verzögerung bringt die Lösung
Aber wie hängt das mit dem eigentlich überflüssigen CBI/SBI-Befehl zusammen. Ganz einfach, nach dem Einbau der 3µs-Verzögerung brauchte man den CBI nicht mehr. Bei genauerem Nachdenken fand ich dann auch den Grund, weshalb der CBI-Befehl (auf egal welchen Port!!!) beim Umstieg vom Tiny4313 auf den Mega 8 zumindest meistens die korrekten Werte geliefert hat aber ein NOP nicht. Es gibt sogar zwei Gründe. Erstens, beim Umstieg wurde auf dem Mega8 die doppelte Taktfrequenz verwendet. Zweitens, ein CBI-Befehl braucht 2 Taktzyklen, der NOP nur einen. Das heißt beim Tiny @8MHz dauert ein Taktzyklus 0,125µs, das LDI R19,0 reichte daher offenbar als Verzögerung beim Anstieg des Datenpegels aus, zumal zusammen mit dem Makro "trigger_an", das auch einen SBI-Befehl darstellt. Beim Mega8 @16MHz dauert ein Zyklus nur 0,0625µs, und das war offensichtlich zu kurz. Auch ein zusätzliches NOP alleine reichte noch nicht als Verzögerung. Erst die zwei weiteren Taktzyklen des CBI-Befehls (natürlich egal auf welchen Port auch immer) halfen meistens, aber eben auch nicht in 100% der Fälle.
Gleichzeitig mit der Korrektur der Bibliothek wurde der Funktionlsumfang etwas erweitert. Hier die hinzugefügten Subroutinen.
Die Anzeigeroutinen für ein LCD setzen ein zweizeiliges Display mit jeweils mindestens 16 Zeichen voraus
dht_show_h:
Der Wert der relativen Luftfeuchte wird in einem LCD in der ersten Zeile ab
Position 8 angezeigt.
dht_show_t:
Der Temperaturwert wird am Beginn der zweiten Zeile angezeigt. Dafür wurde
für das selbstdefinierbare Zeichen 0x00 das Gradsymbol definiert.
Damit die via RS232 übermittelten Werte auch von einem PC/Server einfach verarbeitet werden können, wird statt dem Dezimalkomma ein Punkt gesendet und auf die angehängten Einheiten verzichtet.
dht_send_h_dezimal:
Der dezimale Wert der Feuchtigkeit wird via RS232 übertragen.
dht_send_t_dezimal:
dto. der Tempraturwert.
dht_send_h_roh:
Die Rohwerte der Luftfeuchte werden als Binärwert mit dem Präfix hr
via RS232 übertragen.
dht_send_t_roh:
Die Rohwerte der Temperatur werden als Binärwert mit dem Präfix tr
via RS232 übertragen.
Da der DHT22 auch negative Temperaturen bis -40°C messen kann, wurde als Vorzeichenspeicher die Variable DHT_sign zu den Variablen DHT_int und DHT_frac hinzugefügt. Zum nachträglichen einfachen Abfangen von Transferfehlern durch das Hauptprogramm wurde die Variable DHT_error_flag eingeführt. Die Variablendefinition wurde in die Datei dht_h.asm ausgelagert, die ursprüngliche Datei von dht11.asm in dht.asm umgetauft. In der Headerdatei werden auch die Konstanten DHT_used und DHT_typ deklariert. Erstere Festlegung teilt anderen Programmteilen die Verwendung der Bibliothek dht.asm mit, die andere regelt die leicht unterschiedliche Behandlung der Ergebnisse von DHT11 und DHT22.
Hier wie immer alle Downloadangebote zum Projekt in der Übersicht.
Sourcecodes des DHT11-Projekt für den ATTiny4313, den ATMega8 und den ATMega88
HEX-Dtaeien des DHT11-Projekt für den ATTiny4313@8MHz, den ATMega8@16MHz und den ATMega88@16MHz
EXCEL-Mappe zur Delay-Berechnung
Die erweiterte Bibliothek für DHT11 und DHT22