Hallo ich habe ein Problem mit der Tastenentprellung? eigentlich wollte ich nur die Tastenentprellung des AVR-Tutorials auf mein Programm anwenden. Leider klappt das nicht so ganz: Die Entprellung aus dem Tutorial ist in der Datei "tasterentprellung.asm" zu finden die andere Datei stellt die Hauptschleife meines Programmes dar. in dem Programm wird jede Sekunde ein Timer1On COmpare Interrupt ausgelöst. Ich hatte gedacht, dass ich statt der Wartezeit einfach meine Ausgabe-Befehle an das LCD einmal aufrufe, dann sollte doch genügend Zeit vergangen sein um den "TASTER_PORT" erneut einzulesen? Leider gibt das Programm den Text, den ich ganz unten angegeben habe permanent aus (egal ob eine Taste gedrückt wurde oder nicht?)
Ok falls es hilft hier das komplette Programm.
Olli R. schrieb: > Ich hatte gedacht, dass ich statt der Wartezeit einfach meine > Ausgabe-Befehle an das LCD einmal aufrufe, dann sollte doch genügend > Zeit vergangen sein um den "TASTER_PORT" erneut einzulesen? Grrr. Zeiten macht man nicht mit Vermutungen, sondern durch Wissen. Entweder die Delay-Macros des Compilers benutzen oder mit nem Timer. Mit Vermuten ist ein Programm schon im Ansatz falsch. Peter
Hallo Olli, ich nutze eine andere Tasterentprellung, ich glaube die von Peter Dannegger. Du solltest die Tasterenprellung als Unterprogramm einbinden (.include...), so mache ich es zumindest. Dann behält man leichter den Überblick und debuggen wird einfacher. Ich habe dir mal die Datei angehängt. Wenn ich also eine Taste abfragen will, rufe ich das Unterprogramm Get_DigiTasten auf. Je nach Tastendruck wird dann eine Zahl von 1-5 in die Variable "zeichen" geschrieben. Wenn keine Taste gedrückt wurde steht der Wert 0x00 in der Variable. Im Hauptpgramm entscheide ich dann was bei welcher Zahl gemacht werden soll, ähnlich einer Case-Anweisung Das Unterprogramm nutzt den Timer 2, außerdem müssen die Interrupts freigegeben werden. Die Definitiopn ist auskommentiert, weil ich mir diese immer in Hauptprogramm kopiere. Ich hoffe das hilft dir weiter, Gruß, Jürgen
Jürgen schrieb: > Das Unterprogramm nutzt den Timer 2, außerdem müssen die Interrupts > freigegeben werden. Die Definitiopn ist auskommentiert, weil ich mir > diese immer in Hauptprogramm kopiere. > > Ich hoffe das hilft dir weiter, Ja schon aber deine Lösung arbeitet ja auch mit nem Timer! Da fand ich fürs Erste die Lösung von Peter Dannegger besser. Dein Sache werde ich mir auch nochmal zu Gemüte führen. Ich hab nun erstmal die Version "Tastenentprellung und Abfrage" von Peter Dannegger in mein Programm eingearbeitet. Das Programm mit diesem "get8key" (erstmal vorläufig morgen wird das dann noch übersichtlicher gestaltet) Peter Dannegger schrieb: > Grrr. > > Zeiten macht man nicht mit Vermutungen, sondern durch Wissen. > Entweder die Delay-Macros des Compilers benutzen oder mit nem Timer. > Mit Vermuten ist ein Programm schon im Ansatz falsch. Ok das war wirklich ein Schnellschuß. Aber ich wollte unbedingt vermeiden, deine Interrupt-Lösung mit dem get8key zu verwenden. Jetzt verwende ich es doch und es scheint sehr gut zu funktionieren. Eine Frage dazu habe ich allerdings noch: Die Tastenentprellung verwendet ja den Timer0 und die zugehörige ISR wird alle 10ms aufgerufen! Ich habe in meinem Programm aber noch einen zweiten Interrupt laufen! Einen Interrupt On-Compare des Timers1. Dieser Interrupt wird einmal pro Sekunde ausgelöst! Inwieweit beeinflusst denn nun mein Overflow-Interrupt an Timer0 meinen On-Compare-Interrupt an Timer1? der Overflow-Timer0-Interrupt sperrt ja für die Dauer seines Aufrufs alle Interrupts? Würde dann mein Interrupt-On-Compare seltener auftreten? -->alle 10ms Overflow-Interrupt --> alle 1000ms On-Compare-Interrupt
dort steht drin, dass kein Interrupt verloren geht, die ISRs werden nacheinander ausgeführt. Deswegen die ISRs so kurz wie möglich halten. http://www.mikrocontroller.net/articles/Interrupt
> Die Tastenentprellung verwendet ja den Timer0 und die zugehörige ISR > wird alle 10ms aufgerufen! Der Tastenentprellung ist es völlig egal, welcher Interrupt das Timing bereitstellt. Wichtig ist nur, dass die (Hintergrund-)Entprellroutine im festen Zeitraster von etwa 5 bis 30 ms aufgerufen wird. > Ich habe in meinem Programm aber noch einen zweiten Interrupt laufen! > Einen Interrupt On-Compare des Timers1. Dieser Interrupt wird einmal pro > Sekunde ausgelöst! Den 1s-Takt kannst Du aus dem Entprell-Takt ableiten. Wenn Dein Entprell-Takt (per Compare-Interrupt) alle 10 ms erfolgt, dann kannst Du darin neben der Tastenentprellung noch die "Hundertstelsekunden" hochzählen und bei Erreichen von 100 auf 0 setzen und die Sekunde hochzählen. So hast Du nur einen Timer-Interrupt für mehrere Dinge genutzt. Angenommen, es käme noch ein Drehgeber (manuell betätigt) dazu, dann würde man den Interrupt auf 1 ms legen, darin den Drehgeber auswerten, über einen (Software-)Teiler (Zählregister) jede 10. Runde die Tasten entprellen und über einen weiteren Teiler jede 100. Tastenrunde (1000. Interrupt-Runde) die Uhr hochzählen. Und wenn die Uhr noch wecken soll und das Wecksignal per Count-Down deaktiviert werden soll, dann kann dieser Count-Down auch noch durch diesen Interrupt synchronisiert werden. Sollte das Wecksignal (Piepsen) "zerhackt" (gepulst) werden müssen, so kann man dessen "Blinktakt" auch noch aus diesem Interrupt ableiten. ...
Hannes Lux schrieb: > Den 1s-Takt kannst Du aus dem Entprell-Takt ableiten. Wenn Dein > Entprell-Takt (per Compare-Interrupt) alle 10 ms erfolgt, dann kannst Du > darin neben der Tastenentprellung noch die "Hundertstelsekunden" > hochzählen und bei Erreichen von 100 auf 0 setzen und die Sekunde > hochzählen. So hast Du nur einen Timer-Interrupt für mehrere Dinge > genutzt. > > Angenommen, es käme noch ein Drehgeber (manuell betätigt) dazu, dann > würde man den Interrupt auf 1 ms legen, darin den Drehgeber auswerten, > über einen (Software-)Teiler (Zählregister) jede 10. Runde die Tasten > entprellen und über einen weiteren Teiler jede 100. Tastenrunde (1000. > Interrupt-Runde) die Uhr hochzählen. > > Und wenn die Uhr noch wecken soll und das Wecksignal per Count-Down > deaktiviert werden soll, dann kann dieser Count-Down auch noch durch > diesen Interrupt synchronisiert werden. Sollte das Wecksignal (Piepsen) > "zerhackt" (gepulst) werden müssen, so kann man dessen "Blinktakt" auch > noch aus diesem Interrupt ableiten. Ok Viele Dank Hannes, also ok so langsam bekomme ich eine Ahnung, wie man möglichst geschickt programmiert! Um nochmal ganz kurz auf den Timer1-On_compare-Interrupt einzugehen: wenn dieser Interrupt ausgelöst wurde und die ISR gerade abgearbeitet wird, wird dann trotzdessen, dass Interrupts gerade gesperrt sind der Timer1 hochgezählt (also schon begonnen, sich dem Verlgeichswert, bei dem der Interrupt ausgelöst wird, zu nähern?), so dass ich keine Verzögerung um die Abarbeitszeit der Befehle innerhalb der ISR habe?
> wenn dieser Interrupt ausgelöst wurde und die ISR gerade abgearbeitet > wird, Dann hast Du beim Timing was Grundlegendes falsch gemacht. Wenn der Compare-Interrupt erneut zuschlägt, ehe die ISR fertig ist, dann dauert die ISR zu lange. > wird dann trotzdessen, dass Interrupts gerade gesperrt sind der > Timer1 hochgezählt (also schon begonnen, sich dem Verlgeichswert, bei > dem der Interrupt ausgelöst wird, zu nähern?), so dass ich keine > Verzögerung um die Abarbeitszeit der Befehle innerhalb der ISR habe? Diese Betrachtungen sind erst erforderlich, wenn ein Programm mehrere Interrupts hat. Deshalb legt man ja möglichst alle zeitabhängigen Dinge auf einen Timer-Interrupt zusammen. Das geht zwar nicht immer, aber hier (10 ms, 1 s) bietet es sich förmlich an. Sind mehrere Interrupts nötig, dann muss man darauf achten, dass alle ISRs so kurz (so schnell) wie möglich sind. Denn je schneller eine ISR fertig ist, desto weniger müssen andere Interrupts auf ihre Abarbeitung warten. Noch kurz zum Thema "gesperrter Interrupt". Ein Interrupt wird durch ein Ereignis ausgelöst. Das Ereignis kann z.B. sein: Timer-Überlauf, Timer Vergleich (Compare), Pegelwechsel am externen Interrupt-Pin, Pegelwechsel am Pin-Cange-Port, Zeichenempfang an U(S)ART, TWI oder SPI, ..., siehe Interrupt-Vektorliste des jeweiligen AVRs. Tritt das Ereignis ein, so wird das Interrupt-Flag (Pending-Flag) dieses Ereignisses gesetzt. Ist dieser Interrupt durch Setzen des Interrupt-Enabled-Flags freigegeben, dann wird zur ISR gesprungen. Dies erfolgt aber nur, wenn auch das I-Flag im SREG gesetzt ist. Bei jedem Sprung in eine ISR wird das I-Flag gelöscht, denn "es kann nur Einen geben". Durch RETI am Ende des ISR wird das I-Flag wieder gesetzt. Das Interrupt-Pending-Flag wird bei den meisten Interrupts (aber nicht bei allen!) durch den Aufruf der ISR gelöscht (genaue Hinweise gibts im Datenblatt). Wenn während einer ISR ein anderer Interrupt ausgelöst wird, so wird dieser nach Beenden der aktuell laufenden ISR ausgeführt, allerdings arbeitet der AVR zwischendurch noch einen Befehl des Hauptprogramms ab. Um diese Verzögerungen klein zu halten, macht man die ISRs so kurz (schnell) wie möglich. Dazu macht man in der ISR nur das Allernötigste (Daten sichern) und setzt ein Flag (eigene Bitvariable) als "Jobauftrag" für die Hauptschleife, die dann die Daten aufarbeitet. Wie das genau geschieht, ist stark vom Einzelfall abhängig. Ich habe z.B. die Dannegger-Bulletproof-Entprellung mal in der ISR und mal im Hauptprogramm laufen, je nachdem, was das Programm sonst noch so machen muss. ...
Hannes Lux schrieb: > Tritt das Ereignis ein, so wird das Interrupt-Flag (Pending-Flag) dieses > Ereignisses gesetzt. Ist dieser Interrupt durch Setzen des > Interrupt-Enabled-Flags freigegeben, dann wird zur ISR gesprungen. Dies > erfolgt aber nur, wenn auch das I-Flag im SREG gesetzt ist. Bei jedem > Sprung in eine ISR wird das I-Flag gelöscht, denn "es kann nur Einen > geben". Durch RETI am Ende des ISR wird das I-Flag wieder gesetzt. Das > Interrupt-Pending-Flag wird bei den meisten Interrupts (aber nicht bei > allen!) durch den Aufruf der ISR gelöscht (genaue Hinweise gibts im > Datenblatt). Wenn während einer ISR ein anderer Interrupt ausgelöst > wird, so wird dieser nach Beenden der aktuell laufenden ISR ausgeführt, > allerdings arbeitet der AVR zwischendurch noch einen Befehl des > Hauptprogramms ab. Ok also ist in etwa so, wie ich es versucht habe zu erklären: z.B. mein On-Compare-Timer1 löst z.B. bei einem Zählerstand von 10000 einen Interrupt aus, dann wird in die ISR gesprungen, bei Verlassen der ISR hat mein Zähler z.B. den Zählerstand "50" erreicht und wird während der "Main weiter bis 10000 erhöht? Also beginnt mein Zähler nicht erst wieder bis zum Vergleichswert zu zählen, wenn der vorherige ISR-Durchlauf vollzogen wurde!
Der Timer zählt (über den Vorteiler) die CPU-Takte (eigentlich die I/O-Takte, siehe Datenblatt, Kapitel zur Takterzeugung). Das macht er, solange der Vorteiler im Control-Register eingestellt ist. Dabei ist es egal, ob der AVR dabei eine ISR abarbeitet, oder die Mainloop, oder ein Unterprogramm oder ob sich der AVR im Sleep (Sleep-Mode Idle) befindet. Der Timer zählt die Takte, solange man ihm den Takt nicht wegnimmt (siehe Kapitel Timer und Kapitel Sleep im Datenblatt). Ich verweise deshalb auf das Datenblatt, da die Timer der AVRs unterschidlich ausgestattet sind. Du benutzt einen Compare-Interrupt. Da gibt es auch wieder unterschiedliche Möglichkeiten. Eine Möglichkeit ist der CTC-Modus des Timers. Bei ihm wird der Timer beim Erreichen des Compare-Wertes automatisch gelöscht. Damit erreicht man höchste Konstanz, auch wenn die ISR mal wegen eines anderen Interrupts etwas warten muss. Die Wartezeiten addieren sich nicht. Manchmal braucht man aber mehrere Features des Timers und ist nicht bereit, auf die zweite Compare-Einheit und/oder die ICP-Einheit zu verzichten. Dann darf man den Timer nicht löschen oder anderweitig manipulieren, dann muss man ihn frei durchlaufen lassen. Man könnte nun in der Compare-ISR den aktuellen Timerstand nehmen, das gewünschte Intervall (Zeitabstand biszum nächsten Interrupt) aufaddieren und als nächsten Interrupt-Termin (Compare-Wert) setzen. Das wird aber ungenau, da der Timer während des Aufrufs der ISR weiter läuft. Genau wird es, wenn man das Compare-Register (das je eine feste Zahl enthält, während die Zahl im Timer-Register sich ständig verändert) einliest, das Intervall draufaddiert und ins Compare-Register zurückschreibt. Dann kann zwar gelegentlich der einzelne ISR-Aufruf etwas verzögert werden, die Fehler addieren sich aber nicht, da man ja das Intervall auf den Termin des aktuellen Interrupts aufaddiert und als Termin des nächsten Interrupts setzt. Das war zwar etwas abschweifend, ich hoffe aber, es hilft trotzdem beim Verständnis der Timer. ...
Jo Vielen Dank Hannes, ich bin jetzt allerdings auf ein weiteres Problem gestoßen: und zwar wird der µC, immer wenn ich die Taste an PD0 drücke, resetet? ich bin schon seit Stunden dabei, den Fehler zu finden. Bislang ohne Erfolg. also ich habe ja den PORTD als Eingang und die Internen PullUp-Widerstände aktiviert!
Ich hatte die Asm-datei vergessen.
Hi >ich bin jetzt allerdings auf ein weiteres Problem gestoßen: >und zwar wird der µC, immer wenn ich die Taste an PD0 drücke, resetet? > brne stell_wechsel ; Subroutine stell_wechsel wird aufgerufen >stell_wechsel: > dec stellen_wechsel > brne stell_wechsel_1 > ldi temp1, 7 > mov stellen_wechsel, temp1 >stell_wechsel_1: > swap stellen_wechsel > ret Fällt dir etwas auf? MfG Spess
Spess53 schrieb: > Fällt dir etwas auf? Ne leider nicht aber ich beschreib das mal: also stellen_wechsel wird mit dem Wert 8 initialisiert! >stell_wechsel: > dec stellen_wechsel dann wird stellen_wechsel dekrementiert und hat den Wert 7 wenn ich beim Dekrementieren auf 0 "gestoßen" bin wird über das Hilfsregister "temp1" > brne stell_wechsel_1 > ldi temp1, 7 stellen_wechsel wieder mit dem Wert 7 geladen! zum Schluß werden die Nibbles miteinander getauscht > swap stellen_wechsel > ret die Zahl 7 oder 6 oder... 1 steht dann im HIGH-Byte das High-byte wird dann mit der Taste 2 als dem Wert von PD1 verknüpft (addiert). > add temp2, stellen_wechsel > cpi temp2, 114 > breq sekunden_stellen Also der Wert 7 ins High-Byte gepackt (112) und Taste 2 an PD1 gedrückt (2) ergibt zusammen 114 Wenn der Wert 114 erreicht ist sollen z.B. die Sekunden zurückgesetzt werden! So hatte ich mir das gedacht!?
Hi Du springst aus deinem Loop die Routine an und versuchst mit 'ret' wieder zurüchzukommen. Das führt zum Absturz. Ich habe vor kurzem für jemand anderes meine Routinen zum Thema Datum/Uhrzeit angehängt. Da sind auch solche Sachen, wie Schaltjahrberechnung, Feiertage dabei. Beitrag "Re: Erste geh versuche mit AVR bitte mal anschauen" MfG Spess
Ja Danke für den Hinweis. Also deine Routine ist ja schon ziemlich umfangreich im Vergleich zu meinem noch recht unübersichtlichen Gehversuchen. Aber alles zu seiner Zeit! Ich hab gerade gemerkt, dass ich das etwas merkwürdig programmiert habe: zuerst initialisiere ich meine Zählvariable für die Größe, die gerade verstellt werden darf > mov stellen_wechsel, temp1 dann werden die Nibbles vertauscht > swap stellen_wechsel > loop: > cli > mov temp1, key_press > mov temp2, key_press > clr key_press > sei > andi temp1, 1 dann wird Taste 1 abgefragt! > breq loop_1 Ist diese Taste gedrückt wird das hier ausgeführt: > stell_wechsel: hier wird jetzt wieder "umgeswapt", weil ich hier nur die Zähvariable inkrementieren will. Alternativ könnte ich auch statt des "swap" und inc um 16 erhöhen > swap stellen_wechsel > dec stellen_wechsel > brne stell_wechsel_1 > ldi temp1, 7 > mov stellen_wechsel, temp1 > stell_wechsel_1: > swap stellen_wechsel Dann wird die Zählvariable ins High-Byte gepackt! > loop_1: > andi temp2, 2 > add temp2, stellen_wechsel dann wird die Zählvariable "stellen_wechsel mit dem aktuellen Zustand von Taster2 verknüpft (addiert) > mov temp1, temp2 dann Frage ich meine Bedingungen ab, dazu muss der ursprüngliche Inhalt erhalten bleiben > cpi temp2, 114 114 entspricht Zählerstellung von stellen_wechsel = 7 > brne loop_2 > rcall sekunden_stellen > loop_2: > mov temp2, temp1 > cpi temp2, 98 98 entspricht Zählerstellung von stellen_wechsel = 6 > brne loop_3 > rcall minuten_stellen Aber bis ich herausgefunden hatte, dass ich an der Markierung "stell_wechsel: gleich ein swap stellen_wechsel einfügen musste und dann natürlich gleich auch bei der Initialisierung swappen muss, verging auch schon eine Weile. Na egal dann werde ich das heute oder besser "demnächst" morgen oder so mal etwas übersichtlicher gestalten, auch wenn ich dann immer noch weit von deiner Uhren-Routine entfernt bin ;) Mein Programm ist der VW Jetta und deins ein Porsche! Spess53 schrieb: > Ich habe vor kurzem für jemand anderes meine Routinen zum Thema > Datum/Uhrzeit angehängt. Da sind auch solche Sachen, wie > Schaltjahrberechnung, Feiertage dabei. Zu den Feirtagen ich hab das mal überflogen: Ich gehöre zu den Glücklichen, die wohl keine zusätzlichen Feiertage haben. --> Mein Bundesland war nicht dabei;)
Hi >Na egal dann werde ich das heute oder besser "demnächst" morgen oder so >mal etwas übersichtlicher gestalten,... Vielleicht solltest du dir mal überlegen, ob du nicht bestimmte Sachen von den Registern in den Ram verlagerst. Abgesehen davon, das ich ohne dieses .def-Gedödel glänzend auskomme, ist das nur bei kleineren Programmen praktikabel. Irgendwann brauchst du die Register mal für etwas anderes. MfG Spess
Spess53 schrieb: > Vielleicht solltest du dir mal überlegen, ob du nicht bestimmte Sachen > von den Registern in den Ram verlagerst. Ok ja später bestimmt aber soweit bin ich noch nicht! das heißt ich würde einen bestimmten Wert, den ich momentan noch in einem Register speichere, das nur für diesen Zweck zuständig ist, von einem Arbeitsregister in den RAM speichere (z.B. Register inkrementieren dann den Registerinhalt an einem "bestimmten" Ort (Adresse) im Ram speichern, wenn dann dieser Wert z.B. für einen Vergleich herangezogen werden soll, kann man den Wert aus dem Ram wieder in ein Arbeitsregister laden. Ist das mit dem Ram prinzipiell so gemeint? das mit dem .def Gedödel würde sich vermutlich bei Nutzung des RAM erübrigen!? Noch bleibe ich dabei! Ich habe irgendwie Angst davor in einem Programm einen Fehler zu suchen nach dem Motto -> r17 ist 0 aber r 16 sollte den Wert 32 haben, wobei das 4te Bit von r18 gesetzt sein sollte. Da e"rrrrrrrrrrrr"t es bei mir nur noch im Kopf. Aber mit genügend guten Kommentaren sollte das wohl auch kein Problem mehr sein!
@ Spess53: Ich habe mir jetzt mal deine DateTime.asm genauer angeschaut: bei der Isleapyear-Routine bin ich der Meinung, einen Fehler gefunden zu haben? Ist das möglich? in der angefügten Datei findest du zuerst meine fast identische Routine und darunter deine. Ich habe die Stelle, die mir aufgefallen ist mit ;***** markiert! Gruß Olli
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.