Forum: Mikrocontroller und Digitale Elektronik STM32: Wie funktioniert diese Entprellung?


von joop13 (Gast)


Lesenswert?

http://www.emcu.it/STM32/STM32Discovery-Debounce/STM32Discovery-InputWithDebounce_Output_UART_SPI_SysTick.html

Ich habe diese Methode zur Entprellung entdeckt, verstehe allerdings 
nicht wirklich was er bei der Debounce Routine gemacht hat.

Wäre freundlicht, wenn mir jemand die Funktionsweise erklären könnte.

von Stefan R. (1994rstefan)


Lesenswert?

Jede Millisekunde wird der Input gelesen. Es gibt zwei Variablen, die 
eine speichert die Anzahl an Samples bei der der Input 1 war und die 
andere bei der der Input 0 war. Jedes mal wenn sich der Status ändert, 
wird die Variable des anderen Status auf 0 gesetzt. So lange der Input 
prellt werden also beide Variablen regelmäßig auf 0 zurück gesetzt.
Wenn der Input nicht mehr prellt steigt der Wert der einen Variablen bis 
sie größer oder gleich REFdebounce ist, dadurch wird die Variable 
StatoIn1 auf den aktuellen Status des Inputs gesetzt.

Kleiner Nachteil dieses Ansatzes: Die Eingabe ist etwas verzögert (je 
nachdem wie lang der Input prellt und was für REFdebounce gewählt wurde) 
da der Status erst gesetzt wird nachdem der Input REFdebounce 
Millisekunden nicht mehr prellt.

: Bearbeitet durch User
von Stefan K. (stefan64)


Lesenswert?

In1_0 zählt, wie lange In1 schon am Stück 0 ist. Wenn In1_0 größer als 
REFdebounce wird, dann wird Statoln1 auf 0 gesetzt.

In1_1 zählt, wie lange In1 schon am Stück 1 ist. Wenn In1_1 größer als 
REFdebounce wird, dann wird Statoln1 auf 1 gesetzt.

Mal Dir einfach mal auf, was bei einem prellenden In1 mit In1_0, In1_1 
und Statoln1 passiert.


Übrigens finde ich die Namensgebung in dem Beispiel etwas unglücklich.

Gruß, Stefan

von joop13 (Gast)


Lesenswert?

Stefan R. schrieb:
> leiner Nachteil dieses Ansatzes: Die Eingabe ist etwas verzögert (je
> nachdem wie lang der Input prellt und was für REFdebounce gewählt wurde)
> da der Status erst gesetzt wird nachdem der Input REFdebounce
> Millisekunden nicht mehr prellt.

Danke für die hilfreiche Erklärung.
Ein weiterer Nachteil ist dann doch auch, dass die Entprellfunktion jede 
Sekunde aufgerufen wird und nicht nur wenn man sie benötigt, z.B. erst 
nach einem externen Interrupt (durch Taster ausgelöst). Stimmts?

Wo kann ich noch eine andere/bessere (die den von mir oben beschriebenen 
Nachteil nicht hat) Entprellung finden, die für Anfänger gut 
verständlich sind?

Am besten für den STM32, da ich die Funktionen von
http://www.mikrocontroller.net/articles/Entprellung
nicht verstehe, da mir die Befehle des anderen Controllers einfach 
nichts sagen.

von Peter D. (peda)


Lesenswert?

joop13 schrieb:
> Ein weiterer Nachteil ist dann doch auch, dass die Entprellfunktion jede
> Sekunde aufgerufen wird und nicht nur wenn man sie benötigt, z.B. erst
> nach einem externen Interrupt (durch Taster ausgelöst). Stimmts?

Nein, stimmt nicht.
Du kannst ja mal den "Overhead" an CPU-Last ermitteln, der ist 
lächerlich klein, da lohnt sich einfach kein zusätzlicher externer 
Interupt.
Ein externer Interrupt ist nur dann notwendig, falls man den MC per 
Taste aus dem Power-Down aufwecken will.

Diese Routine liefert schon exzellente Ergebnisse bezüglich Entprellung 
und Störunterdrückung bei sehr geringer CPU-Last im Gegensatz zu vielen 
anderen Lösungen, die so im Web rumgeistern.

Man kann sie bestenfalls noch etwas optimieren, insbesondere, wenn 
mehrere Taste zu entprellen sind. Z.B. reicht ein 2Bit-Zähler bei ~10ms 
Timerintervall aus. Dann kann man mit nur 2 Variablen bis zu 32 Tasten 
parallel entprellen. Und man kann im Interrupt gleich das 
Gedrückt-Ereignis bereitstellen bzw. bei Bedarf auch das 
Losgelassen-Ereignis.

von Micha (Gast)


Lesenswert?

joop13 schrieb:
> Am besten für den STM32, da ich die Funktionen von
> http://www.mikrocontroller.net/articles/Entprellung
> nicht verstehe, da mir die Befehle des anderen Controllers einfach
> nichts sagen.

Naja, das ist genauso in C geschrieben wie der Code in deinem Beispiel 
auch. Die paar "Spezialfunktionen" für AVRs muss man halt austauschen. 
Wenn man das nicht selber machen will oder kann, guckt man sich 
Beitrag "Re: Universelle Tastenabfrage" an und nimmt den 
dortigen Code. Da fehlt dann nur noch die Header-Datei für das genaue 
Derivat.

von joop13 (Gast)


Lesenswert?

Peter D. schrieb:
> Man kann sie bestenfalls noch etwas optimieren, insbesondere, wenn
> mehrere Taste zu entprellen sind.

Wie kann ich diese Funktion für alle Pins verallgemeinern?
Ich würde an die Funktion den Pin und Port übergeben (z.B. PD2) und dort 
eine switch-case Abfrage mit allen möglichen Pins. In dieser Abfrage 
dann den jeweiligen Pin einlesen.


Peter D. schrieb:
> Z.B. reicht ein 2Bit-Zähler bei ~10ms
> Timerintervall aus. Dann kann man mit nur 2 Variablen bis zu 32 Tasten
> parallel entprellen.

Wie soll deine Möglichkeit genau funktionieren?

von joop13 (Gast)


Lesenswert?

joop13 schrieb:
> Wie kann ich diese Funktion für alle Pins verallgemeinern?
> Ich würde an die Funktion den Pin und Port übergeben (z.B. PD2) und dort
> eine switch-case Abfrage mit allen möglichen Pins. In dieser Abfrage
> dann den jeweiligen Pin einlesen.

Mir fällt gerade auf, dass dies keinen Sinn macht, weil dann der Rest 
der debounce Routine auch für jeden Pin extra gemacht werden müsste.

Aber wie kann ich das sonst machen?

von Peter D. (peda)


Lesenswert?

joop13 schrieb:
> Wie soll deine Möglichkeit genau funktionieren?

Ist hier auf den STM32 portiert:
Micha schrieb:
Beitrag "Re: Universelle Tastenabfrage"

von Hosenmatz (Gast)


Lesenswert?

Peters Methode (er ist hier unser Chef-Entpreller) funktioniert auch mit 
mehreren Tasten. Das ist in dem Artikel; unter dem Code; sehr gut 
erklärt.
Achte mal darauf, wenn von "Vertikalen Zählern" die Rede ist. Da wird es 
spannend.

von joop13 (Gast)


Lesenswert?

Ich denke mal für meine Zwecke (Entprellung von bis zu 16 Taster und für 
Anfänger) wäre wahrscheinlich die Methode "Debounce-Makro von Peter 
Dannegger" gut geeignet. Allerdings kann ich mit diesem Code überhaupt 
nichts anfangen. Hier einige Verständnisprobleme (ich komme mit dem AVR 
nicht klar):

