Forum: Mikrocontroller und Digitale Elektronik ? Tastaturauswertung in C


von Thomas P. (topla)


Lesenswert?

Hallo zusammen,
ich benötige mal wieder die geballte Kompetenz des Forums für den 
Anschub zu einer Problemlösung:
Es ist eine Tastatur mit 32 Tasten auszuwerten. Die Daten kommen über 
eine Schnittstelle (geht also nicht um das Thema Tastenentprellung) und 
liegen in einem Array mit 4 Bytes vor, jedes Byte für 8 Tasten. 
Gedrückte Tasten sind logisch 1. Die Auswertung wird zyklisch 
durchlaufen, wird auf eine Statusmaschine hinauslaufen.
Drei Zustände der 32 Tasten sind zulässig, keine Taste gedrückt (das ist 
trivial), eine Taste gedrückt (das ist auch noch trivial) und zwei 
Tasten gedrückt. Das ist der Fall, der mich beschäftigt. Hier ist zu 
ermitteln, welche der beiden Tasten als erste gedrückt wurde und welche 
die zweite war. Beispielsweise soll die Reihenfolge Taste 1 + Taste 5 
eine andere Reaktion als Taste 5 + Taste 1 bewirken.
In ASM hätte ich jetzt die 4 Bytes nacheinander durch das Carry rotieren 
und einen Zähler mitlaufen lassen und mir bei jedem 1-Bit die Anzahl 
hochgezählt und die Position gemerkt. Wie fängt man das unter C jetzt am 
besten an?  Muss ich mich mit Inline-ASM auseinandersetzen oder wie geht 
das performant zu lösen?

Danke für alle Hinweise
Thomas

von nicht"Gast" (Gast)


Lesenswert?

Thomas P. schrieb:
> In ASM hätte ich jetzt die 4 Bytes nacheinander durch das Carry rotieren
> und einen Zähler mitlaufen lassen und mir bei jedem 1-Bit die Anzahl
> hochgezählt und die Position gemerkt. Wie fängt man das unter C jetzt am
> besten an?  Muss ich mich mit Inline-ASM auseinandersetzen oder wie geht
> das performant zu lösen?

Da machst du eine Schleife, in der du ein Bit durch die Gegend schiebst 
und bitweise und verknüpfst. Das kannst du dann Zählen. Ist im Grunde 
das gleiche, wie dein ASM Vorschlag.

von Michael B. (laberkopp)


Lesenswert?

Thomas P. schrieb:
> Wie fängt man das unter C jetzt am besten an?

Genau so wie unter ASM: Bei der ersten erkannten Taste anhalten, warten 
bis sie losgelassen wird, und wenn man dann an der zweiten vorbeikommt 
ist das halt die nächste gedrückte (n-Key-rollover).

Schwieriger wird es, wenn das Niederdrücken der zweiten Taste genau zu 
dem Zeotpunkt zum aussenden des Codes führen soll, denn man kann sich 
zwar bei der ersten Taste merken daß sie gedrückt ist und so lange sie 
gedrückt ist sie nicht nochmal als ausgelöst betrachten, aber wenn man 
dann die zweite Taste findet, könnte ja genau so gut die dritte, vierte 
und fünfte gedrückt werden. Irgendwann reicht aber der Speicherplatz für 
die schon gedrückten nicht mehr aus, man muss also die Tasten die 
gedrückt werden während die erste niedergehalten und gespeichert wird 
anders behandeln, nämlich nach der ersten alle wie oben 
(n-Key-rollover).

von Wolfgang (Gast)


Lesenswert?

Michael B. schrieb:
> Irgendwann reicht aber der Speicherplatz für die schon gedrückten nicht
> mehr aus, ...

µCs mit so wenig Speicher gibt es noch? ;-)

Bei einem 32-Tasten Keyboard sollte spätestens mit der 32ten Taste 
Schluss sein. Das ist vielleicht nicht elegant, aber endlich.

von Thomas P. (topla)


Lesenswert?

Danke für die Antworten. Mir ist da noch eine Unschärfe aufgefallen. Der 
Zustand 2 gedrückte Tasten meint immer, dass die erste Taste gedrückt 
und festgehalten wird und dann die zweite dazu gedrückt wird. Wird die 
erste Taste vor dem Drücken der zweiten Taste wieder losgelassen, geht 
alles wieder von vorne los.

Das Prinzip umzudrehen und eine Maske über die Variable zu schieben, ist 
eine Idee.
Gibt aber gleich noch ein neues Problem. Ich habe versucht, eine 
uint32_t-Variable mit dem Arrayinhalt zu füllen:
uint8_t gzio_ebuf[10];
uint32_t tastdata;
uint32_t mask;
  mask = 1;
  tastdata = gzio_ebuf[1] | gzio_ebuf[2] << 8 | gzio_ebuf[3] << 16 | 
