Morgen zusammen!
Ich habe mal eine Frage: Wie macht ihr die Tastenerkennung bei einer
Menüführung auf einem LCD?
Die Tastenentprellung a la Peda habe ich mir schon zu Gemüte geführt.
Ganz dahinter komme ich jedoch leider nicht. Ich verstehe auch die
Syntax vom AVR nicht ganz, da ich mit MSPs programmiere.
Ich frage nun alle 10ms per Timer einen Taster ab und habe eine
Variable, die sich beeinhaltet, ob ein Taster kurz, lang oder schon sehr
lang gedrückt ist.
In der main(), bzw. in meinem Menü kann ich nun diese Variable
auswerten.
Als pseudo-Quelltext:
1
switch(menupunkt)
2
{
3
case1:
4
{
5
zeigedasunddasan
6
7
if(keystatus==mindestenskurzgedrückt)
8
{
9
menupunkt=naechstermenupunkt
10
}
11
}
12
13
case2:
14
{
15
zeigewasanderesan
16
17
if(keystatus==mindestenskurzgedrückt)
18
{
19
menupunkt=naechstermenupunkt
20
}
21
}
22
}
Mein Problem ist jetzt, dass ich ja quasi eine Rücksetzvariable haben
muss, da ich, wenn ich im Menüpunkt 1 bin und die Taste auswerte, er den
Menüpunkt erhöht und in den nächsten springt. Hier müsste aber jetzt
erstmal die Taste wieder losgelassen sein, damit er nicht direkt weiter
in den nächsten Punkt springt.
Mir fehlt da grad die Idee, wie ich das machen kann. Ich könnte
natürlich am Anfang eines jeden Menüpunktes ein
1
while(tasteimmernochgedrueckt)
2
{
3
machenichtsundwarte,bissielosgelassenwurde...
4
}
aber das ist ja nicht Sinn der Sache, zumal dann erst das Display
aktualisiert wird, wenn man die Taste wieder loslässt und das dumm ist.
Ich stehe da grad etwas auf dem Schlauch, kann mir da evtl. grad jemand
unter die Arme greifen?
Danke schonmal!
Micha schrieb:> Achso: mit PeDas Code ist auch dein "Loslass-Problem" gelöst..
Kann mir vielleicht jemand in Worten erklären, wie in Pedas Lösung
umgesetzt wird, dass der Tastendruck nur einmal zurückgegeben wird? Ich
rall das nicht, sorry!
Jens Ebers schrieb:> Micha schrieb:>> Achso: mit PeDas Code ist auch dein "Loslass-Problem" gelöst..>> Kann mir vielleicht jemand in Worten erklären, wie in Pedas Lösung> umgesetzt wird, dass der Tastendruck nur einmal zurückgegeben wird? Ich> rall das nicht, sorry!
Wird bei einer Taste ein 1->0 Übergang erkannt (die Tasten sind als
Low-Aktiv angenommen) so wird in einer anderen Variable dieser
Tastendruck mit einem 1 Bit vermerkt. Holst du dir den Tastendruck ab,
so wird das Bit wieder gelöscht. -> Du kriegst jeden Tastendruck nur 1
mal.
Karl heinz Buchegger schrieb:> Wird bei einer Taste ein 1->0 Übergang erkannt (die Tasten sind als> Low-Aktiv angenommen) so wird in einer anderen Variable dieser> Tastendruck mit einem 1 Bit vermerkt.
Ah, jetzt fällt der Groschen, danke schonmal, muss ich mal eben
versuchen umzusetzen.
Jens Ebers schrieb:> Karl heinz Buchegger schrieb:>> Wird bei einer Taste ein 1->0 Übergang erkannt (die Tasten sind als>> Low-Aktiv angenommen) so wird in einer anderen Variable dieser>> Tastendruck mit einem 1 Bit vermerkt.>> Ah, jetzt fällt der Groschen, danke schonmal, muss ich mal eben> versuchen umzusetzen.
Wozu?
Nimm doch einfach die PeDa Entprellung.
Das einzige KEY_PIN, das du zum Einlesen des Ports anpassen musst.
Hier musst du eingreifen:
1
i=key_state^~KEY_PIN;// key changed ?
und auf deinen Prozessor anpassen.
Der Rest ist Standard-C und geht auf jedem anderen Prozessor genausogut.
Einen Timer Interrupt mit ca 10ms hast du ja schon, das Nachladen des
Timer Registers schmeisst du raus und .... fertig
OK, vielen Dank schonmal! Ich muss die trotzdem erstmal komplett
verstehen! Habe mir grad schon alles mögliche zum AVR im Internet
gesucht, um zu sehen, was was bedeutet mit den Ports.
Was ich zum Beispiel nicht verstehe:
1
#define REPEAT_MASK (1<<KEY1 | 1<<KEY2)
und KEY0 ist definiert zu 0, KEY1 zu 1 und KEY2 zu 2
Soweit ich mir das jetzt angelesen hab, heisst das beim AVR, dass KEY1
und KEY2 gleichzeitig auf 'high' geprüft/(gesetzt) werden - beim Taster
natürlich nicht gesetzt.
Aber warum ist die REPEAT_MASK KEY1 und KEY2?
Jens Ebers schrieb:> OK, vielen Dank schonmal! Ich muss die trotzdem erstmal komplett> verstehen! Habe mir grad schon alles mögliche zum AVR im Internet> gesucht, um zu sehen, was was bedeutet mit den Ports.>> Was ich zum Beispiel nicht verstehe:> #define REPEAT_MASK (1<<KEY1 | 1<<KEY2)> und KEY0 ist definiert zu 0, KEY1 zu 1 und KEY2 zu 2
Das hat mit AVRs speziell nix tun. Das sind ganz normale Makros. Es wird
der ganze Port eingelesen (Port B in diesem Fall: PINB ->
AVR-spezifisch) und KEY1/KEY2 sind einfach nur (sprechende) Namen für
die Eingänge (C allgemein).
> Soweit ich mir das jetzt angelesen hab, heisst das beim AVR, dass KEY1> und KEY2 gleichzeitig auf 'high' geprüft/(gesetzt) werden - beim Taster> natürlich nicht gesetzt.>> Aber warum ist die REPEAT_MASK KEY1 und KEY2?
Weil bei diesen beiden Tasten die Repeat-Funktion zur Verfügung stehen
soll, bei den anderen nicht.
i bekommt den Wert von (key_state ge-XOR-ed mit invertiertem Eingang)
OK.
1
ct0=~(ct0&i);// reset or count ct0
ct0 bekommt invertierten Wert von übereinstimmenden "1" von ct0 und i
Warum?
1
ct1=ct0^(ct1&i);// reset or count ct1
ct1 bekommt Wert von (ct0 ge-XORed mit (übereinstimmenden "1" von ct1
und i
Warum?
1
i&=ct0&ct1;// count until roll over ?
2
key_state^=i;// then toggle debounced state
3
key_press|=key_state&i;// 0->1: key press detect
Das kann ich grad einfach nicht nachvollziehen, was in den ganzen Zeilen
passiert.
Kann mir das einer erläutern? Sorry, wenn ich damit nerve, aber einfach
copy paste, dann werd ich es nie verstehen.
Gut - es sind noch andere AVR-spezifische Sachen mit drin, dabei handelt
es sich aber hauptsächlich um Initialisierungsgeschichten (vor allem in
der main; TCNT0=... in der ISR setzt die Timerüberlaufzeit auf 10ms).
Micha schrieb:> TCNT0=... in der ISR setzt die Timerüberlaufzeit auf 10ms
Ja, hab ich schon gesehen, das ist ja kein Problem, ich blick nur durch
die C-Zeilen nicht durch.
Jens Ebers schrieb:> Kann mir das einer erläutern?
Pinsel dir mal 8 Bits auf ein Blatt und geh das Schritt für Schritt,
sprich Zeile für Zeile durch.
Grobe Erklärung: ct0 und ct1 stellen 8 Stück 2-Bit-Zähler (4 Zustände)
für maximal 8 Eingänge dar. Es gehört immer ct0.0 & ct1.0 usw. bis ct0.7
& ct1.7 zusammen. Auf anderen Architekturen lassen sich so auch 16 oder
32 Eingänge gleichzeitig entprellen. Andere Ansätze nutzen für jeden
Eingang einen eigenen Zähler - diese Version ist wesentlich
Ressourcenschonender.
Micha schrieb:> ct0 und ct1 stellen 8 Stück 2-Bit-Zähler (4 Zustände)> für maximal 8 Eingänge dar
... und nur wenn nach 4 Durchläufen noch immer derselbe Pegel am Eingang
anliegt wird dieser als gültig akzeptiert. Ansonsten wird der Zähler
zurückgesetzt.
Ich habe gerade angefangen im Wiki-Artikel eine kurze Beschreibung der
Funktionsweise anzufügen.
Ist schon eigenartig. Das ist eine der wenigen Codestellen, bei denen
ich es OK finde, wenn man nicht im Detail versteht was da abgeht, eben
weil er so tricky ist. Und ausgerechnet davon will jeder wissen, wie das
funktioniert andernfalls nimmt er den Code nicht.
Der Schlüssel zum Ganzen ist das Verständnis dessen, das ct0 UND ct1
zusammengenommen 8 Stück 2-Bit Zähler bilden (siehe Bild)
Jens Ebers schrieb:> Das mach ich grad :)
Die ganze Sache ist sehr trickreich und am Anfang schwer zu verstehen.
Auch wenn du es ne Weile nicht genutzt hast, braucht es ein bisschen bis
du wieder drin bist. Aber du wirst feststellen, dass es funktioniert und
kaum effizienter realisiert werden könnte.
Ansonsten: vertrau Peter und verwende den Code einfach... ;-)
PS: funktioniert auch mit verteilten Eingängen (Pegel in eine Variable
kopieren und diese entprellen).
Karl heinz Buchegger schrieb:> Ich habe gerade angefangen im Wiki-Artikel eine kurze Beschreibung der> Funktionsweise anzufügen.> Ist schon eigenartig. Das ist eine der wenigen Codestellen, bei denen> ich es OK finde, wenn man nicht im Detail versteht was da abgeht, eben> weil er so tricky ist. Und ausgerechnet davon will jeder wissen, wie das> funktioniert andernfalls nimmt er den Code nicht.>> Der Schlüssel zum Ganzen ist das Verständnis dessen, das ct0 UND ct1> zusammengenommen 8 Stück 2-Bit Zähler bilden (siehe Bild)
Bild ist beim editieren verloren gegangen
Jens Ebers schrieb:> Und woher kommen hier die vier Durchgänge?
Der Pegel wird 4 mal überprüft (mit 10ms Pause) und wenn er bei allen 4
Durchgängen gleich geblieben ist wird er akzeptiert. Peter hätte auch 2
oder 8 nehmen können, aber 4 ist wohl ein sehr guter Kompromiss.
Karl heinz Buchegger schrieb:> Ich habe gerade angefangen im Wiki-Artikel eine kurze Beschreibung der> Funktionsweise anzufügen.
Besten Dank! Du bist mein persönlicher Held für heute! Das find ich echt
super.
Es ist auch blöd das immer wieder rauszukramen, jeder 10. Thread hier
geht um diese Tastenentprellung und die, die es verstanden haben, die
nervt es, das kann ich auch vollkommen verstehen, nur leider gehöre ich
noch zu der anderen Fraktion.
Ich male mir das gerade Bitweise auf und selbst das ist noch nicht so
leicht zu überblicken im Moment.
Was mich halt auch wundert: Alle Variablen wie ct0, ct1, key_state,
key_press, key_rpt werden garnicht initialisiert.
Ist es egal, welcher Wert schon drinne steht?
> i bekommt den Wert von (key_state ge-XOR-ed mit invertiertem Eingang)> OK.
Schon. Aber was ist die Bedeutung eines 1 Bits?
Ein 1 Bit bedeutet an dieser Stelle: Der aktuelle Pin Zustand
unterscheidet sich vom letzten bekannten, entprellten Zustand (in
key_state)
(Wenn du so weitermachst, wirst du deine Analysefähigkeiten nicht
schärfen. Du liest nur vor, was im Code steht. Aber du musst dich immer
fragen: Was ist die Bedeutung dessen was da steht? Was heißt es, wenn da
ein 1 Bit oder ein 0 Bit auftaucht? Kann man das in allgemeineren
Begriffen als 1-Bit / 0-Bit formulieren?
)
>
1
>ct0=~(ct0&i);// reset or count ct0
2
>
> ct0 bekommt invertierten Wert von übereinstimmenden "1" von ct0 und i> Warum?>
1
>ct1=ct0^(ct1&i);// reset or count ct1
2
>
> ct1 bekommt Wert von (ct0 ge-XORed mit (übereinstimmenden "1" von ct1> und i> Warum?
Die beiden letzten Zeilen muss man zusammen sehen.
Sie erhöhen den vertikalen 2-Bit Zähler um 1. Aus 0b00 wird 0b01 wird
0b10 wird 0b11
Aber nur dann, wenn an der entsprechenden Bitposition in i eine 1 steht.
Steht dort eine 0, so wird der Zähler fix auf 0b01 gesetzt.
Der Zähler zählt also eigentlich von 0b01 auf 0b10 auf 0b11
>
1
>i&=ct0&ct1;// count until roll over
2
>?
3
>key_state^=i;// then toggle debounced
4
>state
5
>key_press|=key_state&i;// 0->1: key press detect
6
>
>> Das kann ich grad einfach nicht nachvollziehen, was in den ganzen Zeilen> passiert.
Ja?
Die erste Zeile ist die Abfrage ob der Zähler bei 0b11 angelangt ist.
Wenn ja wird in i ein 1 Bit für diesen Zähler hinterlassen, wenn nein
dann wird in i eine 0 eingeschrieben. Allerdings findet das ganze nur
dann statt, wenn in i schon eine 1 war, d.h. (siehe oben) wenn es an
dieser Taster überhaupt eine Veränderung gab.
Alles zusammengenommen macht also die erste Zeile:
in i bleibt ein 1-Bit erhalten für alle Tasten, die eine Veränderung
haben (da war i vorher schon i) UND deren 2-Bit Zähler die 3 (0b11)
erreicht haben.
Hat der Zähler die 3 erreicht, dann wird in key_state vermerkt, dass es
für dieses Bit eine Veränderung gibt. das macht die mittlere Zeile
Und die letzte wertet zu guter letzt noch aus, welcher Natur diese
Veränderung war. War es eine 0->1 Veränderung oder eine 1->0 Verändern.
Wenn 0->1 dann wird in key_press ein 1 Bit gesetzt, die vermerkt, dass
die entsprechende Taste gedrückt wurde.
Jens Ebers schrieb:> Was mich halt auch wundert: Alle Variablen wie ct0, ct1, key_state,> key_press, key_rpt werden garnicht initialisiert.
C Regeln lernen!
Diese Variablen sind alle von der Sorte: Werden vom Compiler beim
Programmstart mit 0 initialisiert.
Karl heinz Buchegger schrieb:> Diese Variablen sind alle von der Sorte: Werden vom Compiler beim> Programmstart mit 0 initialisiert.
Na da hab ich doch direkt wieder was gelernt!
Wie gesagt:
Der Code ist extrem trickreich. Eine einzige harmlos aussehende Zeile
mit einer eigentlich simpel anmutenden Bitmanipulation macht viele
semantische Dinge gleichzeitig und basiert auf dem Wissen was ein 1 Bit
in diversen anderen Variablen für eine Bedeutung hat, bzw. verwendet
zeimlich viel Rundumwissen und Wissen aus den vorhergehenden Zeilen. Bei
diesem Code ist jedes einzelne Bit und jedes AND OR XOR wichtig!
Diesen Code muss man nicht im Detail verstehen. Ja, er ist trickreich.
Und ja, er funktioniert auch dann, wenn man ihn nicht versteht. Er ist
ein absoluter Hingucker: zunächst sieht er einfach aus, schaut man
genauer hin wird er kompliziert, dann wieder einfach etc. Seine
Funktionsweise zu erklären ist nicht einfach. Man könnte über diese 5
Zeilen Code ohne Probleme eine 3 seitige Abhandlung schreiben und hätte
selbst dann noch nicht alle Aspekte und Feinheiten erfasst.
Aber all das ist unwichtig, gegenüber:
Man kann ihn ohne Probleme einfach so verwenden und er tut was er soll
ohne irgendwelche großartigen AVR-spezifischen Besonderheiten zu
benutzen. Und zwar jedes mal.
Karl heinz Buchegger schrieb:> Und ausgerechnet davon will jeder wissen, wie das> funktioniert andernfalls nimmt er den Code nicht.
Wie ich an anderer Stelle schon schrieb ist das genau das Problem dieser
Routine: Sie ist "zu gut".
Der Hobbyprogrammierer will aber auch verstehen was er da tut, und genau
deshalb kommen immer wieder Fragen zu dem Teil und es wird nicht
benutzt, da kann es noch so gut funktionieren.
IMHO ist das für ein Tutorial nicht ganz das geeignete.
Micha
Micha H. schrieb:> Karl heinz Buchegger schrieb:>> Und ausgerechnet davon will jeder wissen, wie das>> funktioniert andernfalls nimmt er den Code nicht.>> Wie ich an anderer Stelle schon schrieb ist das genau das Problem dieser> Routine: Sie ist "zu gut".
LOL
Das ist sie wohl.
Um eine Analogie zu benutzen:
Ich bezweifle, dass die meisten verstehen wie eine Fast Fourier
Transformation funktioniert bzw. mit welchen Code-Tricks diese schnell
gemacht wurde. Und trotzdem haben die wenigsten Skrupel, eine FFT
einzusetzen um bei ihrem MP3 Player auf dem LCD ein Spektrum tanzen zu
lassen.
Genau so muss man das auch mit diesem Code sehen: Er ist 'ready to use'.
Karl heinz Buchegger schrieb:> Genau so muss man das auch mit diesem Code sehen: Er ist 'ready to use'.
Also besten Dank!
Ich werde ihn jetzt anpassen und einsetzen, male parallel trotzdem noch
an meinen Nullen und Einsen rum :) Und irgendwann werde ich es
verstehen!
Jens Ebers schrieb:> Was ist noch nicht verstehe: wodurch sind die 4 Abfragen gegeben? Das> ist mir aus dem Code nicht ersichtlich.
He, he
Peter hat es irgenwo mal bestätigt (wenn mich mein Gedächtnis nicht im
Stich lässt). Der C-Code macht nur eine 2 fache Samplung. Im Gegensatz
zum Assembler Code, der macht 4.
Spielt aber keine wirklich bedeutende Rolle.
Ich mein, der Timer läuft doch immer weiter, es wird doch also
regelmäßig abgefragt, wo ist der Knackpunkt, dass es dann halt zwei
gleiche sein müssen?
Hab das halt mal für drei Durchläufe mit nicht-wechselndem Tastenstatus
gemacht, da ändert sich ja halt garnichts:
Karl heinz Buchegger schrieb:> Peter hat es irgenwo mal bestätigt (wenn mich mein Gedächtnis nicht im> Stich lässt). Der C-Code macht nur eine 2 fache Samplung. Im Gegensatz> zum Assembler Code, der macht 4.
Ne, genau umgekehrt.
Der Assembler im Tutorial macht nur 2-fach.
Die C-Beispiele machen alle 4-fach.
Hier mal Assembler geändert auf 4-fach:
1
.include "1200def.inc"
2
3
.def deb_ct0 = r2
4
.def deb_ct1 = r3
5
.def deb_i = r4
6
.def deb_keystate = r5
7
.def deb_keypress = r6
8
9
10
debounce:
11
;i = key_state ^ ~KEY_PIN;
12
13
in deb_i, PINB
14
com deb_i
15
eor deb_i, deb_keystate
16
17
;ct0 = ~( ct0 & i );
18
19
and deb_ct0, deb_i
20
com deb_ct0
21
22
;ct1 = ct0 ^(ct1 & i);
23
24
and deb_ct1, deb_i
25
eor deb_ct1, deb_ct0
26
27
;i &= ct0 & ct1;
28
29
and deb_i, deb_ct0
30
and deb_i, deb_ct1
31
32
;key_state ^= i;
33
34
eor deb_keystate, deb_i ;deb_keystate = debounced key state
35
36
;key_press |= key_state & i;
37
38
and deb_i, deb_keystate
39
or deb_keypress, deb_i ;deb_keypress = key pin has changed from 1->0
Jens Ebers schrieb:> Ich mein, der Timer läuft doch immer weiter, es wird doch also> regelmäßig abgefragt, wo ist der Knackpunkt, dass es dann halt zwei> gleiche sein müssen?>> Hab das halt mal für drei Durchläufe mit nicht-wechselndem Tastenstatus> gemacht, da ändert sich ja halt garnichts:
Der Fall ist ja auch nicht besonders spannend.
Nimm jetzt den eingeschwungenen Zustand her (dein letztes Ergebnis) und
ändere genau 1 Pin und dann verfolgst du, nach wievielen Durchgängen
dieser Pinwechsel in key_press durchschlägt.
Peter Dannegger schrieb:> Karl heinz Buchegger schrieb:>> Peter hat es irgenwo mal bestätigt (wenn mich mein Gedächtnis nicht im>> Stich lässt). Der C-Code macht nur eine 2 fache Samplung. Im Gegensatz>> zum Assembler Code, der macht 4.>> Ne, genau umgekehrt.> Der Assembler im Tutorial macht nur 2-fach.> Die C-Beispiele machen alle 4-fach.
Hmm.
Dann muss ich mich auch noch mal drann setzen und genauer mit einem
Datendurchlauf analysieren.
Wie Gerda Rogers (österr. Astrologien) immer sagt: "Das hätt ich jetzt
nicht gesehen"
Jens Ebers schrieb:> Was ist denn ATOMIC_BLOCK?
Das ist ne sehr nützliche Erweiterung des GCC für richtig echte
Echtzeit, wo man Interrupts braucht.
Du liest eine Variable und willst darin nur ein Bit ändern. Das geht
beim AVR nicht atomar, d.h er muß lesen, UNDieren und schreiben.
Wenn nun gerade dazwischen ein Interrupt auch auf die selbe Variable
schreibt, geht dessen Änderung verloren.
Deshalb macht man es atomar, d.h. unter Interruptsperre und schon gehts.
Peter
Karl heinz Buchegger schrieb:> Dann muss ich mich auch noch mal drann setzen und genauer mit einem> Datendurchlauf analysieren.> Wie Gerda Rogers (österr. Astrologien) immer sagt: "Das hätt ich jetzt> nicht gesehen"
War erleuchtend
Wenn ct0 das LowBit ist und ct1 das High Bit
dann durchläuft der Zähler aus seinem Grundzustand 0b11 heraus, die
Zustände
0b10, 0b01, 0b00
ehe er dann wieder zu 0b11 wird.
Wenn ich das richtige sehe, dann wird da eigentlich runtergezählt und
nicht rauf :-)
Und der entscheidende Punkt ist dann erreicht, wenn der Zähler von 0b00
zu 0b11 unterläuft (und wieder im Grundzustand ist). Und damit versteh
ich dann auch das hier
i &= ct0 & ct1; // count until roll
over
viel besser, denn in i ist nur beim ersten mal Erreichen des
Grundzustands eine 1 enthalten. Dadurch wird dann key_state auf 1
gesetzt und beim nächsten Durchlauf ist dann ganz am Anfang keine
Änderung mehr, i wird zu 0, wodurch hier dann in weiterer Folge 0
auftaucht.
Ja tatsächlich, mit runterzählen macht das mehr Sinn.
Hatte mich sowieso schon die ganze Zeit gefragt, wieso hier
ct0 = ~(ct0 & i)
ct1 = ct0 ^ (ct1 & i)
ct0 und ct1 bei einem i von 0 auf 1 gesetzt werden, wo ich doch die
ganze Zeit 0 erwartet hätte.
So, also ich checks nicht, sorry!
Wenn es vier Durchgänge macht und dann der entsprechende Wert vorliegt,
warum ist dann seit drei Durchgängen nichts passiert? Die wichtige
Variable ist doch die ganze Zeit gleich, wenn ich nach dem zweiten Timer
abtaste, dann bekomme ich doch das gleiche Ergebnis: