Forum: Mikrocontroller und Digitale Elektronik Entprellen von Timerinterrupttasterabfragen


von Thomas (Gast)


Lesenswert?

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!

von Thomas (Gast)


Lesenswert?

Ach so, beide Codebeispiele befinden sich natürlich in:

ISR(TIMER0_OVF_vect)
{
    CODEBEISPIEL
}

von Jadeclaw D. (jadeclaw)


Lesenswert?

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.

von Thomas (Gast)


Lesenswert?

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.

von Thomas (Gast)


Lesenswert?

Ach so, ja ist ein AtMega8.

von yalu (Gast)


Lesenswert?

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

von preller (Gast)


Lesenswert?

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

von preller (Gast)


Lesenswert?

ü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;

von Thomas (Gast)


Lesenswert?

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?

von Thomas (Gast)


Lesenswert?

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.

von AVRFan (Gast)


Lesenswert?

"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.

von yalu (Gast)


Lesenswert?

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

von Thomas (Gast)


Lesenswert?

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.

von AVRFan (Gast)


Lesenswert?

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.

von yalu (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von Thomas (Gast)


Lesenswert?

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?

von Kachel - Heinz (Gast)


Lesenswert?

@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

von Thomas (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.