Hallo Leute, ich bin dabei, ein Programm von mir durch eine Zusatzfunktion zu erweitern. Also, ein Timer fragt alle paar µs einen Pin ab, ob dieser ein LOW hat, als der daran angeschlossene Schalter gedrückt ist (gegen Masse). Wenn dieser gedrückt ist, soll er etwas machen, also so: if(!(PINB &(1<<PINB0))) { mache etwas; } Das Programm soll nun so erweitert werden, dass beim ersten mal drücken etwas gemacht wird und beim zweiten mal drücken etwas anderes gemacht wird und beim dritten mal drücken wieder das erste Ereignis ausgeführt wird usw, also nach dem Schema: if(!(PINB &(1<<PINB0))) { if(PORTD &(1<<PORTD0)) { PORTD &= ~(1<<PORTD0); } else { PORTD |= (1<<PORTD0); } } Das Problem dabei stellt die Entprellung dar. Die ganze Abfrage geschieht ja innerhalb einer Timer ISR. Da kann ich ja schlecht mit Endlosschleifen entprellen oder ein delay einfügen etc., wie ich es sonnst außerhalb einer ISR gemacht hätte. Auch das Setzen einer Hilfsvariable macht aus meiner Sicht keinen Sinn. Zurzeit rast er eben laufend beide Schleifen durch, so dass das Ergebnis einem Zufall entspricht. Hat jemand eine Idee? Leider muss die PIN/PORT-belegung so beibehalten werden, weil die Schaltung dazu schon steht. Ich kann also die Taster nicht auf einem normalen Interrupt-pin verbinden. Vielen Dank!
Ach so, beide Codebeispiele befinden sich natürlich in: ISR(TIMER0_OVF_vect) { CODEBEISPIEL }
Was ist das für ein Controller? AVR? Wenn ja, dann reicht ein Kondensator vom Eingangspin nach Masse. AVR-Controller haben an allen Eingängen einen Schmitt-Trigger, der die Flanke wieder begradigt. Bei Benutzung des internen PullUp reichen typischerweise 100nF keramisch. Bei niedrigeren PullUps den Kondensator entsprechend vergrößern, evtl. zusätzlich einen Serienwiderstand Richtung Taster. Gruß Jadeclaw.
Ein Kondensator am Eingangspin? Das Problem resultiert doch daraus, wie das Programm programmiert ist. Der Timer fragt ein paar hundert mal pro Sekunde die Eingangspins ab, ob dort ein LOW anliegt. Wenn ich nun die Abfrageanweisung so benutze, wie oben beschrieben, dann ists doch Zufall, welche Anweisungsbedingung gerade eingegangen wird. Wie könnte man das Problem denn programmiertechnisch lösen? Vielen Dank.
> Das Problem dabei stellt die Entprellung dar. Die ganze Abfrage > geschieht ja innerhalb einer Timer ISR. Da kann ich ja schlecht mit > Endlosschleifen entprellen oder ein delay einfügen etc., wie ich es > sonnst außerhalb einer ISR gemacht hätte. Die Schleife und die Delays stören überhaupt nicht, weil man sie weglassen kann: Die Schleife etfällt dadurch, dass der Interrupthandler periodisch aufgerufen wird. Die entfallen delays dadurch, dass zwischen zwei Interrupts eine gewisse (einstellbare) Zeit vergeht. Du brauchst dir also keinen grundsätzlich neuen Entprellalgorithmus zu überlegen, sondern nur die Methode, die du außerhalb des Interrupthandlers benutzen würdest, etwas anzupassen. Es gibt aber schon viel Fertiges zu diesem Thema, bspw. dieses hier: http://www.mikrocontroller.net/articles/Entprellung#Komfortroutine_.28C_f.C3.BCr_AVR.29 Insbesondere dann, wenn du mehrere (bis zu 8) Tasten entprellen möchtest, stellt diese Routine so ziemlich das Optimum dar.
hallo, programmiertechnisch ist das eigentlich nicht schwer. Sagen wir dein Taster hat eine Prell-Zeit von 3 ms.. also bis der Low-Pegel "konstant" für die Dauer des Tastendrucks anliegt. Nun Tastest du eben nur alle 10 ms ab! Ein "normaler" Tastendruck dauert zwischen 100 ms und 300 ms... je nach dem wer und wie gedrückt wird! Nun, einfach den letzten Zustand merken. Z.b. letzer Zustand gedrückt.. neuer Zustand auch gedrückt. -> dann tue etwas! Das ganze ist dann sehr sicher, da das Signal konstant über 20 ms auf low-pegel liegt. MFG
übrigens... zum merken des letzten Zustands keine globalen variablen, sondern statische variablen in der ISR benutzen... z.b. static unsigned char letzer_zustand = ZUSTAND_GEDRUECKT;
Also, ich hab nun nochmal etwas nachgelesen und bemerkt, dass ich ja einfach nur auf die Flanke abfragen muss und nicht auf den Zustand, so wie das hier beschrieben ist: http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten Das Beispiel ist aber leider in asm und nicht c. @yalu, den Artikel hatte ich mir auch angeschaut, aber ich find den Code für diese einfach Anwendung ein wenig zu kompliziert. Geht das nicht auch einfacher, so wie in den oben genannten Link mit xor, also dem ^ Operator? @preller, warum static?
So, ich hab mir jetzt mal folgenden Codeschnippsel überlegt, der sich in der Timer-ISR befindet: static uint8_t alt=0, neu=0, onoff=0; neu=PINB & (1<<PINB0); if((neu^alt) == (!(PINB &(1<<PINB0)))) { if(onoff==0) { PORTD |= (1<<PORTD0); onoff = 1; } else { PORTD &= ~(1<<PORTD0); onoff = 0; } Leider funktioniert die Abfrage aber nicht immer. Worann könnte das liegen? Ist mein Ansatz zur Erkennung der Flanke korrekt? Danke.
"alt" wird nirgendwo gesetzt? ... alt=neu <-- ? neu=PINB & (1<<PINB0); ... Beachte auch, dass "alt" und "neu" globale Variablen sein müssen; sie dürfen nicht beim Verlassen der Routine zerstört werden.
> So, ich hab mir jetzt mal folgenden Codeschnippsel überlegt, der sich > in der Timer-ISR befindet: Da wird aber noch nichts entprellt. Nur die Flanke abzufragen, reicht nicht, da während des Prellens ganz viele Flanken entstehen. Um das Prellen wegzubekommen, musst du während der maximalen Prellzeit (z.B. 10 ms) den Eingang mehrfach abfragen. Eine Reaktion erfolgt erst, wenn der Eingang während dieser Zeit konstant auf low oder konstant auf high liegt. Wechselt der Eingang während dieser Zeit, ist der Prellvorgang offensichtlich noch nicht abgeschlossen, deswegen erfolgt noch keine Reaktion. > if((neu^alt) == (!(PINB &(1<<PINB0)))) Du vermischt in dieser Abfrage Bitoperationen (^, &) mit einer logischen Operation (!). Das liefert sicher nicht das gewünschte Ergebnis. > Beachte auch, dass "alt" und "neu" globale Variablen sein müssen; sie > dürfen nicht beim Verlassen der Routine zerstört werden. Global müssen sie nicht unbedingt sein. Es reicht, dass sie als static deklariert sind, das ist schon ok.
Ja, die ganze Routine befindet sich doch innerhalb eines Timers. Das müsste doch zur Entprellung ausreichen. Wenn der Taster nun nach dem "Erstkontakt" weiterprellt, geschieht dies in einer Programmphase nach der Timer-ISR. Ein weiteres Prellen wird somit ignoriert. Das Problem ist ja, dass bei der Routine mit dem xor-Operator z.Zt. irgendwie nicht nur die Flanke an sich abgefragt wird, sondern auch der Zustand, sprich, das Programm kehrt immer wieder in die Anweisung rein, wie der Taster am Eingang gedrückt ist und nicht nur zu dem Zeitpunkt, wenn er gedrückt wird. Ich bin langsam echt am verzweifeln, rätzel hin und her. Ich wäre um jeden Tip oder Lösung(sansatz) sehr dankbar.
Also ich mach das bei Tasten u. ä. in Assembler meistens so, dass ich die Statusbits (bei Tasten: gedrückt/nicht gedrückt) über den Befehl 'rol' (rotate left) in ein Register reinschieb. Unmittelbar davor wird der Registerwert noch mit 1 geANDet. Das ist in Assembler mit fünf Instruktionen erledigt ("Key" ist das besagte Register):
1 | clc |
2 | sbic PORTD, PD3 ; Taste angeschlossen an PD3 |
3 | sec |
4 | ; jetzt enthält das C-Flag die Tastenzustandsinformation: |
5 | ; Taste down <--> C = 0, Taste up <--> C = 1 |
6 | andi Key, 1 |
7 | rol Key |
Was bewirkt das? Nun, das Register "Key" hat dann zu jedem Zeitpunkt genau einen von vier möglichen Werten, nämlich 0, 1, 2, oder 3, und jeder Wert hat eine bestimmte Bedeutung: Bei 3 = 0b11 ist die Taste ungeändert up, bei 0 = 0b00 ungeändert down, bei 2 = 0b10 wurde sie gerade gedrückt, bei 1 = 0b01 gerade losgelassen. Abgefragt wird die Taste natürlich timergesteuert im 10 oder 20 ms Zeitraster (nicht zu kurz, damit der µC noch mit der Abarbeitung aller Tasks hinterherkommt, aber auch nicht zu lang, damit Reaktionen auf Tastendrücke vom Benutzer noch als unmittelbar wahrgenomen werden) während der gesamten Programmlaufzeit. Der "Key"-Wert wird dann irgendwo im Hauptprogramm z. B. auf "2" getestet und ggf. die zugehörige Aktion - meistens das Starten irgendeines Software-Timers, von dem dann wiederum andere Sachen abhängen - ausgelöst.
> Ja, die ganze Routine befindet sich doch innerhalb eines Timers. Das > müsste doch zur Entprellung ausreichen. Dann sind aber die paar µs aus deinem ersten Post viel zu wenig. Die Prellzeiten bei den meisten Tastern liegen im ms-Bereich. Ich glaube, ich habe jetzt auch verstanden, was du mit der dubiosen If-Abfrage erreichen möchtest. Probier's mal mit
1 | if((neu^alt) && (!(PINB &(1<<PINB0)))) |
oder besser
1 | if((neu^alt) && !neu) |
Damit, mit einem Timer-Intervall von ein paar ms statt µs und mit einem
1 | alt = neu; |
am Ende des Interrupthandlers sollte die Sache funktionieren und zumindest das Prellen beim Schließen und Öffnen des Tasterkontakts beseitigt werden. Wird bei gedrückter Taste der Finger etwas bewegt, so dass die Kontaktflächen leicht aufeinander "schaben", kann es passieren, dass der Kontakt kurzzeitig abreißt, was dann ebenfalls zu einer Fehlauslösung führt. Dieses Problem lässt sich nur dadurch beseitigen, dass überprüft wird, ob der Taster wirklich für eine bestimmte Zeitdauer losgelassen wurde.
Thomas wrote: > Also, ein Timer fragt alle paar µs einen Pin ab, ob dieser ein LOW hat, Das ist Unfug, kein mechanischer Kontakt schaltet innerhalb weniger µs, sondern von ms. Zusätzlich hat ein Mensch etwa 300ms Reaktionszeit. Ein optimales Abfrageintervall ist 5..50ms. Hier mal ein Beispielcode: Beitrag "Universelle Tastenabfrage" Peter
So, ich habs mit Hilfe des Codes von yalu hinbekommen. Komischerweise reagiert er aber nicht bei jedem Tastendruck, also sagen wir mal im Verhältnis, 5:1, als 5 mal funktionierts und beim 6. mal Drücken eben nicht. Die Zeit des Timers beträgt 250us. Der angeschlossene Taster ist kein mechanischer, sonder ein Piezo Taster. Prellen die überhaupt? Könnte mir vorstellen, dass beim kurzen Antippen keine 5-50 ms reichen, oder? Jedenfalls hab ich hier schon bei meinen 250 us diese Probleme. Vielleicht zur Info, es läuft noch ein zweiter Timer, der exakt jede Sekunde unterbricht. In der dazugehörigen ISR steht aber nur eine Zeile drinn, nämlich : d++ mehr nicht. Könnten sich diese beiden Timer irgendwie beeinflussen?
@Thomas, versuche doch einfach mal Peters Code zu analysieren. Ich nutze seinen Algorithmus in verschiedenen Varianten in Assembler (der C-Code wird nicht viel anders arbeiten) und behaupte kackfrech, dass es nichts gibt, das das Entprellen zuverlässiger und effektiver realisiert. Der Timer, in dessen ISR die Entprell-Task läuft, kann nebenher noch andere Aufgaben erledigen, auch das Zählen der Zeit. Ein zweiter Timer-Int. mit einem Intervall von 1s ist also nicht nötig. Man setzt den Timer zur Entprellung auf 10ms und zählt darin nebenbei die Hundertstelsekunden für die Zeitzählung hoch. KH
Peters Code scheint ja wirklich das non plus Ultra zu sein, wenn hier jeder davon schwärmt. Ich les mich da mal rein. Vielen Dank nochmals! Grüße!
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.