1) Warum ist das Makro (welches laut Titel entscheidend für die 
Entprellung ist) auskommentiert.
2) Was bedeutet DDRB  &= ~(1<<PB0);
3) PORTB |=   1<<PB0; Bedeutet dies, dass ich PB0 auf HIGH setze?
   PORTB ^= 1<<PB2; Bedeutet dies, dass ich PB2 auf LOW setze?
4) Warum hat man hier die ;;:  for(;;){}

Hosenmatz schrieb:
> Peters Methode (er ist hier unser Chef-Entpreller) funktioniert auch mit
> mehreren Tasten. Das ist in dem Artikel; unter dem Code; sehr gut
> erklärt.
> Achte mal darauf, wenn von "Vertikalen Zählern" die Rede ist. Da wird es
> spannend.

Welche Erklärung meinst du? Von der vertikalen Variante habe ich auch 
schon gelesen. Das sollte für mein Projekt allerdings schon zu 
umfangreich sein.

von W.S. (Gast)


Lesenswert?

joop13 schrieb:
> Wo kann ich noch eine andere/bessere (die den von mir oben beschriebenen
> Nachteil nicht hat) Entprellung finden, die für Anfänger gut
> verständlich sind?
>
> Am besten für den STM32, da...

Das ist nicht sinnvoll, sowas zu fragen. Vorgehensweisen zum Entprellen 
von Tasten sind per se völlig unabhängig von jeglicher Rechnerstruktur. 
Du solltest danach trachten, den Sinn zu verstehen, dann kannst du damit 
Tasten an allen Typen von µC entprellen.

Hier mal meine Herangehensweise:
- abgefragt wird in der Systemuhr, die die Abfrage zumeist so alle 10 ms 
macht.
- gilt eine Taste als ungedrückt und wird sie als gedrückt erkannt, dann 
wird sofort eine systeminterne Botschaft "Taste_XYZ_gedrückt" (aka 
"event") veranlaßt und die Taste als gedrückt markiert und ihr 
Entprellzähler gesetzt. Obendrein wird ein Repetier-Abwärtszähler auf 
eine erste (lange) Zeit gesetzt.

- gilt eine Taste als gedrückt und wird sie als gedrückt erkannt, dann 
wird der Repetierzähler dekrementiert und der Entprellzähler gesetzt. 
Wird der Repetierzähler null, dann wird obige Botschaft veranlaßt, der 
Entprellzähler gesetzt und der Repetierzähler auf eine zweite (kurze) 
Zeit gesetzt.

- gilt eine Taste als gedrückt und wird sie als ungedrückt erkannt, dann 
wird der Entprellzähler dekrementiert. Wird dieser null, dann wird die 
Taste als ungedrückt markiert.

Mit diesem Verfahren hat man direktemang beim Drücken das Tsten-Ereignis 
verfügbar, das Entprellen kommt danach und man hat ein freundliches 
Repetierverhalten: ne längere Zeit beim ersten Repetieren und dann ne 
kürzere Zeit.

So, jetz thoffe ich bloß, daß ich mich weiter oben nicht verschrieben 
habe...

W.S.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

