Datum: 20.02.2003 14:34
Angehängte Dateien:Auf besonderen Wunsch eines einzelnen habe ich die Routine erweitert, um auch die labrigsten Tasten der Welt entprellen zu können. Diese Version ist also wirklich kugelsicher. Es werden jetzt 4 gleiche Zustände hintereinander benötigt, um als gültiges Drücken oder Loslassen erkannt zu werden. Dadurch hat sich die Ausführungszeit von 10 auf 12 Zyklen verlängert. Außerdem habe ich den Vorteiler verkürzt, da sonst durch das nun 4-malige Abtasten eine deutliche Verzögerung sichtbar wäre. Die Funktionsweise ist folgende: Die Bits der Register CT0, CT1 bilden für jede Taste einen Zähler 0..3. Ist der Tastenzustand mit dem entprellten Zustand identisch, wird dieser 2-Bit Zähler permanent zurückgesetzt. Nur wenn also 4-mal hintereinander der entgegengesetzte Tastenpegel anliegt, kann dieser Zähler einmal rum zählen (= 4 Zustände 0..3) und wenn dann immer noch der entgegengesetzte Tastenpegel anliegt, wird er von dem entprellten Register (key_state) übernommen. Da die Tasten low-activ sind, bedeutet dort eine 1 = Taste gedrückt. Ich habs noch nicht getestet. Das Prinzip sollte das oben beschriebene sein. Falls sich wider Erwarten doch ein Fehler eingeschlichen haben sollte, sind sachliche Kommentare herzlich willkommen. Peter
Datum: 20.02.2003 14:38
P.S.: Beschimpfungen statt sachlichen Kommentaren bitte per E-Mail direkt an mich richten, anstatt damit das Forum zu verunstalten. Peter
Datum: 20.02.2003 16:38
Angehängte Dateien:Ups, da war ich wohl etwas zu hektisch. Hier nochmal ohne Schreibfehler. Peter
Datum: 24.02.2003 17:07
Angehängte Dateien:Und hier nochmal für die C-Freaks (Keil C51). Peter
Datum: 05.03.2003 16:42
Hier findet man die nötige Theorie dazu: http://www.ece.umd.edu/~msquared/rtas2000.pdf Peter
Datum: 17.07.2003 13:23
und das ganze, wenn ich nur eine taste habe, die einen interrupt auslöst?
Datum: 17.07.2003 13:33
Ja, für eine Taste sind 4 Register etwas viel Speicherverbrauch. Hier im Zip-File: http://www.mikrocontroller.net/forum/read-4-32158.html findest Du die Routine "ovf0int.inc", die nur eine Taste abfragt und dafür auch nur 4 Bits eines Registers benötigt. Das Prinzip ist aber dasselbe. Peter
Datum: 18.07.2003 09:36
@Martin, "die eintastenroutine ist ok, aber ich meine natürlich wieder einmal was anderes: die taste zu entprellen, die den interrupt auslöst!" Wie Du bestimmt auch in den anderen Entprellroutinen gesehen hast, muß man mindestens noch ein weiteres mal den Eingang nach einer Wartezeit abtesten. Warten in einem Interrupt ist jedoch tödlich, da solange ja auch alle anderen Programmroutinen hängen bleiben. Du must also einen Timerinterrupt verwenden, um die Wartezeit zu realisieren. Und daher ist es ziemlich witzlos, noch zusätzlich einen externen Interrupt zur Tastenabfrage zu verwenden. Das kostet Dich nur unnötig Code. So ein Timerinterrupt ist auch ja sehr nützlich, um sämtliche anderen Zeitabläufe zu machen. Kein einziges meiner Programme kommt ohne den Timerinterrupt aus. Peter
Datum: 12.11.2003 22:52
Habe soeben versucht, das Programm Get8key4.c51 (siehe Beitrag weiter oben) nachzuvollziehen. Die Idee, die Zählbits auf verschiedene Bytes zu verteilen und damit eine sehr kompakte und schnelle Tastenentprellung zu realisieren ist wirklich Klasse. Würde mich freuen, wenn jemand bereit ist etwas Hirnschmalz zu investieren um meinen Überlegungen zu folgen. Vielleicht kapier' ich das Programm aber auch einfach nicht. ;-) i = key_state ^ ~KEY_INPUT; // key changed ? Das entsprechende Bit in i ist 0 bei Änderung (Wenn key_state <> KEY_INPUT) ct0 = ~( ct0 & i ); // reset or count ct0 ? toggeln der Bits in ct0 ist Teil des Zählvorgangs, der in ct0 und ct1 abläuft. Das geschieht hier, wenn das entsprechende Bit in i == 1 ist, also solange keine Änderung vorliegt. Ist i(n) = 0 (bei Änderung) wird ct0(n) bei jedem Aufruf wieder auf 1 gesetzt. Laut Beschreibung im ersten Beitrag sollte der Zähler aber permanent zurüchgesetzt werden solange key_state == KEY_INPUT. ct1 = ct0 ^ ct1 & i; // reset or count ct1 ? Wie oben, bei i(n) = 0 (also bei Änderung, wenn key_state <> KEY_INPUT) wird der Zähler zurückgesetzt. ? Beim Zählen würde der Zustand ct1(t) tatsächlich aus ct0(t-1) und ct1(t-1) durch EXOR-Verknüpfung gebildet. Der Befehl oben verwendet aber bereits ct0(t) anstelle ct0(t-1). Damit das wieder passt müsste der Term (ct0 ^ct1) invertiert werden. i &= ct0 & ct1; // count until roll over ? Auch diese Zeile ist mir nicht klar geworden. Im Simulator macht der Programmcode aus Get8key4.c51 nicht was er soll. Hier ist meine Version: i = key_state ^ KEY_INPUT; ct0 = ~ct0 & i; ct1 = ~( ct1 ^ ct0 ) & i; i = ~ct1 & ~ct0 & i; key_state ^= i; Habe den Programmcode durch den Simulator laufen lassen: -> geht! Andi
Datum: 13.11.2003 09:03
@Andi, die Signale CT0 und CT1 sind invertiert. Damit spart man etwas Code ein (in Deinem Code sind 3 Invertierungen mehr). Ansonsten funktiert es gleich. Das ich ~KEY_INPUT nehme, liegt daran, daß man üblicher Weise die Tasten gegen GND schaltet und somit den internen Pull-Up-Widerstand nehmen kann. Man spart also einen Widerstand ein. Also: Taste gedrückt -> KEY_INPUT = 0 -> key_state = 1 In meinem Scheduler Beispiel in der Codesammlung ist das auch auf dem AVR verwendet worden (WINAVR). Peter
Datum: 13.11.2003 09:05
@Andi, aber super von Dir, daß du es nicht nur stumpf abkopiert hast, sondern auch versucht hast, es nachzuvollziehen und das erfolgreich. Peter
Datum: 14.11.2003 14:25
@Peter, Danke für die Hinweise. Habe inzwischen auch herausgefunden warum Dein Programm bei mir nicht richtig simulierte. I. Mein Compiler wollte bei ct1 = ct0 ^ ct1 & i; Klammern haben, die hatte ich falsch gesetzt. Jetzt gehts. Es compiliert auch tatsächlich um zwei Befehle kürzer als meine Version. II. Werden mit static deklarierte lokale Variablen (ct0 und ct1) standardmässig vom Compiler initialisiert? Zumindest der Simulator machts. Dann übernimmt Dein Programm KEY_INPUT bereits beim ersten Durchlauf nach key_state. Meine Variante benötigt zunächst 4 Aufrufe und entprellt so auch unmittelbar nach Programmstart anliegende Schalterzustände. Macht in der Praxis wohl keinen Unterschied. Hat mich aber bei der Simulation zunächst verwirrt. Andi
Datum: 05.04.2004 12:54
ups und das ganze nun nochmal für richtige DAUs :) Ich hab hier nen AT90S8535 in Betrieb und am PortD 8 Taster drann Ich programmiere mit dem CodeVision AVR Compiler und komme mit dem Code selbst nicht zurecht. Ich weiß ja noch ncihtmal wie ich solche Interupts verwende ob nun externe oder interne :( Ich wäre dankbar für jede hilfe
Datum: 05.04.2004 14:43
@Robert, ein komplettes C-Beispiel findest Du hier: http://www.mikrocontroller.net/forum/read-4-49709.html läuft zumindest unter WINAVR (AVR-GCC). Allerdings solltest Du schon mal einen Blick in das Datenblatt werfen, welche Bits man setzen muß, um einen bestimmten Interrupt freizugeben, bzw. einen bestimmten Timermodus zu setzen. Auch kann es sein, daß unter CV Interrupts anders deklariert werden müssen, als unter WINAVR. Peter
Datum: 12.06.2004 16:59
@Peter, was ich noch nicht ganz kapiert habe: Wie oft muß der Schalter gleichen Zustand haben, dass es als solcher erkannt wird? Ist es richtig, daß es nach dem dritten mal soweit ist? Armin
Datum: 12.06.2004 17:16
4 mal. Kannst ja mal den Interrupt auf 1s runtersetzen und dann mitzählen. Peter
Datum: 12.06.2004 20:18
Hi ich habe mal das C-File in Codevision importiert und durchsimuliert. In welcher Variablen steht jetzt eigentlich der entprellte Tasten-Zustand? In "key_press" oder in "key_state"? Ich habe das so herausgefunden, daß eben dieser entprellte Tastenzustand nach 4 Zyklen in "key_state" drinsteht. Das wirft aber die Frage auf, wozu die Variable "key_press" ist?
Datum: 13.06.2004 14:51
Oftmals will man ja etwas machen, wenn eine Taste gedrückt wird und nicht, solange eine Taste gedrückt ist. Und "key_press" reagiert eben genau auf den Vorgang des Drückens, d.h. den Wechsel von losgelassen zu gedrückt. Peter
Datum: 19.10.2004 11:40
Hallo, ich denke ich habe Peters C-Code soweit verstanden. Nochmal zusammengefasst in i steht für Tasten die eine Veränderung seit dem letzten Aufruf erfahren haben eine "1" und für Tasten die ihren Status behalten haben eine "0". Alles darauf bezogen die Entprellung ist beendet. Möchte ich jetzt eine Erkennung für gerade gelöste Tasten hinzufügen müsste es doch wie folgt gehen: i = key_state ^ (~console); // key changed? ct0 = ~(ct0 & i); // reset or count ct0 ct1 = ct0 ^ ct1 & i; // reset or count ct1 i &= ct0 & ct1; // count until roll over? // **** Release Erkennung key_release |= key_state & i; // 1->0: key release detect // **** key_state ^= i; // then toggle debounced state key_press |= key_state & i; // 0->1: key press detect Zur Erklärung, in key_state stehen zu diesem Zeitpunkt der Zuweisung noch die "gedrückten Tasten" vom Aufruf davor. In i die Tasten deren Zustände sich geändert haben. Wenn also der Zustand einer Taste zuvor 1 war und sie sich geändert hat müsste man dies mittels und-Verknüpfung "mitbekommen". Somit müsste key_state AND i die gelösten Tasten ergeben. Sehe ich das richtig ? Grüße Dirk
Datum: 19.10.2004 15:22
faszinierend, wie derart kleine stückchen code so viel interesse hervorrufen; ich wünschte mir, ich könnte auch so kompakt und einfallsreich programmieren... übrigens, die c-version dieses entprell-codes sorgt seit ca. 4 monaten täglich in meinem auto für die entprellung von vier (alten und schlechten) tastern; bis jetzt hat die entprellung immer einwandfrei funktioniert! somit wäre an dieser stelle ein kompliment und dank an peter angebracht!
Datum: 11.05.2005 12:39
Kann es sein, dass nach dem Reset, nach 4 Timer-zyklen key_press einmal auf 255 geht ? Ich hab hier nämlich das phänomen...
Datum: 11.05.2005 13:19
> faszinierend, wie derart kleine stückchen code so viel interesse > hervorrufen; ich wünschte mir, ich könnte auch so kompakt und > einfallsreich programmieren... Das kommt schon, Peter kann es auch nicht von Geburt an.
Datum: 11.05.2005 13:21
>> Das wirft aber die Frage auf, wozu die Variable "key_press" ist?
Das ist wie in Hardware, du kannst direkt auf einen bestimmten Pegel am
Eingang reagieren oder du kannst Flanken-getriggert arbeiten also nur
bei Pegelwechsel eine Aktion ausführen. Flankengetriggert bedeutet
immer Taktbasierend. Wenn der Flankenwechsel so wei bei Peters
Entprellung sauber arbeitet ist eine Flankengetriggerte Auswertung von
Tasten immer vorzuziehen, das ist viel störunanfäliger als das reine
Polling. Denn der Informations-Gehalt einer Flankensteuerung enthält
zum Zeitpunkt des Auslösen ZWEI Informationen statt nur EINER. Nämlich
"jetzt hat sich der Pegel von 0 auf 1 geändert" statt "der Pegel ist
jetzt 1". Ein klemmender Schalter kann so eben nicht permanent schalten
sondern nur einmalig bevor er klemmt.
Gruß Hagen
Datum: 12.05.2005 21:14
Hallo,
ich bin gerade am Bau eines Belichtungstimers mit Atmel 90S2313 mit 4
7-Segmentanzeigen und 3 Taster als Eingang.
Die Taster sind Kurzhubtaster Reichelt 3301D, und ich habe die mit
folgendem Programm Code ohne Probleme in Betrieb.
//--------------------------------
if((PIND.6==0) && (S3==0)) // Start/Stop
{
S3=1;
if(TCCR0==0)
{
TCNT0=0; // Timer löschen
TCCR0=5; // Timer on 4mHz/1024 = 3,906 kHz
PORTB |= 0x80; // Relais PB7 = 1
}
else
{
TCCR0 = 0; // Timer off
PORTB &= ~0x80; // Relais PB7 = 0
}
}
if(PIND.6==1)
S3=0;
P.S ist mit CodeVision Programmiert
Datum: 12.05.2005 21:21
...wie war das denn jetzt mit Key_press ? Ist das Normal, dass es nach dem Reset, nach dem 4.Zyklus einmal auf 255 geht ? Wenn ja, kann man das vermeiden ?
Datum: 12.05.2005 21:36
"Ist das Normal, dass es nach dem Reset, nach dem 4.Zyklus einmal auf 255 geht ?" Nein. Hast Du die Tasten auch gegen GND geschaltet und die internen Pull-Ups an oder externe Pull-ups gegen VCC ? Peter
Datum: 12.05.2005 21:47
Da ich nur zwei Pins gebrauchen kann, hab ich diese am Anfang Deiner
Routine Maskiert, daher wäre eigentlich schon 255 ausgeschlossen.
Testen tue ich auf dem STK 500.
Ist mir auch erst aufgefallen, nachdem ich das key_press in ein
weiteres Register kopiert habe.
in save_sreg,SREG ;Statusregister sichern
in iwr0,key_port ;Port D einlesen
andi iwr0,0b00001100 ;nur Pin 2 und 3 sind interessant
com iwr0
eor iwr0,key_state ;
and key_ct0,iwr0 ;
and key_ct1,iwr0 ;
com key_ct0 ;count
eor key_ct1,key_ct0 ;
and iwr0, key_ct0 ;input AND last counterstate
and iwr0, key_ct1 ;
eor key_state,iwr0 ;toggle state
and iwr0,key_state ;0-1-transition
or key_press,iwr0 ;store key_press detect
or Status,key_press;Tastenergebnis auch in Status speichern
In der Mainloop lösche ich im Grunde nur noch key_press
Datum: 12.05.2005 21:48
@Buffy "... 3 Taster als Eingang." Wie soll das gehen, ich sehe da nur PIND.6 als Eingang ??? Neben dem Vorteil gleichzeitig 8 Tasten zu entprellen, zeichnet sich mein Code doch gerade dadurch aus, daß der Timer durchlaufend ist und somit z.B. als Systemuhr mit verwendet werden kann. Als weiterer Vorteil werden die Tastendrücke gemerkt und können später ausgewertet werden (Ich hasse es bei vielen kommerziellen Geräten, daß man immer so elendig lange warten muß, bis man endlich die nächste Taste drücken darf). Daß daneben noch viele andere Code laufen, die eben nicht diese Vorteile aufweisen, glaube ich Dir gern. Peter
Datum: 12.05.2005 22:27
@Carsten: Key_press kopiert man nicht. Man maskiert es auch nur, wenn während eines bestimmten Programmstatus einige Tasten gesperrt werden müssen, was im Normalfall nicht der Fall ist. Für key_press nutzt man ein oberes Register, um bitweisen Zugriff zu erhalten. Die einzelnen Bits in key_press werden von der Entprellroutine im Timer-Int gesetzt wenn eine Taste (erneut) betätigt wurde und vom Hauptprogramm gelöscht, wenn der zugehörige Job erledigt wird. Will man in der Mainloop auf Tastendrücke reagieren, fragt man die jeweilige Taste einzeln ab: sbrc key_press, tastenbit1 ;überspringe, wenn Bit gelöscht rjmp/rcall tast1 ;zur Abarbeitung, da Bit gesetzt ist sbrc key_press, tastenbit2 ;überspringe, wenn Bit gelöscht rjmp/rcall tast2 ... tast1: cbr key_press,1<<tastenbit1 ;Tastenbit1 löschen, Job wird erledigt, ... ;Job erledigen, den die Taste ausgelöst hat... rjmp mainloop / ret ;fertig... tast2: genauso... ... War ein Bit gesetzt, wird die zugehörige Routine aufgerufen (die den Job erledigt, den ein Tastendruck bewirken soll). Diese löscht erstmal das eine Bit in kex_press, denn der zugehörige Job ist ja nun in Arbeit, und er soll ja nur einmal ausgeführt werden. Dann wird der Job erledigt und die Routine verlassen. Ob man die Routine per rcall/ret aufruft oder per rjmp/rjmp, hängt von anderen Faktoren ab (z.B. Hirarchie/Wichtigkeit der einzelnen Jobs). Das Löschen aller Bits in key_press ist nur dann sinnvoll, wenn ein Taster/Schalter so hohe Priorität hat, dass alle anderen Tastendrücke annulliert werden müssen. Ansonsten löscht jede Tastenjobroutine nur ihr eigenes Bit in key_press. ...
Datum: 13.05.2005 01:36
>>Das Löschen aller Bits in key_press ist nur dann sinnvoll, wenn ein >>Taster/Schalter so hohe Priorität hat, dass alle anderen >Tastendrückeannulliert werden müssen oder, wenn man bulletproof die hektischen und unsinnigen Eingaben des Benutzers löschen möchte, weil der höchst priorisierte Job die anderen Jobs annuliert. Zb. in Key_Press stehen die Tasten für Lautstärke +, Kanalwahl 12 und Fernseher Off drinnen. Fernseher Off ist am höchsten priorisiert und löscht demzufolge die anderen tasten ebenfalls, da diese dann keinen Sinn mehr machen. Daran erkennt man: bei einen guten Code weis der Initial-Programmierer selber nicht sofort was man so alles mit dem Code in Zukunft noch anstellen kann :-) Gruß Hagen
Datum: 13.05.2005 08:24
Nundenn... Key Press wird nach der Abarbeitung gelöscht. Genauso tat ich und es war falsch, weil ich viel zu viel in der main-loop gemacht hab. Da meine Jobs aber nun mal Zeit in Anspruch nehmen (siehe Threat Timing Problem) musste ich mir Flags setzen. >Man maskiert es auch nur, wenn während eines bestimmten >Programmstatus >einige Tasten gesperrt werden müssen, was im Normalfall nicht der >Fall ist. Da bin ich ja mal gespannt, wie die Routine mein UART entprellt und meine LED's. Was spricht dagegen, den Port vor der Routine zu maskieren ??? Ich brauche nun mal nur zwei Eingänge. Das ganze ist M.E. auch niemals nicht der Grund, dass die Routine bei mir Zickt. Fakt ist, wenn immer nur 0b00001100 als maximal gelesene Eingänge kommen dürfen unten nicht 0b11111111 raus kommen oder ? Das key_press nach der Abarbeitng (hier dem kopieren) gelöscht wird, ist genau so richtig. In Peter's Routine ist es auch nicht anders. Wie gesagt, das letzte mal hab ich Mecker bekommen, weil ich den Interrupt so lange unterdrückt habe, bis alles erledigt war. Jetzt hab ich die M.E. einzige konsequenz daraus gezogen und wieder falsch :( ...
Datum: 13.05.2005 09:42
Was heißt nun "falsch"? Was in der einen Situation falsch erscheint, ist in der anderen Situation richtig. Wie Hagen richtig angemerkt hat, hat man bei diesem Profi-Code alle Optionen offen. Im Prinzip meinte ich dasselbe, habe es aber wohl nicht präziese genug formuliert. Nochmals meinen Dank an Peter für diesen Code. Ich habe meinen Beitrag eigentlich nur geschrieben, weil ich den Eindruck hatte, dass du (Carsten) noch Verständnisprobleme mit der Routine bzw. dem Umgang damit hast. Du brauchst nur 2 Bits weil du nur 2 Tasten dran hast. Also brauchst du auch nur 2 Bits in key_press auszuwerten. Machst du das im Ganzen (key_press auf 0 prüfen), dann musst du es natürlich vorher maskieren. Machst du es aber bitweise (so wie ich es beschrieb), dann soll es dich doch nicht interessieren, ob USART und die anderen Portpins die unbenutzten Bits in key_press setzen oder nicht, denn du fragst sie ja nie wieder ab. ...
Datum: 13.05.2005 09:45
Ok, da gebe ich Dir Recht. Generell sollte aber das Maskieren keine probleme für die Routine bedeuten oder ?
Datum: 13.05.2005 10:57
@Carsten: Abstrahiere doch mal... Die ISR setzt die Bits in key_press, wenn eine Taste neu gerückt wurde (Flanke). Damit wird sozusagen ein "Job" angemeldet, den das Hauptprogramm irgendwann ausführen muss. Wie du die "angemeldeten Jobs" abarbeitest und die zugehörigen "Jobflags" (also die Bits in key_press) löscht ist von Fall zu Fall unterschiedlich. Es hat (eigentlich) keine Auswirkungen auf die ISR-Routine. Bei 2 Tasten würde ich sie einzeln abfragen (sbrc) und einzeln löschen (cbr). ...
Datum: 13.05.2005 11:24
Es macht aber doch keinen Unterschied, ob ich ein eigenes Statusregister verwende oder key_press. Im Statusregister habe ich nebenbei noch andere Flags. Das Statusregister ist für mich auch ein Speicher, der mir alle 30min einen neuen Job aufdrückt, unabhängig vom Tastenzustand (Selbsthaltung). Das ist notwendig um der Benachrichtigten Person so lange Messages zu schicken, bis sie reagiert und den Reset-Knopf gedrückt hat. Anders herum wirft diese Diskussion die Frage auf, ob es sinnvoll wäre, eine Alles OK Meldung zu schicken, wenn der betreffende key-press eingang wieder weg ist. Das geht aber mit der Entprellung nicht, da die fallende Flanke nicht ausgewertet wird :( Stellt sich die Frage, wie man das einigermaßen sicher hin bekommt... ...Ein Eingang (entprellt) der ein Signal auslöst wie bei key_press. Bleibt der Eingang High, tut sich nix. Wird der Eingang nach einer gewissen Zeitspanne Low (entprellt) kommt wieder ein Signal. Aber jetzt dämmert's... Was, wenn key_press nicht gelöscht wird? Dann bleibt das auch bis zum nächsten Reset an. <<...denk...denk...>> Dann brauche ich den Speicher garnicht.
Datum: 13.05.2005 13:03
Vergiss nicht, dass dir neben key_press (den Flag, die dir sagen, dass die Tasten erneut gedrückt wurden, also inzwischen mal losgelassen waren) auch key_state zur Verfügung steht, mit dem du den aktuellen Tastenzustand auswerten kannst. Wer hindert dich daran, mit einer eigenen Routine den Flankenwechsel in key_state zu erkennen und darauf zu reagieren? Das Tastenflag in key_press stehen zu lassen halte ich für nicht so gut. Denn dann wird ja der "Job" bei der nächsten Mainloop-Runde erneut augeführt. OT: Mich stört es etwas, dass wir solche banalen Dinge in der Codesammlung diskutieren. ...
Datum: 13.05.2005 14:13
"Mich stört es etwas, dass wir solche banalen Dinge in der Codesammlung diskutieren." Ich denke mal, genau dazu ist die Codesammlung da. Es gehört natürlich etwas Disziplin dazu, die Threads bis zum Ende zu verfolgen, ob sich relevante Zusätze ergeben haben. Zum Thema Ausmaskieren: Das Ausmaskieren mit 0 bedeutet Taske gedrückt und somit ist der Zustand 255 zumindest einmalig möglich. Tritt er aber bei losgelassenen Tasten auf, dann könnte ein fehlender Pull-Up die Ursache sein oder die Register wurden nicht initialisiert. Du hast doch den Code aus dem 3. Posting genommen (das 1. ist buggy) ? Da aber die Bits völlig unabhängig voneinander arbeiten, ist ein Ausmaskieren schlichtweg unnötig, man testet einfach nicht die Bits, die kein Tasten bedeuten. Peter
Datum: 13.05.2005 14:43
Der Zustand tritt nur einmal nach dem Reset auf. Ich denke schon, dass ich das dritte Posting gnommen habe, werde ich aber nochmal prüfen. Danke erstmal
Datum: 13.05.2005 15:27
@Peter: Gut, dann brauche ich also kein schlechtes gewissen wegen Zumüllen der Codesammlung haben... ;-) ...HanneS...
Datum: 02.06.2005 15:07
Kennt ihr schon den? Einfach und genial.
// Get debounced key state, TRUE if key was pressed
BOOL AT_DebouncedKey (void)
{
static UINT16 state = 0;
state = (state<<1) | RAWKEYPRESS | 0xe000;
return (state == 0xf000);
}
Einfach periodisch aufrufen, RAWKEYPRESS ist das Portbit, 0 oder 1
Datum: 02.06.2005 15:53
@pittbull "Kennt ihr schon den? Einfach und genial." Ja kenn ich, der hat aber schon nen sooooo langen Bart: http://www.mikrocontroller.net/forum/read-4-179926.html#new Ist also weder einfach noch genial noch effizient. Peter
Datum: 02.06.2005 16:20
ach, den kanntet ihr schon :-( ich finde den jedenfalls nicht schlecht, ist hardwareunabhängig und muss nicht inner isr laufen. ist übrigens von da: http://www.ganssle.com/debouncing.pdf
Datum: 02.06.2005 16:52
Wenn ich das richtig sehe, ist er auch nicht störsicher während des gedrückt haltens. Ein einziges 1-Bit (Störimpuls) bewirkt den sofortigen Wechsel zu losgelassen. Meine Routine will aber mindestesn 4 Einsen hintereinander bis zum nicht gedrückt, entprellt also beim Drücken und beim Loslassen. "ist hardwareunabhängig und muss nicht inner isr laufen" Trifft auch voll auf meine Routine zu. Man kann sie auch im Main aufrufen und die rein zufällige Programmlaufzeit als Abtastintervall nutzen. Allerdings würde dadurch die Programmierung wesentlich umständlicher, man müßte dann nämlich nach jeder Programmänderung prüfen, ob die Laufzeit noch im optimalen Entprellinterval (2..50ms) liegt. Oder man macht es wie leider so viele: Einfach beide Augen zu und durch. Und dann eben den Kunden sich ärgern lassen, wenns doch mal prellt oder er ne Taste länger drücken muß, ehe sie reagiert. Sowas ist aber nicht mein Stil und genau deshalb nehme ich eben den Timerinterrupt. Peter
Datum: 02.06.2005 17:15
klar ist der code laufzeitabhängig. inner main loop muss man die konstanten anpassen wenn's nicht richtig funzt. um dem problem zu entgehen kann man kann ihn natürlich auch in eine timer-isr packen, geht aber auch ohne ;-) >> "ist hardwareunabhängig und muss nicht inner isr laufen" >> Trifft auch voll auf meine Routine zu. sicher nicht (wenn du diesen assemblercode neinst). der braucht schon hardwareunterstützung.
Datum: 06.06.2005 13:04
Hi, ein bißchen OT beim austesten der Routine: aber bei mir im Simulator (AVRStudio 4.11SP3) springt er bei reti wieder an die Adresse 0 und fährt damit nochmals die init routine ab. Entsprechend kann ich den Code-Schnipsel nicht nachvollziehen. Kann mir jemand auf die Sprünge helfen was da passiert? Aber zum Code: geschieht das zählen durch die xor und and Verknüpfungen? Wo kann man dies mal nachlesen? Viele Grüße Olaf
Datum: 06.06.2005 15:26
Ha, der Stackpointer wird ja benutzt - alles klar :-)
Datum: 22.08.2005 23:34
hallo hat schon jemand (ich wage es ja fast nicht zu fragen (schäm)) diese routine in basic (bascom) übersetzt bei mir klappt es leider noch nicht PeterL
Datum: 23.08.2005 01:16
Gibt es in BASCOM nicht bereits die DEBOUNCE-Anweisung? Ansonsten kann man doch wohl in BASCOM ASM einbinden, oder? ...
Datum: 10.09.2005 11:25
@Peter und alle anderen die es wissen könnten Ich versuche nun schon seit mehreren Tagen den Code aus dem 3. Post auf meinem STK500 Board mit einem 8515 zum Laufen zu bekommen... Leider ohne Erfolg. Das Prinzip des Codes ist mir (nach seitenweisem malen der Register auf Zettel gg) mittlerweile klar. Wäre dankbar für ein paar Hinweise an welchen Stellen ich anpassen muss um ihn mit meiner Hardware lauffähig machen zu können. Wenn ich mich nicht täusche dann müsste ich die Interrupts anders programmieren oder? Danke Christopher
Datum: 10.09.2005 12:25
@Christopher, der einzige Unterschied dürfte der Hardwarestack des 1200 sein, Du mußt also nur noch den Stackpointer setzen, dann sollte es laufen. Peter
Datum: 04.11.2005 21:56
Interessante Routine, das Prinzip ist aber nicht neu. Das Grundprinzip läßt sich übrigens vielseitig verwenden. Wer die Grundlagen wissen will: vertical counters: http://www.everything2.com/index.pl?node_id=1145892 http://www.dattalo.com/technical/software/pic/vertcnt.html Entprellroutine für PIC: http://www.dattalo.com/technical/software/pic/debounce.html Der eigentliche Entprellalgorithmus läßt sich übrigens auch recht einfach in Hardware implementieren: http://pdfserv.maxim-ic.com/en/ds/MAX6816-MAX6818.pdf (Figure 1, Seite 4) Evtl. interessant für Verwendung mit CPLDs/FPGAs.
Datum: 05.11.2005 11:50
@Marko "Evtl. interessant für Verwendung mit CPLDs/FPGAs." Du wirst lachen. Ich habe zuerst einen Schaltplan gezeichnet, dann so optimiert, daß möglichst wenig AND, OR, EXOR, NOT-Glieder vorkommen und erst dann habe ich den Schaltplan in Software übertragen. Daß sich sowas vertical counter nennt, habe ich dann mal im 8052-Forum gelesen. Eigentlich komisch, daß derart hocheffiziente Algorithmen so selten verwendet werden. Man sieht viel häufiger die Echtzeit vernichtende Warten im Interrupt Methode (grr). Peter
Datum: 06.11.2005 16:01
Hallo danke für deinen Code, er funktioniert sehr gut (mspgcc). Ich habe nur in der Zeile: key_press |= key_state & i; Das |= durch ein = ersetzt. Christian
Datum: 22.11.2005 23:10
Hi Allz, so ganz hab ich Peter Dannegger's grundsätzliche Vorgehensweise zur Tastenentprellung noch nicht verstanden. Vielleicht könnt ihr mir oder Peter mal auf die Sprünge helfen. 1.) Ich lasse keinen Taster mehr einen Interrupt auslösen (und warte dann einige Zeit um danach den Zustand des Tasters nochmal abzufragen). 2.) Ich definiere ein relativ kurzes Timerintervall (~10 ms). In der dazugehörigen Timer Overflow Ereignis hängt die Entprell Routine. 3.) (der Teil den ich überhaupt nicht raffe)Muss dieses Ereignis mindestens viermal eintreten (also die Routine viermal aufgerufen werden) um den vierfach entprellten Zustand festzustellen? 4.) Der gedrückte Taster wird z. B. in der Hauptschleife ständig abgefragt. In meinen Testroutinen funktioniert das ganze wunderbar. Selbst meine uralt Taster prellen überhaupt nicht. Würde mir das Simulieren eventuell helfen das ganze besser zu verstehen (hab ich bisher überhaupt noch nicht benutzt)? Wie integriere ich in sowas eine Auswertung in C wie lange ein Taster gedrückt wurde (vom Prinzip her - ich will jetzt keinen fertigen Code haben)? Fragen über Fragen... Markus
Datum: 22.11.2005 23:37
Simulation bringt auf alle Fälle was. Tastendruck-Dauer brauchte ich noch nicht. Müsste aber gehen, wenn man im Timer-Int nebenbei eine Variable (mit weiterem Vorteiler?) hochzählt, die alle Routinen als Zeit-Referenz für "langsame Dinge" nutzen können. Beim ersten Reagieren auf key_press nimmst du die momentane Zeit, dann fragst du zyklisch (in Mainloop) key_state ab und nimmst den zweiten Zeitstempel, wenn key_state zum ersten mal deaktiviert ist. Die Differenz der Zeitstempel ist dann die gewünschte Tastendruckzeit. Es gibt vermutlich aber noch elegantere Möglichkeiten... Der große Vorteil dieser Entprellung ist ja gerade das Bereitstellen von zwei Flags je Taste, eines für Zustand/Status (key_state) und eines für erneutes Drücken (key_press). Damit kann das Programm sehr flexibel darauf reagieren. ...
Datum: 24.11.2005 08:38
Hi, danke schon mal für deine Ausführungen. Hilft mir auf alle Fälle schon mal, weitere Fragen werden folgen ;-) . Markus
Datum: 24.11.2005 08:42
Um es noch mal zusammenzufassen: Wenn namhafte Hersteller diverser Fernbedienungen endlich mal Peters Algorithmus verwenden würden anstatt einfach zu pollen, dann könnte man sehr schnell bis gleichzeitig den Kanal wechseln und die Lautsträke verändern. Das nervt nämlich wirklich wenn man nach dem Kanalwechseln immer noch 1 Sekunde wartten muss bevor die nächste Eingabe erfolgen kann... :-) Hail to Peter Ich verwende auch seine Routine und bin immer wieder erstaunt über die flexible Lösung, welche er da gezeigt hat... RESPEKT
Datum: 29.12.2005 23:40
Hallo! Ich muss erstmal ganz groß Danke sagen! Die Entprell-Funktion aus der Get8key4.c51 funktioniert wunderbar. In der key_press Variable kann ich wunderbar auf den Tastendruck (0->1) reagieren. Nur hab ich jetzt natürlich wieder ein spezielles Problemchen: Ich müsste auch zusätzlich den Übergang (1->0) erkennen. Damit möchte ich unterscheiden, ob eine Taste nur kurz angetippt wurde (toggelt dann eine LED) oder länger gedrückt gehalten wird (LED soll dann solang leuchten). Der Code ist so herrlich kompakt, dass ich leider Probleme hab, ihn so genau zu verstehen, um diese Funktion hinzuzufügen. Vielleicht kann mir da jemand auf die Sprünge helfen? Viele Grüße, Daniel
Datum: 30.12.2005 10:31
mach mal aus dieser zeile; i = key_state ^ ~KEY_INPUT; einfach das: i = key_state ^ KEY_INPUT; und schon haste das umgekehrt
Datum: 31.12.2005 13:31
Hier mal ein Bespiel mit kurz/lang Erkennung: http://www.mikrocontroller.net/attachment.php/2524... Peter
Datum: 01.03.2006 11:04
Wunderbare Diskussion ! Ich habe aber noch eine weitere Frage, bis jetzt wird ja nur die "0->1: key pressing detect" in der Variable key_press gespeichert. Ich bräuchte aber auch noch die Info, wann die Taste wieder losgelassen worden ist. Hat das schon mal jemand in den obigen Code implementiert ? Gruß Ralph
Datum: 02.03.2006 14:18
Guck mal weiter oben, wenn Du für C suchst: http://www.mikrocontroller.net/forum/read-4-20549.... Die Funktion zur Erkennung läßt sich leicht von der 'press'-Erkennung herleiten.. es gibt aber auch zahlreiche Verweise zu kompletten Codes
Datum: 25.10.2006 09:05
Hallo Peter, ich habe mich mal eine Weile mit Deiner Routine zum Entprellen von Tasten beschäftigt. Größtenteils habe ich sie auch verstanden und nachvollzogen, sie ist nun also schon im Einsatz. Es würde mich allerdings interessieren, ob es dazu auch eine Variante gibt, die all diese Funktionen auch noch bei der Entprellung einer Tastenmatrix (ich suche beispielsweise eine 4 x 8 Matrix Entprellung) ermöglicht. Und da würde ich halt auch gern auf die interrupt-gesteuerte Lösung zurück greifen. Ich dachte ich frage mal nach, bevor ich mir einen Knoten in den Kopf denke ... Man muss das Rad ja nicht immer neu erfinden, und außerdem kann man ja auch mal versuchen von den Fehlern der Anderen zu lernen. Viele Grüße Michael P.s.: Super Homepage und super Forum so allgemein! Und vielen Dank für den Entprellcode. Gutes Werk! P.p.s.: In der Zeile TCCR0 = (1<<CS02 | 1<<CS01 | 1<<CS00); // divide by 1024 habe ich noch CS01 setzen müssen, damit mein ATMega64 auch den 1024er Teiler nutzt. Wird wohl an unterschiedlichen MCs liegen. Ach noch etwas: Zur Erkennung ob eine Taste wieder losgelassen wurde, habe ich die ISR Routine (ehemals Signal) in größeren Teilen kopiert, also mit abgewandelten Variablennamen. Oder kann man die entprellte abfrage auch gleich aus dem Code entnehmen? Und eigentlich müsste man doch durch nutzen von ISR auf cli(); und sei(); verzichten können, oder? Was ja anbei noch gut wäre, da man nicht ausversehen ein nicht gesetztes Interrupt Hauptbit setzt, obwohl es vorher ungesetzt war.
Datum: 23.02.2007 10:14
Hallo Peter, ich nutze deine Routine mit Lang/kurz-erkennung recht gern und viel, muss aber zugeben, dass ich noch nicht ganz durchgestiegen bin ;-) Bisher hat das aber nie zu Problemen geführt. Jetzt hab ich aber mal eine Anwendung wo ich die Taster an PortG vom Mega64 angeschlossen habe. 5 Taster an PG.0-PG.4 Die Funktionen get_key_press und get_key_short funktionieren bei allen Tasten auch einwandfrei, aber get_key_long und get_key_rpt funktionieren nur an PG.1 und PG.2. Ich hab schon alles aus dem Programm rausgenommen was irgenwie stören könnte, aber ich komm einfach nicht dahinter... ist das vieleicht Prinzipbedingt, da der PortG ja nur 5 Bits hat? Wenn ja, warum gehn dann grad bit 1 und 2? Hoffe auf Erleuchtung. Gruß Fabian
Datum: 23.02.2007 20:06
Fabian B. wrote: > Die Funktionen get_key_press und get_key_short funktionieren bei allen > Tasten auch einwandfrei, aber get_key_long und get_key_rpt funktionieren > nur an PG.1 und PG.2. Hast Du Dir mal diese Zeile angesehen ? .
#define REPEAT_MASK (1<<KEY1^1<<KEY2) // repeat: key1, key2 |
Peter
Datum: 24.02.2007 13:18
Keine Fragen mehr... asche-auf-mein-haupt die hab ich in der Tat nicht gesehn...aber die ist dann auch für das long-delay zuständig, richtig? Danke, Peter! Gruß Fabian
Datum: 02.03.2007 19:46
hallo zusammen, ich bin noch ganz ganz neu auf dem gebiet avr mc. habe noch nichmal das equiptment hoffe aber das es die nächsten tage bald mit der post kommt ;) naja wollte aber vorher schonmal anfangen ein testprogramm mit leds und schaltern zu schreiben und bin dann über die routine von Peter Dannegger gestossen. mir ist beim code aufgefallen das es "fälle" gibt wo die routine sprich overflow interrupt nicht 4 mal durchlaufen wird sondern das es bis zu 7 durchläufen brauch bis key_state und key_press richtig gesetzt sind. ich benutze mal dezimalzahlen und beziehe mich auf den c-code der routine http://www.mikrocontroller.net/articles/Entprellung und nehme ein beispiel wo die routine 7 mal durchlaufen wird z.B. ich gehe mal von der situation aus das : KEY_PIN = 1 ist was bedeutet alle taster bis auf pin0 gesetzt. wenn nun KEY_PIN während 3 interrupt overflow auf 3 steht (taster pin0 u. pin1 nicht gesetzt) und beim nächsten overflow auf 2 (taster pin1 nicht gesetzt), wechselt key_state auf 3 und nicht auf 2. erst nach 3 weitereren interrupt durchläufen wechselt key_state auf 2 und setzt key_press wie gewollt. das geht auch mit anderen fällen wobei der durchlauf von 5 bis max 7 liegen kann je nachdem wielang der kurze KEY_PIN wechsel anliegt. kann das einer bestättigen oder definitiv verneinen? ich bin linux benutzer und kann das ganze leider nich ausführlich in avrstudio testen. hier noch von meiner seite grossen respekt an peter dem dieser algorithmus eingefallen
Datum: 02.03.2007 20:41
Ich kenne ja nur die Assembler-Routine, muss aber sagen, dass sie exakt funktioniert. Dezimale "Zahlenwerte" (Zählerstände) nützen Dir beim Versuch, den Algorithmus nachzuvollziehen nichts, da die beiden Bits des jeweiligen Prellzählerstandes in zwei verschiedenen Registern liegen. Denn jedes involvierte Byte enthält ja die Bits von 8 zu entprellenden Eingängen parallel. MfG
Datum: 02.03.2007 21:08
ich benutze nur dezimal in meinem beispiel um die schreibweise 0b00000001 für dezimal 1 und 0b00000011 für 3 usw zu verkürzen.
Datum: 02.03.2007 22:30
Hast Du beim "Testen" auch auf die Variablen ct0 und ct1 geachtet? Die verkörpern nämlich den eigentlichen Prellzähler. Das sind zwei 8-Bit-Variablen, von denen jede je ein Bit der acht vorhandenen Zweibit-Zähler enthält. Wenn Du meinst, dass Du auf AVR-Studio verzichten möchtest weil... Naja, das ist nicht mein Problem. MfG
Datum: 03.03.2007 00:51
danke erstmal Werbungverschmäher für deine flotten antworten. ja auf ct0 und ct1 habe ich geachtet denn sonst würde nix passieren. ich versuche das ganze mal zu erklären so wie ich mir vorstelle das diese routine funktioniert: KEY_PIN eingabe : 255 i='0' ct0='255' ct1='255' i='0' key_state='0' key_press='0' KEY_PIN eingabe : 254 i='1' ct0='254' ct1='255' i='0' key_state='0' key_press='0' KEY_PIN eingabe : 254 i='1' ct0='255' ct1='254' i='0' key_state='0' key_press='0' KEY_PIN eingabe : 254 i='1' ct0='254' ct1='254' i='0' key_state='0' key_press='0' KEY_PIN eingabe : 254 i='1' ct0='255' ct1='255' i='1' key_state='1' key_press='1' KEY_PIN eingabe : 254 i='0' ct0='255' ct1='255' i='0' key_state='1' key_press='0' so nun benutze ich mal ein anderes beispiel bei dem 7 durchläufe statt 4 vorkommen: KEY_PIN eingabe : 254 i='0' ct0='255' ct1='255' i='0' key_state='1' key_press='0' KEY_PIN eingabe : 252 i='2' ct0='253' ct1='255' i='0' key_state='1' key_press='0' KEY_PIN eingabe : 252 i='2' ct0='255' ct1='253' i='0' key_state='1' key_press='0' KEY_PIN eingabe : 252 i='2' ct0='253' ct1='253' i='0' key_state='1' key_press='0' KEY_PIN eingabe : 253 i='3' ct0='254' ct1='255' i='2' key_state='3' key_press='2' KEY_PIN eingabe : 253 i='1' ct0='255' ct1='254' i='0' key_state='3' key_press='0' KEY_PIN eingabe : 253 i='1' ct0='254' ct1='254' i='0' key_state='3' key_press='0' KEY_PIN eingabe : 253 i='1' ct0='255' ct1='255' i='1' key_state='2' key_press='0' KEY_PIN eingabe : 253 i='0' ct0='255' ct1='255' i='0' key_state='2' key_press='0' entweder liege ich total falsch oder er registriert erst nach 7 ints das richtige key_state
Datum: 03.03.2007 01:25
Also C ist nicht mein Ding. Ich nutze die Tastenentprellung nach Peter Dannegger in ASM in verschiedenen Varianten und Modifikationen. Hier eine Version, die neben dem entprellten Tastenzustand nur die Flanken des Tastendruckes erfasst:
Tastenabfrage: ;Entprellroutine geklaut bei Peter Dannegger... in scan,tap ;Tastenport einlesen (gedrückt=L) com scan ;invertieren (gedrückt=H) eor scan,tas ;nur Änderungen werden H and tz0,scan ;Prellzähler unveränderter Tasten löschen (Bit0) and tz1,scan ;Prellzähler unveränderter Tasten löschen (Bit1) com tz0 ;L-Bit zählen 0,2,->1, 1,3,->0 eor tz1,tz0 ;H-Bit zählen 0,2,->tz1 toggeln and scan,tz0 ;Änderungen nur dann erhalten, wenn im Prellzähler and scan,tz1 ;beide Bits gesetzt sind (Zählerstand 3) eor tas,scan ;erhaltene Änderungen toggeln alten (gültigen) Tastenstatus and scan,tas ;nur (neu) gedrückte Tastenbits bleiben erhalten or tfl,scan ;und zugehörige Bits setzen (gelöscht wird nach Abarbeitung) ;in "tas" steht jetzt der gültige Tastenzustand, ;in "tfl" die Flags der neu gedrückten, noch nicht abgearbeiteten Tasten... |
Diese Variante arbeitet mit Registern und im Interrupt. Gelegentlich nutze ich diesen Algorithmus auch mit temporären Registern in der Mainloop und halte die Variablen im SRAM. Mit kleinen Zusätzen lässt sich auch (nur oder zusätzlich) das Loslassen der Taster erfassen und / oder auch ei Repeat bei Dauerdruck. Da dieser Algorithmus mit 2-Bit-Prellzählern (bei mir tz0 und tz1) arbeitet, muss ein veränderter Eingangspegel (also Taster gedrückt oder losgelassen) exakt 4 mal stabil erfasst worden sein, damit die Änderung übernommen wird, sich also der Wert von tas ändert. Da der Controller nur Maschinencode (ASM) kann, der C-Code auch nur in ASM (bzw. Maschinencode) übersetzt wird, solltest Du das mal in ASM durchspielen. MfG
Datum: 03.03.2007 10:37
theend wrote: > KEY_PIN eingabe : 255 > i='0' ct0='255' ct1='255' i='0' key_state='0' key_press='0' Ist ja schön, daß Du die Dezimalentsprechung jedes Bitmusters im Kopf hast, ich nicht. Wenn Du also willst, daß man Deine Frage nachvollziehen kann, solltest Du besser alle Werte binär hinschreiben. Aber wie Werbungverschmäher schon richtig sagte, es reicht völlig, sich nur ein Bit anzugucken. Die logischen Operatoren beeinflussen nur die gleichwertigen Bits und lassen alle anderen unverändert. Es kann sein, daß Du das Einschaltverhalten meinst, da habe ich mich nicht drum gekümmert. Wichtig ist ja nur, daß nicht fälschlich ein Druck gespeichert wird. Man kann auch die Initialisierung so ändern, daß erst die Taste losgelassen werden muß, d.h. ein Druck schon während des Reset keine Aktion auslöst. Peter
Datum: 04.03.2007 14:00
@peter nein die initialisierung is schon durch siehe i=0 u. ct0,ct1 = 255 so dann hier noch mal die mundgerechte version in der hoffnung das es dann mal angeschaut wird ohne gleich ratschläge zu geben: KEY_PIN eingabe : 11111111 i='0' ct0='11111111' ct1='11111111' i='0' key_state='0' key_press='0' KEY_PIN eingabe : 11111110 i='1' ct0='11111110' ct1='11111111' i='0' key_state='0' key_press='0' KEY_PIN eingabe : 11111110 i='1' ct0='11111111' ct1='11111110' i='0' key_state='0' key_press='0' KEY_PIN eingabe : 11111110 i='1' ct0='11111110' ct1='11111110' i='0' key_state='0' key_press='0' KEY_PIN eingabe : 11111110 i='1' ct0='11111111' ct1='11111111' i='1' key_state='1' key_press='1' KEY_PIN eingabe : 11111110 i='0' ct0='11111111' ct1='11111111' i='0' key_state='1' key_press='0' so nun benutze ich mal ein anderes beispiel bei dem 7 durchläufe statt 4 vorkommen: KEY_PIN eingabe : 11111110 i='0' ct0='11111111' ct1='11111111' i='0' key_state='1' key_press='0' KEY_PIN eingabe : 11111100 i='10' ct0='11111101' ct1='11111111' i='0' key_state='1' key_press='0' KEY_PIN eingabe : 11111100 i='10' ct0='11111111' ct1='11111101' i='0' key_state='1' key_press='0' KEY_PIN eingabe : 11111100 i='10' ct0='11111101' ct1='11111101' i='0' key_state='1' key_press='0' KEY_PIN eingabe : 11111101 i='10' ct0='11111110' ct1='11111111' i='10' key_state='11' key_press='10' KEY_PIN eingabe : 11111101 i='1' ct0='11111111' ct1='11111110' i='0' key_state='11' key_press='0' KEY_PIN eingabe : 11111101 i='1' ct0='11111110' ct1='254' i='0' key_state='11' key_press='0' KEY_PIN eingabe : 11111101 i='1' ct0='11111111' ct1='11111111' i='1' key_state='10' key_press='0' KEY_PIN eingabe : 11111101 i='0' ct0='11111111' ct1='11111111' i='0' key_state='10' key_press='0'
Datum: 04.03.2007 14:57
> so dann hier noch mal die mundgerechte version in der hoffnung das es > dann mal angeschaut wird ohne gleich ratschläge zu geben: Muss ich das jetzt verstehen? Mir brauchst Du nix "mundgerecht" zu machen, ich komme auch ohne Deine Aktivitäten zurecht. Wer sucht Hilfe, Du oder wir? Wir wissen, dass der Algorithmus korrekt ist, ich nutze ihn mangels C-Kenntnissen aber in Assembler. Ich habe ihn mittels AVR-Studio-Simulator auch überprüft. Er funktioniert! Du verwirrst Dich selbst unnötig, indem Du die Variablen als Ganzes betrachtest.. Es wird übersichtlicher, wenn Du nur mit einem Taster "spielst" und nur ein Bit (das des Tasters) beachtest. Denn in allen involvierten Variablen wird pro Taster nur ein Bit verwendet, und zwar das mit der Wertigkeit des Anschlusspins, an dem der Taster angeschlossen ist. Das Programm kann bis zu 8 Taster parallel behandeln, jeder Taster nutzt aber in jeder beteiligten Variable nur 1 Bit. Um das zu verstehen, musst Du Deine numerische Sicht auf die Bariablen mal deaktivieren, es sind keine Zahlen, es sind Bitmuster. Zahlen (z.B. die 8 Prellzähler) entstehen durch gemeinsames Betrachten der gleichwertigen Bits in unterschiedlichen Variablen. Nimm AVR-Studio und simuliere die ASM-Routine durch, dann siehst Du es. ...
Datum: 04.03.2007 17:10
theend wrote: > so nun benutze ich mal ein anderes beispiel bei dem 7 durchläufe statt 4 > vorkommen: Ich kann da nirgends 7 Durchläufe erkennen: Nach dem 1. Lauf setzt Du KEY_PIN.1 = 0 und voila, nach dem 5. ist keystate.1 = 1, also 5-1 = 4 Nach dem 4.Lauf setzt Du KEY_PIN.0 = 1 und voila, nach dem 8. ist keystate.0 = 0, also wieder 8-4 = 4 Ist in der Binärschreibweise sehr schön zu erkennen. Peter
Datum: 21.06.2007 17:34
alles toll, aber das Problem bleibt
ich kann selten immer 8 Bits eines Ports verwenden, einige Bits sind
immer reserviert, um trotzdem diese Routine zu nutzen hab ich sie
gedoppelt
-------------------------------------------------
#define KEY_PORT PORTC
#define KEY_PIN PINC
#define KEY_PIN2 PIND
#define KEY1 2
#define KEY2 3
#define KEY3 4
#define KEY4 5
#define KEY5 6
#define KEY6 7
#define KEY7 1
-------------------------------------------------
i = key_state ^ ~KEY_PIN; // key changed ?
ct0 = ~( ct0 & i ); // reset or count ct0
ct1 = ct0 ^ (ct1 & i); // reset or count ct1
i &= ct0 & ct1; // count until roll
over ?
key_state ^= i; // then toggle
debounced state
key_press |= key_state & i; // 0->1: key press
detect
if( (key_state & REPEAT_MASK) == 0 ) // check repeat
function
rpt = REPEAT_START; // start delay
if( --rpt == 0 )
{ rpt = REPEAT_NEXT; // repeat delay
key_rpt |= key_state & REPEAT_MASK;
}
i2 = key_state2 ^ ~KEY_PIN2; // key changed ?
ct02 = ~( ct02 & i2 ); // reset or count
ct0
ct12 = ct02 ^ (ct12 & i2); // reset or count
ct1
i2 &= ct02 & ct12; // count until roll
over ?
key_state2 ^= i2; // then toggle
debounced state
key_press2 |= key_state2 & i2; // 0->1: key press
detect
-------------------------------------------------
uint8_t get_key_press( uint8_t key_mask )
{ static uint8_t key_mask2;
cli(); // read and clear
atomic !
key_mask2=key_mask;
key_mask &= key_press; // read key(s)
key_press ^= key_mask; // clear key(s)
key_mask2 &= key_press2; // read key(s)
key_press2 ^= key_mask2; // clear key(s)
sei();
return (key_mask || key_mask2);
}
-------------------------------------------------
wüsste gerne ob es eine bessere Möglichkeit gibt verschiedene Ports zu
nutzen und davon jeweils nur die von Tasten benutzten Bits zu maskieren,
damit andere Signale keine Taste simulieren
Datum: 21.06.2007 19:11
jar wrote: > alles toll, aber das Problem bleibt > > ich kann selten immer 8 Bits eines Ports verwenden, einige Bits sind > immer reserviert, um trotzdem diese Routine zu nutzen hab ich sie > gedoppelt Ich sag mal, das geht so nicht. Mach es doch so: Angenommen, KEY1,2,3 sind auf PINC:
#define KEY_PIN PINC #define KEY1 2 #define KEY2 3 #define KEY3I 4 //input bit #define KEY3 0 //reading bit #define KEY_PIN2 PIND #define KEY4 5 #define KEY5 6 #define KEY6 7 #define KEY7 1 #define KEY8 4 ... i = KEY_PIN2 & (1<<KEY4^1<<KEY5^1<<KEY6^1<<KEY7^1<<KEY8); if(KEY_PIN & (1<<KEY1)) i |= 1<<KEY1; if(KEY_PIN & (1<<KEY2)) i |= 1<<KEY2; if(KEY_PIN & (1<<KEY3I)) i |= 1<<KEY3; i ^= ~key_state; // key changed ? ct0 = ~( ct0 & i ); // reset or count ct0 usw. |
Peter
Datum: 21.06.2007 19:41
>Ich sag mal, das geht so nicht. bis jetzt lief es so, >Mach es doch so: aber toll finde ich meine Version ja nicht, dein Vorschlag probiere ich, bzw. vielleicht denke ich auch selber noch mal nach Problem war vorher die Statements KEY_PIN -> PINC PIND , die konnte ich nicht maskieren weil sie direkt auch die Register wirken Edit: sorry, so lief es natürlich nicht, aber so: uint8_t get_key_press( uint8_t key_mask ) { static uint8_t key_mask2; cli(); // read and clear atomic ! key_mask2=key_mask; key_mask &= key_press; // read key(s) key_press ^= key_mask; // clear key(s) key_mask2 &= key_press2; // read key(s) key_press2 ^= key_mask2; // clear key(s) sei(); return ((key_mask&(1<<KEY1 | 1<<KEY2 | 1<<KEY3 | 1<<KEY4 | 1<<KEY5 | 1<<KEY6)) | (key_mask2&(1<<KEY7))); }
Datum: 21.06.2007 20:27
so gefällt mir das besser :-)
ich hoffe das ist ohne Nebenwirkungen, aber im Moment läufts
für
#define KEY_PORT PORTC
#define KEY_PIN PINC
#define KEY_PIN2 PIND
#define KEY1 2
#define KEY2 3
#define KEY3 4
#define KEY4 5
#define KEY5 6
#define KEY6 7
#define KEY7 1
#define ALL_KEYS (1<<KEY1 | 1<<KEY2 | 1<<KEY3 | 1<<KEY4 | 1<<KEY5
| 1<<KEY6)
i = key_state ^ ( (~KEY_PIN&(1<<KEY1 | 1<<KEY2 | 1<<KEY3 \
| 1<<KEY4 | 1<<KEY5 | 1<<KEY6)) \
|(~KEY_PIN2&(1<<KEY7))); // key changed ?
ct0 = ~( ct0 & i ); // reset or count ct0
ct1 = ct0 ^ (ct1 & i); // reset or count ct1
i &= ct0 & ct1; // count until roll over ?
key_state ^= i; // then toggle debounced state
key_press |= key_state & i; // 0->1: key press detect
if( (key_state & REPEAT_MASK) == 0 ) // check repeat function
rpt = REPEAT_START; // start delay
if( --rpt == 0 )
{ rpt = REPEAT_NEXT; // repeat delay
key_rpt |= key_state & REPEAT_MASK;
}
Datum: 22.06.2007 10:24
Mal ne generelle Frage unabhängig der hier vorgestellten Methoden: Warum per Software entprellen ?? Ich habe mal gelernt, dass man bei einfachen Schaltungen (und bei Schaltungen ohne µC eh nicht anders lösbar) die Tasten elektronisch entprellt .... Das müsste doch per Kondensator oder so recht günstig gehen ....
Datum: 22.06.2007 10:56
> Warum per Software entprellen ??
Gerät in der Massenproduktion mit vier Tastern:
Du willst 100.000 Stück davon bauen und verkaufen. Ein RC-Glied pro
Taster kostet etwa 4 Cent. Davon 4 Stück pro Gerät mal 100.000 =
16.000,- Euro.
Dabei sind die Kosten für Bestückung, Lagerhaltung, höhere Ausfallrate
(mehr Bauteile!), größere Platinenfläche etc. noch gar nicht
eingerechnet.
Alternative: Software. Eine halbe Stunde für einen Programierer: 100,-
Euro.
Datum: 22.06.2007 10:57
Ergänzung, für jedes eingesparte Bauteil, entflechten, verdrahten, löten ist der Hobbyist dankbar und wenn eh ein MC on Board ist , wieso nicht per SW ?
Datum: 22.06.2007 11:29
Der Hubert wrote: > Mal ne generelle Frage unabhängig der hier vorgestellten Methoden: > > Warum per Software entprellen ?? > > Ich habe mal gelernt, dass man bei einfachen Schaltungen (und bei > Schaltungen ohne µC eh nicht anders lösbar) die Tasten elektronisch > entprellt .... > > Das müsste doch per Kondensator oder so recht günstig gehen .... Entprell-Hardware muss man jedesmal neu kaufen. Entprell-Software kann man wiederverwenden (kopieren). ...
Datum: 22.06.2007 16:21
Ok ok, .... Ich wusste nicht, dass Ihr gleich 100.000 Stück baut .... gg
Datum: 22.06.2007 16:37
Der Hubert wrote:
> Ich wusste nicht, dass Ihr gleich 100.000 Stück baut .... *gg*
Die Routine ist ja schon fertig, also lohnt es sich schon ab dem ersten
Stück.
Und außerdem macht sich noch viel mehr, als nur entprellen.
Peter
Datum: 24.06.2007 21:45
Ich tue in der zwischenzeit komplett alle Ports immer über Interrupts einlesen. Dann habe ich globale Variablen mit dem namen PORTX_STATE PORTX_PRESS PORTX_UNPRESS für jeden PORT. Und habe dann alle Eingänge schön entprellt.
Datum: 29.06.2007 16:58
Angehängte Dateien:Hallo, dank Anfängertum und Brett vorm Kopf hab ich natürlich von diesem Assamblercode erstmal nur Bahnhof verstanden. Erstmal überhaupt vielen Dank dafür. Da ich zu faul bin, alles auf dem Papier nachzuvollziehen und ich im Simulator zu wenig Überblick hatte, hab ich mal eine kleine Excel-Tabelle gemacht, wo man 9 Durchläufe berechnen lassen kann und alle variablen Verfolgen kann. Vielleicht hifts außer mir ja noch jemandem. Unter Excel hat das bei mir wunderbar geklappt mit der Tabelle, wenn mir beizeiten jemand verrät, warum das bei OpenOffice nicht will, wäre ich sehr dankbar. MfG Sönke
Datum: 30.06.2007 16:11
Angehängte Dateien:Nachdem ich nochmal mit Open Office diskutiert habe, läuft das jetzt auch unter Calc.
Datum: 01.07.2007 11:20
Hallo, ich verwenden auch den Code hier, funktioniert wirklich gut. Prozessor ARM7 (LPC2148). Mein Problem, beim ARM hat man ja nur 2 Ports mit je 32 I/O´s ich habe jetzt einfach den code auf uint32_t geändert, es funktioniert auch aber gibts auch etwas eleganteres. Kann man die Pins die man möchte auch irgendwie zusammen mappen auf ein Byte (meisten braucht man ja nicht mehr wie 8 Taster)? Wenn alle nacheinander sind ginge es ja, aber wenn ich zum Beispiel - P0.14, P0.15, P0.30, P0.1 usw. verwenden will sind diese ja über die 32 I/O´s verteilt, sehe da keine ander Möglichkeit bis jetzt. Wie so muss man bei den Interrupt immer ein und auschalten, reicht es nicht wenn man nur die paar Zeilen mit einem Flag ein und ausschaltet? Wenn man zum Beispiel mit dem Interrupt auch einen digitalen Regler triggern möchte ist das etwas problematisch. Gibts auch eine ander Lösung? danke
Datum: 01.07.2007 15:12
mgiaco wrote: > Wie so muss man bei den Interrupt immer ein und auschalten, reicht es > nicht wenn man nur die paar Zeilen mit einem Flag ein und ausschaltet? > Wenn man zum Beispiel mit dem Interrupt auch einen digitalen Regler > triggern möchte ist das etwas problematisch. Gibts auch eine ander > Lösung? Nein. Die Interuptsperre macht die 2 aufeinanderfolgende Befehle atomar, das sollte auf nem ARM7 weit unter 1µs dauern. Wenn selbst so extrem kurze Sperren stören, dann ist an Deinem Programmaufbau etwas grungsätzlich falsch bzw. derart zeitkritische Sachen sollte dann ein CPLD oder FPGA machen. Peter
Datum: 01.07.2007 16:39
okay funktionieren tut es aber trotzdem, wird aber dann mal zu einem Fehler führen oder. Aber zu dem andern Problem gibts wohl nichts anderes oder?
Datum: 01.07.2007 17:18
Habe es jetzt so gemacht funktionier weis nur nicht ob das richtig ist! Das mit den IRQ´s ist ja nicht so einfach wie bei den AVR´s.
uint32_t get_key_press(uint32_t key_mask )
{
unsigned cpsr;
//cli();
cpsr = disableIRQ(); // disable global interrupts
T0MCR &= ~(TMCR_MR0_I); // disable MR0 interrupt
restoreIRQ(cpsr); // restore global interrupts
key_mask &= key_press; // read key(s)
key_press ^= key_mask; // clear key(s)
//sei();
cpsr = disableIRQ(); // disable global interrupts
T0MCR |= TMCR_MR0_I; // enable MR0 interrupt
restoreIRQ(cpsr); // restore global interrupts
return key_mask;
}
|
danke
Datum: 01.07.2007 18:42
mgiaco wrote:
>
> uint32_t get_key_press(uint32_t key_mask )
> {
> unsigned cpsr;
>
> //cli();
> cpsr = disableIRQ(); // disable global interrupts
> T0MCR &= ~(TMCR_MR0_I); // disable MR0 interrupt
> restoreIRQ(cpsr); // restore global interrupts
>
> key_mask &= key_press; // read key(s)
> key_press ^= key_mask; // clear key(s)
>
> //sei();
> cpsr = disableIRQ(); // disable global interrupts
> T0MCR |= TMCR_MR0_I; // enable MR0 interrupt
> restoreIRQ(cpsr); // restore global interrupts
>
> return key_mask;
> }
> |
Kommst Du Dir dabei nicht selber albern vor ? Du machst 2 Funktionsaufrufe und dazwischen eine Codezeile:
//cli();
cpsr = disableIRQ(); // disable global interrupts
T0MCR &= ~(TMCR_MR0_I); // disable MR0 interrupt
restoreIRQ(cpsr); // restore global interrupts
|
Und das soll merkbar Zeit einsparen gegenüber 2 Funktionsaufrufen und 2 Codezeilen:
//cli();
cpsr = disableIRQ(); // disable global interrupts
key_mask &= key_press; // read key(s)
key_press ^= key_mask; // clear key(s)
//sei();
restoreIRQ(cpsr); // restore global interrupts
|
Die Interruptdisablezeit dürfte gerade mal einen Zyklus länger sein, da ja die Variablen für die 2. Codezeile schon in Registern sind. In den Codezeilen ist doch keine float-Division, die Zeit kostet, sondern nur simple Logikbefehle. Peter
Datum: 02.07.2007 10:07
Okay stimmt, ich wollte nur sicher gehen und nur den Timer abschalten, und die IRQ's gleich wieder aktivieren. Danke.
Datum: 02.07.2007 11:23
mgiaco wrote: > Okay stimmt, ich wollte nur sicher gehen und nur den Timer abschalten, Warum Timer abschaslten? Das ist doch Fälschung des Timings. Dann kann man doch gleich ohne Timer arbeiten... > und die IRQ's gleich wieder aktivieren. > > Danke. Ich glaube, Du solltest versuchen, die Rourtine zu verstehen, ehe Du sie modifizierst. ...
Datum: 02.07.2007 18:49
Hallo Hannes, Ich meinen natürlich den Interrupt des Timers nicht den Timer selbst, schon klar. Das habe ich doch oben auch gemacht denke ich, ich habe ja nur den MR0 IRQ ausgeschalten --> wäre das nicht korrekt so? Hab nochmals im Manual nachgesehen wüsste nicht wie man den IRQ sonst abschaltet falls man das mal möchte. >Ich glaube, Du solltest versuchen, die Rourtine zu verstehen, ehe Du sie >modifizierst. Hast natürlich volkommen recht, dennoch weis ich nicht was du genau meinst denn das Beispiel von oben funktioniert, nur ist es wie Herr Danneger geschrieben hat vollig unsinnig was das Timing betrift. Deshalb mach ich es jetzt auch so wie vorgeschlagen.
Datum: 20.07.2007 13:46
ich habe aus Portmangel nun auch 'I2C Tastem mit dem PCF8574 umgestellt nun grübel ich wie ich diese Routine mit dem I2C verheirate... ich mache im 10ms Timerinterrpt die CT0 und CT1 Verarbeitung aber klappt das auch mit I2C lesen ? wegen CLI()SEI() ? als I2C Lib nutze ich die von Peter Fleury könnte dafür etwas Erkennungscode für PCF8574 (0x40) oder PCF8574A beisteuern (0x70), hat mich einen halben Tag Fehlersuche gekostet, ich dachte der Unterschied ist N zu AP aber auch N mit A ist 0x70 und dann wieder das richtiige Datenblatt finden wenn man die goldene Schrift auf dem Stein kaum lesen kann, aber tastenlesen klappt nun nur noch nicht entprellt....
Datum: 20.07.2007 14:03
Ich nutze (in ASM) diesen Algorithmus im Mega32 für 8 (lokale) Tasten an einem Port und 32 Tasten (im ganzen Haus verteilt), die über SPI und Schieberegister eingelesen werden. Dem Algorithmus (der Routine) ist es ja egal, wo die Daten herkommen, anstatt einen Port einzulesen (und ggf zu invertieren) kann man ja auch eine Variable kopieren, die über irgendeine Schnittstelle eingelesen wurde. ...
Datum: 20.07.2007 15:02
Hannes Lux wrote: > Dem Algorithmus (der Routine) ist es ja egal, wo die Daten herkommen, > anstatt einen Port einzulesen (und ggf zu invertieren) kann man ja auch > eine Variable kopieren, die über irgendeine Schnittstelle eingelesen > wurde. stimmt, ich war unsicher wegen CLI() und SEI() , klappt aber fein: if(_i2c_key) { if(_i2c_key=='A') { if(!i2c_start(PCF8574A_0+I2C_READ)) //; // set device address and write mode i = key_state ^ ( ( ~(unsigned char)i2c_readNak() << 1) ); } else { if(!i2c_start(PCF8574_0+I2C_READ)) //; // set device address and write mode i = key_state ^ ( ( ~(unsigned char)i2c_readNak() << 1) ); } i2c_stop(); } else i = key_state ^ ~KEY_PIN; // key changed ? ct0 = ~( ct0 & i ); // reset or count ct0 . . . usw. wie schon bekannt
Datum: 20.07.2007 15:19
Joachim B. wrote:
> stimmt, ich war unsicher wegen CLI() und SEI() , klappt aber fein:
Nunja, cli und sei habe ich nicht drin. Erstens mach' ich's in
Assembler, zweitens mach' ich's 8-bittig. Da entsteht kein
16-Bit-Portzugriff, den ich atomar behandeln muss.
Oftmals nutze ich die Routine (mit Exklusiv-Registern) in der Timer-ISR,
oft aber auch als zyklischen Job der Mainloop (per Timer-Int und
Semaphore), dann meist mit temporären Registern und im SRAM gehaltenen
Variablen.
...
Datum: 19.11.2007 11:29
Hallo Leute, ich finde den Code schön kompakt (auch wenn man erst eine Weile nachdenken muss, bevor man ihn versteht :-). Ich hatte das Problem, auch das Loslassen einer Taste erkennen zu wollen, und habe daher folgende Ergänzung ersonnen (braucht ein weiteres Register). Lieben Gruß, Wiener Würstchen
#define KEY_INPUT P2 char key_state, key_press; /* neue Zeile hier... char key_release; */ void to_int( void ) interrupt INT_T0 { static char ct0, ct1; char i; i = key_state ^ ~KEY_INPUT; // key changed ? ct0 = ~( ct0 & i ); // reset or count ct0 ct1 = ct0 ^ ct1 & i; // reset or count ct1 i &= ct0 & ct1; // count until roll over key_state ^= i; // then toggle debounced state key_press |= key_state & i; // 0->1: key pressing detect /* neue Zeile hier... key_release |= ~key_state & i; // 0->1: key release detect */ } |
Datum: 01.07.2008 14:14
Hallo Leute, ich weiß zwar nicht, ob das Thema bereits geschlossen ist, aber ich hab da eine Frage. Ich benutze diese Entprellung ebenfalls. Gedacht ist es für eine "selbstgebaute" Fernbedienung. Ich will eine Art Steuerkreuz bauen, d.h. wenn man z.b. links und oben gleichzeitig drückt, soll es Diagonal gehen, aber die Entprellung erkennt die Tasten nur einzeln. also wenn ich eine ausgabe mache kommt erst oben und dann links, oder umgekehrt. Woran kann das liegen? ich verwende einen AT90CAN128 und arbeite mit AVR-Studio 4 und WIN-AVR
Datum: 01.07.2008 15:00
Die C-Version kenne ich nicht im Detail, bei der ASM-Version kannst auch auf Tastenkombinationen prüfen, das wird aber in C nicht anders sein. In Key_press stehen die Bits der Tasten, die (seit dem letzten Loslassen) erneut gedrückt wurden. Diese nutzt (und löscht) man, wenn man jeden Tastendruck nur einmal auswerten will/muss. In Key_state stehen die entprellten Tastenzustände der Tasten. Diese kann man nutzen, wenn man Aktionen auslösen will, solange die Taste gedrückt ist. Für eine Fernbedienung, deren Empfänger solange etwas tun soll, wie eine Taste am Sender betätigt ist, wäre das der bessere Weg. Man kann die einzelnen Bits in Key_state oder Key_press aber auch gruppenweise auswerten. Man nimmt (zumindest in ASM) eine Kopie (damit der Original-Wert durch die Gruppenbildung nicht verändert wird) der Variable, maskiert die gewünschten Bitmuster aus und prüft das Ergebnis auf Vollständigkeit. ...
Datum: 01.07.2008 15:31
Da mit der Routine alle Tasten parallel ausgewertet werden, gibt es keine Priorisierung, bspw. in der Form, dass bei zwei exakt gleichzeitig gedrückten Tasten immer eine bestimmte der beiden zuerst ausgewertet wird. Allerdings wirst es nur dann schaffen, zwei Tasten (aus Sicht der Routine) "gleichzeitig" zu drücken, wenn die Zykluszeit, mit der die Routine aufgerufen wird sehr groß ist. Ansonsten wird praktisch immer eine Zeitdifferenz und damit eine von zwei Tasten als erste erkannt werden. Wie ist denn der genaue Ablauf vom Aufruf der Entprell- und der Anzaigeroutine?
Datum: 01.07.2008 15:41
Also ich hab das obige Beispiel genommen. http://www.mikrocontroller.net/attachment/12660/C_TAST.C Hier wird ja zb. mit Taste 0 die Led ein und ausgeschaltet. Wie lautet die if Anweisung, wenn die Led angehen soll, wenn Taste 0 und Taste 1 gedrückt wurden. if( get_key_press( 1<<PD0 )) // Key 0: LED_OUTPUT ^= 1<<PB0; // toggle LED 0 if( get_key_press( 1<<PD1 )) // Key 0: LED_OUTPUT ^= 1<<PB1; // toggle LED 0 Meine if-Anweisung hieß if( get_key_press( (1<<PD0) && (1<<PD1) ) ) LED_OUTPUT ^= 1<<PB2; Aber die Anweisung wird nicht ausgeführt, d.h. es geht nicht die 3. LED an sondern die 1. und die 2. Ich habe mein Problem jetzt auf dieses Beispiel übertragen, aber um eine ähnliche Problematik geht es bei mir. PS: Ich arbeite zum ersten Mal mit AVR-Studio und ich hab erst vor einem Monat das Buch C von a bis z gelesen (nicht komplett) und hab deshalb keine Programmiererfahrung. Den Code von Peter hab ich zum Teil verstanden. zb. steht in key_state und key_press nicht dasselbe?? und key_state ist doch invertiert, wie der eigentliche eingang?
Datum: 01.07.2008 15:57
also die Funktionen und die Definitionen habe ich von http://www.mikrocontroller.net/articles/Entprellun... Mein Programm sieht so aus: int main() { while(1) { eingabe = KEY_PIN; help = get_key_press( eingabe ); if (help != 0) switch (help) { case 1: { print1 (" Taste 1 kurz gedrückt "); } break; case 2: { print1 (" Taste 2 kurz gedrückt "); } break; case 4: { print1 (" Taste 3 kurz gedrückt "); } break; case 3: //wenn Taste 1 und Taste 2 \ //gedrückt dann eingabe = 11111100 => \ //key_state = 00000011 = 3 { print1 (" Taste 1 und Taste 2 kurz gedrückt "); } break; } } } Ich habe den Ablauf für ein paar Tastenkombinationen auf dem Blatt aufgeschrieben und eines davon war das im Kommentar
Datum: 02.07.2008 15:02
Hallo nochmal, @yalu ich glaube du hast recht. Ich habe die Zykluszeit für den Timeroverflow verlängert und es erkennt jetzt, wenn ich zwei Tasten drücke MANCHMAL. Also die einzelnen Tasten funktionieren, aber wenn ich zwei auf einmal drücke, funktioniert es noch nicht so wie ich es will. Wenn man ein Steuerkreuz als Beispiel nimmt, dann wird wenn ich oben drücke oben ausgegeben und bei links wird links ausgegeben. Wenn ich beide drücke sollte nur diagonal ausgegeben werden, aber ich kriege oben, links und diagonal. Ich habe versucht das zu unterdrücken, indem ich bei oben und links erkennen wollte, ob jeweils die andere Taste bereits losgelassen wurde. Dann kommt die Ausgabe oben nur dann, wenn ich vorher die Taste links mal gedrückt habe. Wie lade ich hier eine Datei hoch? kann nur durchsuchen und dann ist die Datei weg Nochmal Danke
Datum: 02.07.2008 20:33
Angehängte Dateien:@Soner Aygün Die Funktion get_key_press ist nicht geeignet, da man kaum innerhalb von 10ms 2 Tasten gleichzeitig drücken kann. Du mußt die Funktion get_key_long nehmen, wenn Du einen Impuls pro Betätigung haben willst oder get_key_rpt für Pulse, solange man drückt. Anbei das Beispiel für beides. Die Festlegung von REPEAT_START bestimmt, in welcher Zeit die Betätigung als gleichzeitig erkannt wird. Um diese Zeit wird natürlich auch die Reaktion verzögert. Im Beispiel habe ich 10 definiert, also 100ms. Da man 2 Tasten abfragt, muß man per switch-Anweisung noch testen, welche oder ob beide gedrückt sind. Peter
Datum: 03.07.2008 09:42
Vielen Dank für die Hilfe, die Tasten funktionieren jetzt. Habe noch Fragen zum Code OCR0A = (s16)(XTAL / 1024.0 * 10e-3 - 0.5); // preload for 10ms Was bedeutet diese Zeile? und wofür steht das OCR0A und XTAL. In dem Beispiel hier im Wiki unter Entprellung steht diese Zeile in der ISR TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5); // preload for 10ms Es hat auch denselben Kommentar. Ich hab versucht es im Datenblatt zu verstehen, aber mein Englisch reicht anscheinend dafür nicht. Das einzige was ich glaube verstanden zu haben ist, das XTAL eine externe Uhr ist. Brauche ich beide Zeilen und wo werden die 10ms festgelegt, falls es das "10e-3" ist, was ist dann die 0,5?? Ich hatte statt XOR-Verknüpfungen einfache OR-Verknüpfungen, ist das falsch?? Soner
Datum: 03.07.2008 10:27
XTAL und F_CPU sind vereinbarte Konstanten und meinen beide die Quarzfrequenz. Ich nenne sie CLOCK, aber nicht in C, sondern in ASM. ...
Datum: 03.07.2008 10:29
Achja, die 0.5 sind für die Rundung des Ergebnisses da. ...
Datum: 03.07.2008 14:27
Soner Aygün wrote: > OCR0A = (s16)(XTAL / 1024.0 * 10e-3 - 0.5); // preload for 10ms > > Was bedeutet diese Zeile? und wofür steht das OCR0A und XTAL. Die neueren AVRs haben nen CTC-Mode (Clear Timer on Compare), d.h. der Timer setzt sich nach Gleichheit mit OCR0A automatisch auf Null. Man muß dann nicht mehr in jedem Interrupt den Timer selber rücksetzen, sondern man setzt nur einmalig den Vergleichswert in OCR0A. XTAL ist der CPU-Takt, im AVR-GCC wird das F_CPU genannt. 10e-3s = 10ms (wie beim Taschenrechner). Techniker schreiben Zenerpotenzen immer als Vielfaches von 3 (p,n,µ,m,k,M,G). Peter
Datum: 03.07.2008 14:54
Vielen Dank für die Antworten. Ich wusste nur nicht, ob die 10ms in der Zeile berechnet werden, oder ob sie bereits mit 10e-3 eingetragen wurden. Und so wie ich das ganze verstanden habe, sind das 2 verschiedene Interrupts. Der eine läuft über und der andere vergleicht die Zeit und wird wieder zurückgesetzt. Auf alle Fälle funktioniert jetzt alles, hab nur versucht es noch zu verstehen. Respekt an Peter für den genialen Code Soner
Datum: 17.09.2008 14:11
Hallo! Ich habe mich mit dem schon ganz oben diskutiertem Code auseinandergesetzt:
KEY_INPUT=11111110; //Um Tastendruck zu simulieren. while(1){ i = key_state ^ ~KEY_INPUT; // key changed ? ct0 = ~( ct0 & i ); // reset or count ct0 ct1 = ct0 ^ ct1 & i; // reset or count ct1 i &= ct0 & ct1; // count until roll over key_state ^= i; // then toggle debounced state key_press |= key_state & i; // 0->1: key pressing detect } |
Jetzt stellen sich mir einige Fragen. (Ich arbeite mit cvavr und simuliere das ganze mit einer schleife im avr studio)) 1.Durchlauf: i = 00000000 ^ ~(11111110) = 1 ct0 = ~(00000000 & 00000001) = ~(00000000) = 11111111 ct1 = 11111111 ^ 00000000 & 00000001 = 11111111 (weil & höhere Priorität) bzw = (11111111 ^ 00000000) & 00000001 = 00000001 Ob mit oder ohne Klammersetzung, die Bits Null der Bytes ct0 und ct1 sind bei mir schon bei ersten Durchlauf High, was zur Folge hat, dass auch key_state nach dem ersten durchlauf High wird!! :-( Ich bin leider noch auf keinen grünen Zweig gekommen, vielleicht kann mir das jemand genau erklären, wär super! Vielen Dank im Voraus! Mfg Jürgen
Datum: 17.09.2008 15:00
Hab den Code jetzt folgendermaßen umgeschrieben:
i = key_state ^ ~KEY_INPUT; // key changed ? ct0 = (~ct0) & i ; // reset or count ct0 |
damit korrekt mit i verundet werden kann
ct1 = ((ct0 ^ ct1) ^ i) & i; // reset or count ct1
|
Alt: ct1 = ct0 ^ ct1 & i; ct1 UND i würde Null ergeben, ^ ct0 ergibt dann 00000001; Falls XOR vor UND kommen würde, was ja glaub ich nicht so ist (oder?) würde ebenfalls 00000001 herauskommen. Daher statt ct1 = ct0 ^ ct1 UND i; ct1 = ct0 ^ ct1 XOR i; Das zusätzliche & UND i bewirkt ein rücksetzen des Bits im ct1 Byte wenn die entprellung fertig ist!
i &= ct0 & ct1; // count until roll over key_state ^= i; // then toggle debounced state key_press |= key_state & i; // 0->1: key pressing detect |
Ich hab das ganze getestet und es funktioniert einwandtfrei! :-) Trotzdem bitte ich euch um eine kurze erklärung warum der Code im obigen Post nicht funktioniert (bzw wo meine unfähigkeit liegt den Code richtig zu verwenden, weil richtig is er sicher da er von profis ist! ;-) Übrigends, Ausgangssituation: Alles Null, dann ein Taster (bit 0) gedrückt. Jürgen PS: Ich bin noch kein Profi, also seid nicht zu streng! ;-)
Datum: 17.09.2008 15:45
Juergen wrote: > Ob mit oder ohne Klammersetzung, die Bits Null der Bytes ct0 und ct1 > sind bei mir schon bei ersten Durchlauf High, was zur Folge hat, dass > auch key_state nach dem ersten durchlauf High wird!! :-( In der Regel schaltet man ja Geräte nicht mit gedrückten Tasten ein, so daß es kaum auffällt. Du hast natürlich recht, CT0 und CT1 müßten mit 0xFF initialisiert werden, damit erst nach 4 Durchläufen die Übernahme erfolgt. Noch besser wäre es, auch key_state mit 0xFF zu initialisieren. Dann werden bereits beim Einschalten gedrückte Tasten ignoriert. Peter
Datum: 18.09.2008 09:48
Stimmt! War meinerseits nicht optimal getestet, da ich KEY_INPUT immer "gedrückt" initialisiert habe, sonst hätte ich das selbst merken können. Allerdings freut es mich sehr zu so einem genialen Code einen nützlichen Beitrag geleistet zu haben! :-) Jürgen
Datum: 18.09.2008 09:57
> In der Regel schaltet man ja Geräte nicht mit gedrückten Tasten ein, so > daß es kaum auffällt. Das kommt schon vor: Gerät geht bei nicht-Benutzung/per Kommando schlafen und wird per Tastendruck (IRQ) geweckt bzw. eingeschaltet. In diesem Einschaltvorgang wird anhand der gedrückten Tasten festgelegt, was passiert. Mein Heizgriffregler hat z.B. zwei Tasten, eine davon fungiert via INT0 als Ein-Taste. Wenn die zweite beim Einschalten gedrückt war, kommt man in den Programmiermodus für Heizleistung und Anheizzeit, wenn nicht, ist man im Standardbetrieb - Anheizen für programmierte Zeit und danach programmierte Heizleistung (z.Z. 40% vom Maximum). Läuft übrigens mit den hier beschriebenen Entprellroutinen.. ;-) Ahoi, Martin
Datum: 21.09.2008 19:12
Angehängte Dateien:Hallo! Ich habe versucht, Peter Danneggers ASM-Code aus Beitrag Nr. 3, in BASCOM umzusetzen. Man kann zwar ASM-Code einbinden, ich wollte es aber trotzdem erst einmal in BASCOM versuchen. Bei mir funktioniert das Basic-Programm ohne Probleme. Vielleicht können andere BASCOM-Freunde etwas damit anfangen. Vielen Dank an Peter Dannegger. Gruß Tom
Datum: 26.09.2008 17:38
Hab nochmal ne Frage zur ASM Routine von peter. und zwar würde ich gerne zum besseren verständnis erklärt haben was mir nun das register key_state sagt. MfG Florian
Datum: 26.09.2008 17:46
> und zwar würde ich gerne zum besseren verständnis erklärt haben was mir > nun das register key_state sagt. Key_state wird von der Routine benötigt, um den entprellten Zustand der Tasten (also den gegenwärtig für den Controller gültigen Zustand) zu speichern und mit den neu eingelesenen Bitmustern zu vergleichen (EXOR). Key_state kann vom Programm (nur lesend) genutzt werden, um Tastenzustände zu überprüfen. Also key_press meldet (und merkt sich) neue Tastendrücke, key_state zeigt den gegenwärtigen (aber entprellten, daher leicht verzögerten) Zustand. Dies kann z.B. für "Shift-Tasten" genutzt werden, also zur Überprüfung, ob während eines Tastendruckes auch eine weitere Taste gedrückt war. KH
Datum: 26.09.2008 17:49
Also ist eine 1 im Key_state register = eine enprellte gedrückte taste? Wie schauts eigentlich mit der 1-->0 erkennung aus kann man die ähnlich realisieren? Gibt es evtl. auch schon beispiele?
Datum: 26.09.2008 17:52
Hab grad nochmal geschaut, wenn im key_state register eine 0 auftaucht ist der jeweilige taster betätigt und entprellt. wenn man in loslässt, erscheint nach 2 erkannten "loslassen an dieser stelle im register wieder eine 1.
Datum: 27.09.2008 11:42
Florian Patzer wrote: > Also ist eine 1 im Key_state register = eine enprellte gedrückte taste? Ja, wobei der Zustand nur von der Entprellroutine verändert werden darf. > Wie schauts eigentlich mit der 1-->0 erkennung aus kann man die ähnlich > realisieren? Gibt es evtl. auch schon beispiele? Ich nutze Peters genialen Algorithmus auch regelmäßig, allerdings mit meinen eigenen Variablennamen. "tas" (TAstenStatus) entspricht key_state, tfl (TastenFLags) entspricht key_press, die Prellzähler heißen bei mir tz0 und tz1. Da im Fall des Beispiels nur die unteren 4 Bits des Tastenports tap (PinX) mit Tasten belegt sind, werden die loslass-Merker gleich in die oberen Bits von tfl gelegt. Sind mehr Tasten zu entprellen, dann ist für die Loslass-Merker natürlich ein eigenes Register zu verwenden. Im vorliegenden Fall läuft die Routine auch nicht im Interrupt, sondern als Job der Mainloop, durch Timer und Jobflag synchronisiert.
Tastenabfrage: ;Entprell-Algorithmus geklaut bei Peter Dannegger... ;die Tasten liegen auf den untern 4 Bits cbr flags,1<<upsync;Jobflag löschen (Routine läuft als Mainloop-Job) in tmp,tap ;Tastenport einlesen (gedrückt=L) com tmp ;invertieren (gedrückt=H) andi tmp,alltast ;ungenutzte Bits ausblenden eor tmp,tas ;nur Änderungen werden H and tz0,tmp ;Prellzähler unveränderter Tasten löschen (Bit0) and tz1,tmp ;Prellzähler unveränderter Tasten löschen (Bit1) com tz0 ;L-Bit zählen 0,2,->1, 1,3,->0 eor tz1,tz0 ;H-Bit zählen 0,2,->tz1 toggeln and tmp,tz0 ;Änderungen nur dann erhalten, wenn im Prellzähler and tmp,tz1 ;beide Bits gesetzt sind (Zählerstand 3) ; Loslasserkennung push tmp ;Kopie and tmp,tas ;nur die Tasten erhalten, die jetzt losgelassen wurden ;entweder: swap tmp ;ins andere Nibble (das ist ja frei) or tfl,tmp ;Bits im anderen Nibble setzen (alternativ) ;oder: ; or tlf,tmp ;Bits im anderen Register setzen (alternativ) ;--- pop tmp ;alten Wert wiederherstellen eor tas,tmp ;erhaltene Änderungen toggeln alten (gültigen) Tastenstatus and tmp,tas ;nur (neu) gedrückte Tastenbits bleiben erhalten or tfl,tmp ;und zugehörige Bits setzen (gelöscht wird nach Abarbeitung) ;in "tas" steht jetzt der gültige Tastenzustand, ;in "tfl" die Flags der neu gedrückten, noch nicht abgearbeiteten Tasten. ;wobei im unteren Nibble die Tastendrücke liegen und im oberen Nibble ;die Loslass-Ereignisse. ;und hier passt noch die Repeat-Erweiterung rein, die allerdings zusammen ;mit der Loslasserkennung nicht sonderlich sinnvoll ist. ret ;fertig... |
Ich hoffe, das Prinzip ist zu erkennen. ...
Datum: 28.09.2008 17:53
Also wie gesagt hab mir die routine von P.D. nochmal angeschaut und meine das der übergang taste gedrückt-->losgelassen auch entprellt wird. Kann mir das jemand bestätigen? MfG Florian
Datum: 28.09.2008 19:57
Ja, es wird das Drücken und das Loslassen entprellt. Es wird in Originalversion nur beim Drücken die Flanke separiert und per Key-press gemeldet (was ja auch sinnvoll ist). Manchmal kann es aber auch sinnvoll sein, dass man auch die Loslass-Flanke meldet, dann greift die Erweiterung, die Hannes vorgestellt hat. Und manchmal (z.B. beim Verändern von Zahlenwerten) braucht man eine Funktion für Autorepeat. Dann greift die Erweiterung, die im Wiki vorgestellt wurde Entprellung. KH
Datum: 28.09.2008 20:05
Ich glaube, jetzt kann man den Thread eigentlich mal sperren denn es wurde doch alles gesagt.
Datum: 06.11.2008 00:51
hab mir mal den C-Code von Peter angeschaut. Hab aber noch nicht soviel Erfahrung in C. Wie ist denn das jetzt. Ich habe zum Beispiel PB2 und möchte den Eingang entprellt haben, wo füge ich den ein. Und wofür ist reg51.h da und was heißt
#pragma cd pl(9999) |
?????
Datum: 07.11.2008 06:57
Wer lesen kann (und es auch tut) ist klar im Vorteil.
> (Keil C51)
Datum: 07.11.2008 08:36
Mathias Obetzhauser wrote: > sorry aber ich kenn nicht Keil C51. Eben, der Codeausschnitt ist aber dafür. Für AVR wird hier MEHRFACH auf den Wiki Entprellung verwiesen.
Datum: 07.11.2008 08:41
Mathias Obetzhauser wrote: > Und wofür ist > reg51.h da Das ist das Include um den AT89C51 (Derivat der 8051-Familie) auszuwählen. Beim 8051 muß der Compiler nicht wissen, welcher Typ benutzt wird, da alle 8051 kompatibel sind. Es reicht daher völlig, die SFRs des verwendeten Derivats per Include bekannt zu machen. > und was heißt
#pragma cd pl(9999) |
????? Ein Pragma des Keil C51, um den erzeugten Code in das Listing einzufügen. Eine WINAVR-Routine findest Du z.B. hier: Beitrag "Universelle Tastenabfrage" Ansonsten über Entprellung in Artikelsammlung/Tutorial nachlesen. Peter
Datum: 16.12.2008 16:27
Angehängte Dateien:Hallo Peter, deine Entprellroutine hat mich so begeistert, dass ich sie unbedingt ganz genau verstehen wollte. Daher habe ich mir alle Erklärungen dieses Forums durchgelesen und die Logik der C-Quelltextzeilen analysiert (s. Anhang). Mir ist aufgefallen, dass gemäß einer deiner Erläuterungen der Zähler nur inkrementiert werden soll, wenn sich der aktuell erfasste Tastenzustand vom letzten konstanten Zustand unterscheidet. Anderenfalls sagst du, dass der Zähler zurück gesetzt wird. Ich nehme mal an, dass du damit "zurück auf 00" meinst. Die Analyse deines Codes führt aber nicht immer zum Zurücksetzen des Zählers, wenn der Tastenzustand und der konstante Zustand sich gleichen. Ich habe im Anhang rot markiert, was ich gemäß deiner Erklärung erwarten würde. Alles schwarz geschriebene ist das Ergebnis der Auswertung deiner Codezeilen. Bitte wirf mal einen Blick drauf und sage mir, ob ich einen Denkfehler mache, oder ob die rot markierten Teile noch besser dem entsprechen, was deine Entprellroutine leisten soll.
Datum: 16.12.2008 18:26
Matthias K. wrote: > Mir ist aufgefallen, dass gemäß einer deiner Erläuterungen der Zähler > nur inkrementiert werden soll, wenn sich der aktuell erfasste > Tastenzustand vom letzten konstanten Zustand unterscheidet. Anderenfalls > sagst du, dass der Zähler zurück gesetzt wird. Ich nehme mal an, dass du > damit "zurück auf 00" meinst. Nein, der Zähler ist invertiert, also der Ruhezustand ist 11. Dadurch brauche ich keine zusätzlichen Invertierungen, wenn er nach 4 Zählimpulsen wieder 11 ist und AND-verknüpt den entprellten Zustand umdreht. Peter
Datum: 17.12.2008 11:18
> Nein, der Zähler ist invertiert, also der Ruhezustand ist 11.
Okay, also zählst du 11, 00, 01, 10, 11, ...
Wenn ich die Logik richtig verstehe, wird der Zähler nur inkrementiert,
wenn i = 1 ist (aktuell erfasster Tastenzustand unterscheidet sich vom
letzten konstanten Zustand).
Mich verwirren aber die unterschiedlichen Folgezustände deiner Logik,
wenn i = 0 ist. Da würde ich für jeden Folgezustand ein Rücksetzen auf
11 erwarten. Gemäß meiner Logiktabelle sehe ich aber folgendes für deine
Logik:
ct0 = ~( ct0 & i ) und ct1 = ct0 ^ (ct1 & i)
ct1 ct0 i ct1+ ct0+
------------+-----------
1 1 0 | 1 1 ist erwartungsgemäß
1 0 0 | 0 1 warum 01 und nicht 11???
0 1 0 | 1 1 ist erwartungsgemäß
0 0 0 | 0 1 warum 01 und nicht 11???
Matthias
Datum: 17.12.2008 11:33
Matthias K. wrote:
> ct0 = ~( ct0 & i ) und ct1 = ct0 ^ (ct1 & i)
Wenn i = 0:
ct0 = ~( ct0 & i )
ct0 = ~( x & 0 )
ct0 = ~( 0 )
ct0 = 1
und:
ct1 = ct0 ^ (ct1 & i)
ct1 = 1 ^ ( x & 0 )
ct1 = 1 ^ ( 0 )
ct1 = 1
Peter
Datum: 17.12.2008 11:54
Besten Dank, das Problem lag in meiner Logiktabelle. Da ct0 als erstes berechnet wird, muss ich den Folgezustand von ct1 natürlich in Abhängigkeit von ct0+ berechnen. Ich habe aber dafür ct0 genommen. So wäre es richtig: ct0 i ct0+ -------+----- 1 0 | 1 0 0 | 1 ct1 ct0+ i ct1+ ct0+(bleibt unverändert) -------------+----------- 1 1 0 | 1 1 0 1 0 | 1 1 Matthias
Datum: 17.12.2008 15:41
Angehängte Dateien:Ich habe meinen Denkfehler im Anhang korrigiert. Hier findet ihr jetzt die Herleitung aller logischen Verknüpfungen des C-Codes. Matthias
Datum: 27.12.2008 16:00
@ Peter Dannegger, habe mit großem Interesse diese Thread und auch noch weitere Beiträge von Dir gelesen. Besteht die Möglichkeit über direkten Email-Kontakt mit Dir in Verbindung zu treten ? Falls ja, sende bitte eine kurze Mail, ich nehme dann Kontakt zu Dir auf. Gruß, Peter [ GrzeschikP (ät) AOL Punkt COM ]
Datum: 21.02.2009 00:08
Hallo Experten, bitte helft mir mal: Das passende Assembler-Programm "get8keyb.asm" funktioniert auf meinem MEGA16. Aber das entsprechende "C-Programm" krieg ich nicht auf die Reihe. Bin noch nicht solange in "C" unterwegs. Meine Frage: Von welchen Stellen im Programm werden die Funktionen
u8 get_key_press( u8 key_mask ) u8 get_key_rpt( u8 key_mask ) und u8 get_key_short( u8 key_mask ) |
aufgerufen? Ich weiß nicht, wie der Programmzähler dort hin kommt. In diesem Thread konnte ich das nicht rauslesen. Hier noch einmal das Programm von Peter
/************************************************************************/ /* */ /* Debouncing 8 Keys */ /* Sampling 4 Times */ /* With Repeat Function */ /* */ /* Author: Peter Dannegger */ /* danni@specs.de */ /* */ /************************************************************************/ // Target: ATMega48 #include <io.h> #include <interrupt.h> typedef unsigned char u8; typedef signed short s16; #define XTAL 8000000L // 8MHz #define KEY_PIN PINB #define KEY0 0 #define KEY1 1 #define KEY2 2 #define KEY3 3 #define LED_DDR DDRC #define LED_PORT PORTC #define LED0 0 #define LED1 1 #define LED2 2 #define REPEAT_MASK (1<<KEY3^1<<KEY2^1<<KEY1^1<<KEY0) #define REPEAT_START 10 // after 100ms #define REPEAT_NEXT 20 // every 200ms u8 key_state; // debounced and inverted key state: // bit = 1: key pressed u8 key_press; // key press detect u8 key_rpt; // key long press and repeat ISR( TIMER0_COMPA_vect ) // every 10ms { static u8 ct0, ct1, rpt; u8 i; i = key_state ^ ~KEY_PIN; // key changed ? ct0 = ~( ct0 & i ); // reset or count ct0 ct1 = ct0 ^ (ct1 & i); // reset or count ct1 i &= ct0 & ct1; // count until roll over ? key_state ^= i; // then toggle debounced state key_press |= key_state & i; // 0->1: key press detect if( (key_state & REPEAT_MASK) == 0 ) // check repeat function rpt = REPEAT_START; // start delay if( --rpt == 0 ){ rpt = REPEAT_NEXT; // repeat delay key_rpt |= key_state & REPEAT_MASK; } } u8 get_key_press( u8 key_mask ) { cli(); // read and clear atomic ! key_mask &= key_press; // read key(s) key_press ^= key_mask; // clear key(s) sei(); return key_mask; } u8 get_key_rpt( u8 key_mask ) { cli(); // read and clear atomic ! key_mask &= key_rpt; // read key(s) key_rpt ^= key_mask; // clear key(s) sei(); return key_mask; } u8 get_key_short( u8 key_mask ) { cli(); // read key state and key press atomic ! return get_key_press( ~key_state & key_mask ); } u8 get_key_long( u8 key_mask ) { return get_key_press( get_key_rpt( key_mask )); } int main( void ) { TCCR0A = 1<<WGM01; // Mode 2: CTC TCCR0B = 1<<CS02^1<<CS00; // divide by 1024 OCR0A = (s16)(XTAL / 1024.0 * 10e-3 - 0.5); // preload for 10ms TIMSK0 = 1<<OCIE0A; // enable timer interrupt LED_PORT = 0xFF; LED_DDR = 0xFF; sei(); for(;;){ // main loop switch( get_key_long( 1<<KEY1^1<<KEY0 )){ case 1<<KEY0: LED_PORT ^= 1<<LED0; break; case 1<<KEY1: LED_PORT ^= 1<<LED1; break; case 1<<KEY1^1<<KEY0: LED_PORT ^= 1<<LED2; break; } switch( get_key_rpt( 1<<KEY3^1<<KEY2 )){ case 1<<KEY2: LED_PORT ^= 1<<LED0; break; case 1<<KEY3: LED_PORT ^= 1<<LED1; break; case 1<<KEY3^1<<KEY2: LED_PORT ^= 1<<LED2; break; } } } |
Datum: 21.02.2009 13:42
Angehängte Dateien:Hallo,
habe mal Peters Original der individuellen Tastenabfrage angehangen.
Im Main wird nachgesehen, ob eine entsprechende Taste gedrückt wurde.
Dies geschieht mit dem if, der Abfrageart der Taste und der
entsprechenden Taste die definiert wurde.
//Für kurzen Tastendruck:
if( get_key_short( 1<<KEY1 ))
{
-Tu was
}
//Für langen Tastendruck:
if( get_key_long( 1<<KEY1 ))
{
-Tu was
}
//Für die Repeat Funktion:
if( get_key_press( 1<<KEY2 ) || get_key_rpt( 1<<KEY2 ))
{
-Tu was
}
Hoffe, konnte helfen.
Gruß Tobi
Datum: 23.04.2009 13:32
Hi, ich hab eine Verständnis Frage. In der Beschreibung von Matthias K. (malterix) hat er geschrieben: "Da die Tasten low-activ sind, bedeutet eine 1 bei KEY_INPUT: Taste gedrückt, während eine 1 bei key_state bedeutet: Taste nicht gedrückt" Ist das nicht so, dass der Pinpegel beim Tastendrücken auf 0 gezogen wird? Das bedeutet, das entsprechende Bit im PINx-Register ist 0 => eine 0 bei KEY_INPUT = Taste gedrückt Gruß Bach
Datum: 15.05.2009 14:59
Hi, ich benutze das Bullet-Proof-Protokoll schon erfolgreich mit 8 Tasten. Nur leider brauche ich jetzt eine neunte und zehnte. Kann mir mal jemand nen Tipp geben, wie ich das auf einen zweiten Port erweitern kann. Gruß und Danke, die Hupe:)
Datum: 15.05.2009 15:11
Den Code einfach ein zweites Mal mit geänderten Variablen- und Port-Namen anhängen, das Ganze evtl. noch etwas nachoptimieren. Oder: Alle 8-Bit-Variablen in 16 Bit ändern und beim Einlesen der Ports die beiden 8-Bit-Werte zu einem 16-Bit-Wert zusammenfassen. Dies ist für die C-Variante sicher der elegantere Weg.
Datum: 15.05.2009 15:13
Irgendwie habe ich das nie richtig verstanden, ich pruefe die Tasten-Ports einfach jede ms und es funktioniert...?
Datum: 15.05.2009 15:15
>Kann mir mal jemand nen Tipp geben, wie ich das auf einen zweiten Port >erweitern kann. Mein erster Ansatz wäre es aus den benötigten Registern (key_xxx, ct0, ct1, i, ...) Integer (16bit) zu machen. Zusätzlich muss dann noch die Zeile
i = key_state ^ ~KEY_INPUT |
entsprechend angepasst werden. Damit sollte es im Wesentlichen schon funktionieren. Es kann aber sein, dass ich noch eine Kleinigkeit vergessen habe... Siehe auch yalus Beitrag oben.
Datum: 07.09.2009 09:57
Angehängte Dateien:Welche Version für Assembler ist die aktuelle, die oben angegebene oder die im Tutorial ? Worin unterscheiden sie sich ? Ich habe gestern versucht, die im Tutorial aufgeführte Fassung im Debugger vom AVR Studio nachzuvollziehen und gleichzeitig auf Papier. Die Werte auf Papier erhielt ich zwar auch im AVR Studio, aber sie ergeben keinen Sinn (nach dem 4. Durchlauf ist key_press immer noch 0). Wo findet sich in der Tutorial-Fassung ein Zähler, der bis 4 zählt ? Die einzigen Variablen, die in jedem Durchlauf übernommen werden, sind key_old, key_state und key_press, iwr0 und iwr1 werden jedesmal verworfen (werden sogar extra auf dem Stack geschrieben) ??? P.S.: Die oben angegebene Version liefert im Debugger von AVR Studio 4.12 die Meldung 'Invalid opcode 0xffff at address 0x000899 und springt nicht mehr aus der Interruptroutine heraus). Wäre klasse, wenn mir jemand weiterhelfen kann.
Datum: 07.09.2009 10:10
> iwr0 und iwr1 werden jedesmal verworfen
Verworfen??? - Das sind doch aber die 8 Zweibit-Zähler...
Bit 0 jedes der 8 Zähler befindet sich in iwr0, Bit 1 in iwr1. Gezählt
wird global (bzw. static), die Werte müssen also zwischen den Aufrufen
der Routine erhalten bleiben. Wenn Du die löscht (verwirfst,
überschreibst), dann kann da ja nix zählen.
...
Datum: 07.09.2009 10:13
Die Assemblerversion im Tutorial ist nur mit 2-fach Abtastung. IWRX soll einfach nur heißen Interrupt_Working_Register_X, d.h. das sind zerstörbare Arbeitsregister nur für Interrupthandler. Wenn man im Main und Interrupt unterschiedliche Register verwendet, braucht man kein PUSH/POP. Das PUSH/POP IWR0/1 ist also Unsinn. Ebenso beim Register zum Sichern des SREG. 4-Abtastung in Assembler sähe so aus (C-Zeilen als Kommentar):
.include "1200def.inc" .def deb_ct0 = r2 .def deb_ct1 = r3 .def deb_i = r4 .def deb_keystate = r5 .def deb_keypress = r6 debounce: ;i = key_state ^ ~KEY_PIN; in deb_i, PINB com deb_i eor deb_i, deb_keystate ;ct0 = ~( ct0 & i ); and deb_ct0, deb_i com deb_ct0 ;ct1 = ct0 ^(ct1 & i); and deb_ct1, deb_i eor deb_ct1, deb_ct0 ;i &= ct0 & ct1; and deb_i, deb_ct0 and deb_i, deb_ct1 ;key_state ^= i; eor deb_keystate, deb_i ;deb_keystate = debounced key state ;key_press |= key_state & i; and deb_i, deb_keystate or deb_keypress, deb_i ;deb_keypress = key pin has changed from 1->0 ret |
Peter
Datum: 07.09.2009 12:23
Vielen Dank für die Antworten!
@Hannes Lux:
Sie sind der Auslöser, daß ich mir diesen Code antue ;-) Eigentlich gehe
ich gerade Ihren Code zum R/C-Sender durch und bin dabei auf die
Tastenentprellung gestoßen. Wenn es nicht stört, würde ich mich nochmals
gesondert mit Fragen zu Ihrem Code an Sie wenden.
@Peter Dannegger / all:
Die zwei 8-Bit-Zähler iwr0 und iwr1 stelle ich mir bislang wie folgt
vor:
1. Zyklus:
iwr0: 00 00 00 00
iwr1: 00 00 00 00
2. Zyklus:
iwr0: 00 00 10 00
iwr1: 00 00 00 00
3. Zyklus:
iwr0: 00 00 00 00
iwr1: 00 00 10 00
4. Zyklus:
iwr0: 00 00 10 00
iwr1: 00 00 10 00 (jetzt wird key_press gesetzt)
^
(für den Fall, daß Bit 3 bei PIND gesetzt ist)
Ist das soweit richtig ?
Ich werde mir das ganze heute abend, nach der Arbeit, nochmals zu Gemüte
führen.
Datum: 07.09.2009 13:35
> Wenn es nicht stört, würde ich mich nochmals > gesondert mit Fragen zu Ihrem Code an Sie wenden. Nur zu. Antwort kann aber etwas dauern, da ich nicht immer online bin. > iwr0 und iwr1 ... Das war natürlich Blödsinn von mir, sorry, ich war auf die Prellzähler-Bytes aus... ...
Datum: 07.09.2009 17:20
Mal eine ganz dumme Frage: Worauf bezieht sich "4-Abtastung" ? Bedeutet das, daß die Interrupt-Routine viermal 'erfolgreich' durchlaufen werden muß oder wird innerhalb eines Interrupts viermal abgetastet (PINB wird aber ja nur einmal eingelesen?) ?
Datum: 07.09.2009 18:11
> Bedeutet das, daß die > Interrupt-Routine viermal 'erfolgreich' durchlaufen werden muß Genau! Bei Durchlaufen erhöhen "erfolgreiche" Bits ihren Zählerstand (COM, EOR), während "nicht erfolgreiche Bits" ihren Zählerstand löschen (AND, AND). Hier mal zum Vergleich eine anders kommentierte Version, vielleich helfen die anderen Kommentare beim Verständnis:
Tastenabfrage: ;Entprell-Algorithmus geklaut bei Peter Dannegger... cbr flags,1<<entprell ;Jobflag löschen in tmp,tap ;Tastenport einlesen (gedrückt=L) com tmp ;invertieren (gedrückt=H) andi tmp,alltast ;ungenutzte Bits ausblenden eor tmp,tas ;nur Änderungen werden H and tz0,tmp ;Prellzähler unveränderter Tasten löschen (Bit0) and tz1,tmp ;Prellzähler unveränderter Tasten löschen (Bit1) com tz0 ;L-Bit zählen 0,2,->1, 1,3,->0 eor tz1,tz0 ;H-Bit zählen 0,2,->tz1 toggeln and tmp,tz0 ;Änderungen nur dann erhalten, wenn im Prellzähler and tmp,tz1 ;beide Bits gesetzt sind (Zählerstand 3) ; Loslasserkennung in diesem Programm deaktiviert ; push tmp ;Kopie ; and tmp,tas ;nur die Tasten erhalten, die jetzt losgelassen wurden ; swap tmp ;ins andere Nibble (das ist ja frei) ; or tfl,tmp ;Bits im anderen Nibble setzen (alternativ) ; or tlf,tmp ;Bits im anderen Register setzen (alternativ) ; pop tmp ;alten Wert eor tas,tmp ;erhaltene Änderungen toggeln alten (gültigen) Tastenstatus and tmp,tas ;nur (neu) gedrückte Tastenbits bleiben erhalten or tfl,tmp ;und zugehörige Bits setzen (gelöscht wird nach Abarbeitung) ;in "tas" steht jetzt der gültige Tastenzustand, ;in "tfl" die Flags der neu gedrückten, noch nicht abgearbeiteten Tasten... Tastendauer: mov tmp,tas ;Tastenzustand kopieren andi tmp,wietast ;nur Tasten mit Wiederholfunktion stehen lassen tst tmp ;ist eine Taste betätigt? breq Tastendauer0 ;nein, Dauer auf Startwert... dec twz ;ja, Zähler runter brne Tastenabfrage_e ;Dauer abgelaufen? - nein... or tfl,tmp ;ja, noch aktive Tasten übernehmen (alternativ) ; or twf,tmp ;ja, noch aktive Tasten übernehmen (alternativ) ldi twz,twz1 ;und Zähler auf Wiederholwert setzen Tastenabfrage_e: |
...
Datum: 07.09.2009 22:15
DL4BM schrieb: > Mal eine ganz dumme Frage: > Worauf bezieht sich "4-Abtastung" ? Sollte 4-fach Abtastung heißen. Also 4-mal hintereinander muß der gleiche Tasterzustand erkannt worden sein, damit er übernommen wird. Peter
Datum: 08.09.2009 22:08
Die Funktionsweise von Hannes` Code kann ich jetzt im Debugger nachvollziehen, die Entprellung funktioniert tatsächlich... Faszinierend! Der im Tutorial wie auch im Artikel über Entprellung angegebene Code funktioniert bei mir jedoch nicht, auch nach zehnmaligem Durchlaufen ändert sich der Zustand von key_press nicht (bei dem Code im Entprellungs-Artikel ist zudem der Stackpointer nicht initialisiert).
Datum: 08.09.2009 23:07
> ist zudem der Stackpointer nicht initialisiert Es gibt inzwischen einige (neuere) AVRs, die den SP beim Reset automatisch initialisieren. > ...funktioniert bei mir jedoch nicht... Dazu kann und werde ich nichts sagen, das Tutorial ist nicht gerade meine erste Anlaufstelle. Ich habe den Algorithmus aus dem Anhang des ersten Beitrags dieses Threads hier, also von hier: Beitrag "Tasten entprellen - Bulletproof" http://www.mikrocontroller.net/attachment/1925/Get8keyb.asm Ich habe die Routine allerdings an meinen (nicht gerade professionellen) Stil angepasst, sowohl die Variablen-Namen als auch die Kommentare. Da ich den Algorithmus genial finde, nenne ich im Kommentar auch grundsätzlich den Autor, denn ich will mich nicht mit fremden Federn schmücken. Meist lasse ich diese Routine auch nicht direkt im Timer-Int laufen, sondern in einem Job der Mainloop, der vom Timer (nebenbei) synchronisiert wird. Ich habe auch Varianten im Einsatz, wo die Variablen im SRAM gehalten werden weil mehrere Tastenports (an Schieberegistern) entprellt werden müssen. Die Routine hat sich bei meinen Basteleien in verschiedensten Varianten bestens bewährt. Deshalb hier nochmal ein besonderes Dankeschön an Peter. ...
Datum: 17.01.2010 22:08
Hallo, ich verwende die Entprell-Routine von Peter und möchte bei zwei Tastern zwischen kurzem und langem Drücken unterscheiden, um diese mehrfach zu verwenden. Das funktioniert aber nur teilweise. Ich habe exakt das Beispiel unten aus http://www.mikrocontroller.net/articles/Entprellung kopiert. REPEAT_START und REPEAT_NEXT sind beide 50, also 500 ms. Der Interrupt kommt alle 10 ms. Das Problem ist, dass Gelegentlich ein kurzes Drücken als langes erkannt wird. Es passiert z.B. bei Tastendrücken von 125 ms (mit DSO gemessen), dass diese mit get_key_long erkannt werden. Hast jemand eine Idee, woran das liegen kann? Ich bin verzweifelt. :-( Gruß martin
Datum: 18.01.2010 00:03
Die lang/kurz/repeat-Funktionen sind nicht unabhängig, d.h. es darf immer nur eine Taste gleichzeitig gedrückt werden, die für diese Funktionen enabled ist. Ich würde auch als zu kompliziertes Bedienkonzept empfinden, wenn das nötig wäre. Der Mensch ist ja kein guter Multitasker. Peter
Datum: 18.01.2010 12:26
@Martin: Was hältst Du davon, mit einer Art Shift-Taste(n) zu arbeiten? Das wäre dann eine Taste, bei der Du Key_Press nicht auswertest, dafür aber beim Auswerten der anderen Tasten Key_State der Shift-Taste(n) überprüfst? Nur mal so als Denkanstoß. Gelegentlich habe ich Peters Entprellung so erweitert, dass sie auch auf Loslassen der Tasten prüft. Dies nutze ich gerne, um eine Taste sowohl als Shift-Taste, als auch als direkte Taste nutzen zu können (sinnvoll z.B. bei Drehgebertasten). Jede andere Taste (oder das Drehen des Drehgebers) löscht das Shift-Bit in Key_Press, Als Normaltaste wird die Shift-Taste primär auf Key_Lost geprüft, erst dann auf Key_Press geprüft, das ja bei Betätigung einer anderen Taste (oder des Drehgebers) bereits wieder entwertet wurde. So kann man z.B. den Drehgeber in 2 Ebenen nutzen (ungedrückt = kleines Inkrement, gedrückt = großes Inkrement), den Taster alleine aber zum Sichern des Wertes. Oder man verändert bei gedrückter Taste den ausgewählten Wert und wählt bei unbetätigter Taste die Werte aus der Liste aus (Array-Index). Tastendruck ohne Drehen führt zum Speichern des Wertes im EEP. Es sind recht viele Bedienkonzepte denkbar, bei vielen davon ist Peters Entprellalgorithmus eine unverzichtbare Hilfe. Man muss ihn ja nicht immer exakt so benutzen, wie das Tutorial beschreibt. ...
Datum: 18.01.2010 21:42
Hallo, vielen Dank für die schnelle Antwort. Leider hat mein Kunde sich keine Gedanken zur Bedienbarkeit gemacht und einfach 4 Tasten platziert. Die zwei anderen sind schon anders belegt. Die beiden Tasten, bei denen zwischen kurzem und langem drücken unterschieden werden soll, werden nicht gleichzeitig betätigt. Das oben beschriebene Problem taucht auch bei einer einzelnen Taste auf. Gruß Martin
Datum: 18.01.2010 22:15
martin schrieb: > Das oben > beschriebene Problem taucht auch bei einer einzelnen Taste auf. Zeig dochmal Dein Testprogramm. Für die lang/kurz Erkennung müssen beide Funktionen auch oft genug aufgerufen werden. Werden sie nicht in der Zeit des Loslassens aufgerufen, kann das Loslassen nicht erkannt werden und das Gedrückt-Bit bzw. Repeat-Bit bleibt gesetzt. Peter
Datum: 20.01.2010 19:20
P.S.: Da meine Mainloops immer sehr kurz sind, habe ich die kurz/lang-Funktionen der Einfachheit halber als Mainroutine implementiert. Dann kann man sogar je nach Kontext zwischen lang/kurz und repeat umschalten. Wenn Deine Mainloop zu langsam läuft, kannst Du natürlich auch die kurz/lang-Erkennung einfach mit in den Interrupt packen und dann in 2 zusätzlichen Variablen die Kurz- und Lang-Bits setzen. Dann kannst Du die auch viel später im Main abfragen und löschen. Wichtig ist dann aber, auch ne Maske zu definieren, welche Tasten kurz/lang erkannt werden müssen, da damit die Drück/Repeat-Funktion für diese Tasten nicht mehr geht. Will man dann auch zwischen lang/kurz und repeat umschalten, muß diese Maske eine Variable sein. Peter