gzio_ebuf[4] << 24;

Gibt eine zweifache Warnung vom Compiler mit
../gzio.c:422:7: warning: left shift count >= width of type [enabled by 
default]
und ich verstehe mal wieder nicht, warum? 8 Bit 24 mal links verschieben 
sollte doch ein uint32_t nicht sprengen?
Ist heute nicht mein Tag....

Thomas

von A. S. (Gast)


Lesenswert?

Entweder Du oder ich haben Dein Problem nicht verstanden.

Geht es Dir nur darum, zu ermitteln welche der 32 Bits gesetzt sind? Für 
32-Bit gibt es perfekte Routinen von K&R aus den 70ern, um das nächste 
gesetzte Bit zu finden.

Oder hast Du ein Problem, wenn z.B. das letzte Telegramm kein Bit 
gesetzt hatte, und das neue nun Bit 1 und 5? Dafür gibt es natürlich 
keine Lösung.

Und wer garantiert Dir eigentlich, dass es nur maximal 2 Tasten sind? 
Und das die in sinnvoller Reihenfolge kommen? Und warum tritt man dem 
Designer der Gegenstelle nicht einfach in den Hintern??? Wer überlegt 
sich ein Protokoll, dass 4 Byte statt 2 überträgt und dann noch jede 
Menge Umrechnung erfordert, auf beiden Seiten?

von Thomas P. (topla)


Lesenswert?

Die Idee, eine Maske über die 32 Bit zu schieben statt die 32 Bit durch 
Carry rotieren zu lassen, hat das erste Problem gelöst (und ein neues 
hervorgebracht - siehe Vorposting).
Garantieren tut mir keiner was. Alles, was für mich nicht sinnvoll 
auswertbar ist, wird irgnoriert. Kommen die beiden Tastendrücke so 
zeitnah, dass eine Datenübertragung keine und die nächste zwei Tasten 
enthält, wird das Ereignis ignoriert.
Der letzte Teil geht runter wie Öl - das war ein Profi aus der 
Industrie, so einer mit richtig hohen Stundensätzen... ;-)) Der hat sich 
das ausgedacht.
Trotzdem ist das so sinnvoll. Das ist ein universelles Modul, von denen 
bis zu 32 Stück an einem Bus hängen und nur ein Modul muss mit der 
Bedienung zweier Tasten umgehen können. Ist hier aber nicht 
beeinflussbar und man kann sich die Diskussion darüber schenken.

Thomas

von Markus F. (mfro)


Lesenswert?

Thomas P. schrieb:

> tastdata = gzio_ebuf[1] | gzio_ebuf[2] << 8 | gzio_ebuf[3] << 16 |
> gzio_ebuf[4] << 24;
>
> Gibt eine zweifache Warnung vom Compiler mit
> ../gzio.c:422:7: warning: left shift count >= width of type [enabled by
> default]
> und ich verstehe mal wieder nicht, warum? 8 Bit 24 mal links verschieben
> sollte doch ein uint32_t nicht sprengen?

Tja, wenn Du mal das tätest, was Du glaubst zu tun.

gzio_ebuf[2] zum Beispiel ist doch gleich was für ein Typ? Und der ist 
wieviele Bits breit? Und soll um wieviele Bits geshifted werden?

von Thomas P. (topla)


Lesenswert?

Mir fehlt auch noch die zündende Idee, wie man die beiden Tasten am 
besten registriert. Ist eine Taste gedrückt, steht der "Eins-Bit-Zähler" 
auf eins und ich habe die Taste, die gedrückt wurde und speichere die. 
In einem der nächsten Durchläufe kommt eine zweite Taste dazu. Beim 
Auslesen kann jetzt die erste oder eine neue Taste als erstes 
auftauchen. Klar, man kann jetzt mit ein paar Vergleichen hantieren, 
gefällt mir aber nicht so richtig. Vielleicht ist es besser, die 
Bitmuster zu merken und dann ein xor zu nutzen. Bin mir eben nicht 
sicher und frage deshalb hier, in der Hoffnung, nicht wieder der Erste 
mit einem solchen Problem zu sein.

Thomas

von Thomas P. (topla)


Lesenswert?

Kopf > Tisch, ist das peinlich. Ich geh schlafen.

Danke,
Thomas

von Peter D. (peda)


Lesenswert?

Hiermit kannst Du die Reihenfolge auswerten:

Beitrag "Tasten-Matrix entprellen"

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

1
uint32_t l = *((uint32_t*) &gzio_ebuf[1]);
2
int n = 0;
3
int Taste1 = 0; /* konvention: 1..32 = Taste 1..32, 0=keine */
4
int Taste2 = 0;
5
int i;
6
7
static int Taste1_old;
8
9
    for(i=0; i < 32; i++)