joop13 schrieb:
> Mir fällt gerade auf, dass dies keinen Sinn macht, weil dann der Rest
> der debounce Routine auch für jeden Pin extra gemacht werden müsste.
>
> Aber wie kann ich das sonst machen?

 Mit SysTick der alle 5ms aufgerufen wird.
 Warum aus Entprellen immer wieder Raketenwissenschaft gemacht wird,
 bleibt für mich unklar.
 Pro Taster wird eine Variable reserviert, mit 9bit (2,5s) für Zähler
 und 2 bits als Tasterflags. Man nimmt aber 16bit, ist einfacher.
 Für 16 Taster ergibt das 16 * 16 bits = 256 Bits oder 16 2Byt Vars.
 Um es schneller und einfacher zu machen liest man alle Taster auf
 einmal ein oder in Gruppen a 8, blendet evtl. nicht benötigte bits
 aus und vergleicht mit vorherigem Zustand.
 Immer wenn irgendein Taster als gedrückt erkannt wird, wird sein Zähler
 um eins inkrementiert. Wenn Zähler >= 8 (40ms), wird der Taster als
 gedrückt geflagt.
 Wird dagegen Taster als nicht gedrückt erkannt, geht der zugehörige
 Zähler sofort auf Null, Tasterflags auch, d.h. man setzt die
 zugehörige Variable auf Null.
 Ist der Zähler >= 255 (1.28s) und Tasterflag == 1, wird Flag fur
 längeren Tastendruck gesetzt.
 Wenn Flag für längeren Tastendruck gesetzt ist, wird sein Zähler
 nicht mehr inkrementiert.
 Und das ist schon alles.

: Bearbeitet durch User
von Micha (Gast)


Lesenswert?

joop13 schrieb:
> 2) Was bedeutet DDRB  &= ~(1<<PB0);
> 3) PORTB |=   1<<PB0; Bedeutet dies, dass ich PB0 auf HIGH setze?
>    PORTB ^= 1<<PB2; Bedeutet dies, dass ich PB2 auf LOW setze?

Warum hängst du dich an dem AVR-Kram auf und warum weigerst du dich 
vehement dir den schonmal 2x verlinkten 
Beitrag "Re: Universelle Tastenabfrage" mitsamt 
dem dortigen Quellcode anzusehen?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Micha schrieb:
> Warum hängst du dich an dem AVR-Kram auf und warum weigerst du dich
> vehement dir den schonmal 2x verlinkten
> Beitrag "Re: Universelle Tastenabfrage" mitsamt
> dem dortigen Quellcode anzusehen?

 Wahrscheinlich weil es einfach zu viel Code fur so wenig Arbeit ist ?

von Peter D. (peda)


Lesenswert?

Marc V. schrieb:
> Wahrscheinlich weil es einfach zu viel Code fur so wenig Arbeit ist ?

Der Code ist absolut minimal und auch die CPU-Last ist lächerlich.
Der große Rest sind Kommentare und Defines. Wens stört, der kann die 
Kommentare doch einfach löschen und die Defines ausschreiben.

Wer aber schon einige Tage programmiert, wird wissen, daß Kommentare und 
Defines das Verstehen ganz enorm erleichtern. Und besonders, wenn man 
nach einigen Monaten weiter daran arbeiten will, weil einem noch 
zusätzliche Ideen eingefallen sind.

von F. F. (foldi)


Lesenswert?

joop13 schrieb:
> Wo kann ich noch eine andere/bessere (die den von mir oben beschriebenen
> Nachteil nicht hat) Entprellung finden, die für Anfänger gut
> verständlich sind?

Ganz im Anfang habe ich in einem Arduino Buch (vom Erfinder selbst 
geschrieben) gelesen, dass er 10ms Pause einfügt. Hat sicher viele 
Nachteile, aber in vielen Anwendungen macht sich eine kurze Pause gar 
nicht bemerkbar. Ich habe das dann mal intensiv ausprobiert und 
festgestellt, dass bei 3ms schon nichts mehr passiert. Ich selbst füge 
4ms Pause ein und bei meinen Sachen hat mir das bis jetzt gereicht.

von Hosenmatz (Gast)


Lesenswert?

Peter D. schrieb:
> Marc V. schrieb:
>> Wahrscheinlich weil es einfach zu viel Code fur so wenig Arbeit ist ?
>
> Der Code ist absolut minimal [...]

Mir kommt es so vor, als müsste man die Auffassung von Marc und die von 
joop13 auf ein Mißverständnis zurückführen.

Der Code von Peter ist in der Lage mehrere Tasten gleichzeitig zu 
entprellen. Und zwar in dem Sinne "gleichzeitig", dass mehrere Tasten 
auch tatsächlich zum selben Zeitpunkt betätigt werden können.

Wenn der TO das ohnehin möchte, dann ist der Code dafür geeignet. Wieso 
sollte er "zu" aufwendig sein? Zu aufwendig, wofür?

Noch mehr: Wenn man sich mal den Artikel über Entprellung hier 
durchliest, dann wäre eine "naive" Implementierung eine Schleife über 
die möglichen Portpins und dann das hochzählen des dem jeweiligen ihm 
zugeordneten Zählers. Das kann man generalisieren, in dem man die Zähler 
im Speicher hintereinander anordnet und aus der Bitnummer des Tasters 
den Offset berechnet. Aber schaut man sich mal Peters Code an, so ist 
seines das effektivere Verfahren, weil es die Auswahl der 
hochzuzählenden Zähler (es müssen ja nicht alle Tasten betätigt worden 
sein) in einem einzigen Ausdruck ohne explizite Bedingung (die 
Bedingungsprüfung ergibt sich aus der Bitoperation) erledigt. Sie ist 
zweifellos nicht auf Anhieb zu verstehen, aber wesentlich effizienter, 
und wenn man sie mal verstanden hat, auch von einfacherer 
Programmstruktur.

Der Code ist also nicht unnötig umfangreich, sondern er macht genau das 
was er soll. Nicht einen Deut mehr. Er tut es mit wenig Code und 
effizient. Zugegeben, manche Details sind etwas tricky, aber wenn man 
mal verstanden hat, wie es geht (und Peter hat bisher viele Fragen zu 
Details beantwortet), dann ist es sogar interessant.

Es lohnt sich wirklich den Artikel zu lesen. Ergänzend auch den Thread 
in Projekte&Code von Peter, der, wenn ich mich recht erinnere (das ist 
nun schon über 10 Jahre her, glaube ich), recht gut erklärt was da wie 
funktioniert und warum. Ich bin nur gerade zu faul zum suchen. Sorry.

Peter: Hoffe es ist Dir nicht allzu unangenehm, wenn ich sozusagen für 
Dich das Wort ergreife.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Peter D. schrieb:
> Der Code ist absolut minimal...
 In deinem Ursprungsprogramm ja, aber nicht unbedingt im verlinktem
 Beitrag. Für einen Anfänger (und der TO ist einer, wie er selber sagt)
 ziemlich ungeeignet.
 Ich habe es schon mehrmals im Forum empfohlen:
 Wenn jemand was funktionierendes haben will, soll er deine Routine
 nehmen, aber nicht um die Funktionsweise zu verstehen, sondern um
 damit zu arbeiten.
 Oder, andersrum:
 Wenn jemand eine Library benutzen will, dann braucht er auch nicht
 zu verstehen wie das alles im einzelnen funktioniert.
 Wenn aber jemand verstehen will wie etwas funktioniert (und der TO
 will das), dann ist lesen von Librarys der denkbar schlechteste weg,
 weil der Code meistens so optimiert ist, dass ein Anfänger sehr wenig
 davon verstehen wird.
 Deswegen habe ich versucht, dem TO einen einfachen Weg zu zeigen, wie
 er das alles alleine schaffen (zumindest probieren) kann.

Peter D. schrieb:
> Wer aber schon einige Tage programmiert, wird wissen, daß Kommentare und
> Defines das Verstehen ganz enorm erleichtern. Und besonders, wenn man
 Stimme ich absolut zu, bei mir geht das manchmal bis 60:40.

Hosenmatz schrieb:
> Peter: Hoffe es ist Dir nicht allzu unangenehm, wenn ich sozusagen für
> Dich das Wort ergreife.

 Absolut unnötig, er kann selber antworten falls er das für nötig hält.

: Bearbeitet durch User
von joop13 (Gast)


Lesenswert?

