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.
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
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.
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.
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.
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?
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?
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.
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.
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.
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.
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?
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 ?
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.
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.
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.
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.
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?
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)gotochk1;//* 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):