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!
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