10
    {
11
        if(l & (1<<i)) 
12
        {   
13
            n++;
14
            if(!Taste1)      {Taste1 =i+1;}
15
            else if(!Taste2) {Taste2 =i+1;}
16
            else             {n=3;}
17
        }
18
    }
19
    switch(n)
20
    {
21
    case 3: /* error, whatever todo */
22
            Taste_old = 0;
23
            break;
24
25
    case 2: /* melde doppeldruck einmal */ 
26
            if(Taste1 == Taste_old) Event(Taste1, Taste2);
27
            if(Taste2 == Taste_old) Event(TAste2, Taste1);
28
            Taste_old = 0;
29
            break;
30
            
31
    case 1: Taste_old = Taste1;
32
            break;
33
34
    case 0: Taste_old = 0;
35
            break;
36
    }
Taste_old enthält hier quasi den Status, ob vorher genau eine Taste 
gedrückt war.

Die Bitschieberei oben geht natürlich schneller, sollte hier aber keine 
Rolle spielen.

von Thomas E. (thomase)


Lesenswert?

Thomas P. schrieb:
> Gibt eine zweifache Warnung vom Compiler mit
> ../gzio.c:422:7: warning: left shift count >= width of type [enabled by
> default]
> und ich verstehe mal wieder nicht, warum? 8 Bit 24 mal links verschieben
> sollte doch ein uint32_t nicht sprengen?

Markus F. schrieb:
> gzio_ebuf[2] zum Beispiel ist doch gleich was für ein Typ? Und der ist
> wieviele Bits breit? Und soll um wieviele Bits geshifted werden?

Nein.

Beim Schieben arbeitet der GCC grundsätzlich mit unsigned int, also 16 
Bit. Schiebt man eine Variable, egal ob (unsigned) char oder (unsigned) 
int, ist sie nach 16 Mal Schieben rausgeschoben und das Ergebnis ist 0. 
Deswegen haben die Compilerbauer da eine Warnung eingebaut.

Die maximale Schiebeweite ist also 15Bit. Normalerweise.

Will man >=16 Bit schieben, muß die Variable länger als 16 Bit sein.
1
unsigned char c[] ={1, 2, 3, 4};
2
unsigned long a, b;
3
4
a = c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24);
5
b = c[0] | (c[1] << 8) | ((unsigned long)c[2] << 16) | ((unsigned long)c[3] << 24);

Variable a enthält als Ergebnis 0x00000201. Das ist natürlich falsch. In 
Kenntnis der Eigenschaften des GCC ist es aber das erwartete Ergebnis. 
Egal, unbrauchbar ist es auf jeden Fall.

Mit dem Cast auf unsigned long wird der GCC dazu veranlasst, mit 32 Bit 
zu arbeiten. Variable b enthält somit das richtige Ergebnis 0x04030201.

Mit unsigned long long lässt sich sogar bis 63 Bit weit schieben.

von Thomas P. (topla)


Lesenswert?

Hallo Achim,

ganz herzlichen Dank für Deine Mühe, das ist die Lösung des Problems und 
auch noch schön kurz!

@ thomase:
Vielen Danbk für die Erläuterung. Dass nur die Verschiebung um 16 bzw 24 
Bit den Fehler bringt, hatte ich dann durch Versuche selber entdeckt, 
aber warum das mit 8 Bit funktioniert, war mir weiterhin unklar, denn 
das hätte ja nach der Erklärung von mfro auch nicht funktionieren 
dürfen.
Jaja, die Geheimnisse des Compilers... Steht sicherlich irgendwo 
beschrieben, aber man muss die Stelle erst mal finden.

Thomas

von Nop (Gast)


Lesenswert?

Thomas P. schrieb:
> und ich verstehe mal wieder nicht, warum? 8 Bit 24 mal links verschieben
> sollte doch ein uint32_t nicht sprengen?

Das Shiften wird aber noch auf den uint8_t angewandt, und erst danach 
wird das implizit nach uint32_t gecastet.

Einen Datentyp der Länge N um >=N Bits nach links zu shiften ist in C 
undefined behaviour, das hat mit GCC-Eigenarten nichts zu tun.

von Thomas P. (topla)


Lesenswert?

Nop schrieb:
> Einen Datentyp der Länge N um >=N Bits nach links zu shiften ist in C
> undefined behaviour, das hat mit GCC-Eigenarten nichts zu tun.

Doch, wie ich oben gerade gelernt habe.
thomase hat es doch begründet, dass intern unsigned int verwendet wird; 
also ist das Schieben von 8 bit um 8 bit nach links problemlos möglich - 
gab dafür ja auch keine Warnung vom Compiler.

Thomas

von Nop (Gast)


Lesenswert?

Thomas P. schrieb:
> thomase hat es doch begründet, dass intern unsigned int verwendet wird

Ah ja richtig, mein Fehler.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.