Marc V. schrieb:
> Immer wenn irgendein Taster als gedrückt erkannt wird, wird sein Zähler
>  um eins inkrementiert. Wenn Zähler >= 8 (40ms), wird der Taster als
>  gedrückt geflagt.
>  Wird dagegen Taster als nicht gedrückt erkannt, geht der zugehörige
>  Zähler sofort auf Null, Tasterflags auch, d.h. man setzt die
>  zugehörige Variable auf Null.

Das Vorgehen bei dieser Entprellung habe ich nun verstanden, ich komm 
nur noch nicht mit mehreren Tastern klar. Ich will das ganze mit 4 
Tastern umsetzen.

So wie ich das verstanden habe, schreibst du alles in eine deine 
"Informationen" in eine einzige Variable. z.B wenn Taster 2 High ist 
bekommt dein integer an der Stelle 8(beispiel) eine 1. Aber wie willst 
du genau auf diese Stellen zugreifen?

Kannst du das bitte nochmal genauer erläutern, wie du dies bei 4 Tastern 
machen würdest?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

joop13 schrieb:
> Kannst du das bitte nochmal genauer erläutern, wie du dies bei 4 Tastern
> machen würdest?

 Hier hab ich versucht, etwas ähnliches zu erklären:
 Beitrag "Re: [AVR] Zwischen einzelnen PCINT-Quellen unterscheiden"

 Die untere Routine kann auch in deiner Timerroutine stehen.
 Es wird angenommen, dass deine Taster an PinB.0-PinB.3 sind, mit
 Pull-up an Vcc und beim draufdrücken auf Log.0 gehen.
 Auch wieder nicht geprüft, da ich C-Compiler nicht dabei habe.
 Und auch nicht mag...  ;)
1
  ActState = PINB & 0x0F; // Anstatt 0x0F kann auch andere Pinmask stehen
2
3
   //*** PinB.0 ueberpruefen
4
    if (ActState & Pin_0) {
5
      Pin0_state = 0;    //* Taster losgelassen oder prellt, alles auf Null
6
    }
7
    else {
8
      if (Pin0_state & 0x8000) goto chk1;          //* steht schon auf Tastendruck lang, nichts zu tun
9
      if ((Pin0_state & 0x1FF) >= 0x177) {          //* laenger als 1.5s ?
10
        Pin0_state |= 0x8000;              //* setze bit fur Tastendruck lang
11
      } else {
12
        Pin0_state++;
13
        if ((Pin0_state & 0x1FF) == 8) Pin0_state |= 0x4000;  //* setze bit fur Tastendruck normal
14
      }
15
    }
16
17
chk1:
18
   //*** PinB.1 ueberpruefen
19
    if (ActState & Pin_1) {
20
      Pin1_state = 0;    //* Taster losgelassen, alles auf Null
21
  ...
22
    }
 Da du deine Taster nicht mit INT-Routinen sondern in der Timer-Routine
 abfragst, werden immer alle vier Taster nacheinander geprüft, hat aber
 den Vorteil, dass auch mehrere Tasten gleichzeitig gedrückt werden
 können und du weisst sogar immer in welcher Reihenfolge das war.

joop13 schrieb:
> bekommt dein integer an der Stelle 8(beispiel) eine 1. Aber wie willst
> du genau auf diese Stellen zugreifen?

 Falls du meinst, wie ich in main() darauf zugreife, dann mit einer
 solchen Abfrage (maskierte Abfrage):
1
  if (Pin0_state & 0x8000)  {
2
    // Tastendruck lang, tu etwas
3
  } else if (Pin0_state & 0x4000)  {
4
    // Tastendruck normal, tu etwas
5
  }

 oder so (bit Abfrage):
1
  if (Pin1_state & (1<<LongPress))  {
2
    // Tastendruck lang, tu etwas
3
  } else if (Pin1_state & (1<<ShortPress))  {
4
    // Tastendruck normal, tu etwas
5
  }

: Bearbeitet durch User
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.