Forum: Mikrocontroller und Digitale Elektronik Flankenauswertung bei vielen Tasten


von Flanke (Gast)


Lesenswert?

Hallo!

Mir ist bekannt, wie ich in C die Flanke für einen Taster auswerten kann 
und wie ich diesen Entprellen kann. Allerdings bezogen sich diese 
Ausführungen immer nur auf eine oder zwei Tasten, sodass der Aufwand 
sich in Grenzen hielt. Nun aber möchte ich 12 Tasten unabhängig 
voneinander auswerten. Natürlich kann ich für jeden Taster eine separate 
Funktion schreiben, was aber viel zu aufwändig wäre. Ich finde nur 
irgendwie gerade keinen richtigen Ansatz, wie ich das bewerkstelligen 
könnte. Meine Idee war, ein Struct zu erzeugen. z.B.:
1
 
2
3
struct Taste
4
{
5
uint8_t flanke_pos, flanke_neg, gedrueckt, inaktiv;
6
}taste1,taste2,taste3.......;

wie kann ich denn nun die entsprechenden Parameter an eine Funktion 
übergeben, sodass innerhalb der Funktion die entsprechende 
Strukturvariable benutzt wird. Ich steh grade voll auf dem Schlauch. Ich 
möchte im besten Fall nur noch die Funktion aufrufen und als Parameter 
den Namen der Taste angeben.

von Stefan S. (chiefeinherjar)


Lesenswert?

Wie hast du die Entprellung denn bisher gemacht?

PeDa hat hier eine super Routine veröffentlicht - hier der Artikel: 
Entprellung

von W.S. (Gast)


Lesenswert?

Flanke schrieb:
> Nun aber möchte ich 12 Tasten unabhängig
> voneinander auswerten.

Und? Sowas ist doch kein wirkliches logisches Problem - oder?

Deine Idee, pro Taste ein struct zu haben, kann man so machen - aber 
nicht so wie du es angedacht hast. Du hast nämlich nicht wirklich drüber 
nachgedacht, sondern drauflos gepoltert.

Tasten sind normalerweise recht langsame Inputs, weswegen man bei Tasten 
im Gegensatz zu Drehencodern mit einem Polling alle 10 ms auskommt. 
Dabei sieht das ganze Verfahren etwa so aus so aus:
1
mache Generaltest ob IRGENDEINE Taste gedrückt ist
2
wenn nicht, dann raus hier,
3
sonst: für alle Tasten do
4
begin
5
  wenn Taste jetzt gedrückt ist, dann do
6
  begin
7
    wenn Entprellcounter 0 ist,  // Taste war zuvor ungedrückt
8
    dann do
9
    begin
10
      schmeiße einen Event "TasteXYZgedrückt" in die Event-Warteschlange
11
      setze den Entprellcounter dieser Taste auf Anfangswert
12
      setze den Repetiercounter dieser Taste auf lange Repetierzeit
13
    end
14
    else
15
    begin
16
      setze den Entprellcounter dieser Taste auf Anfangswert
17
      dekrementiere den Repetierzähler
18
      wenn der Repetierzähler 0 ist, dann do
19
      begin
20
        schmeiße einen Event "TasteXYZgedrückt" in die Event-Warteschlange
21
        setze den Repetiercounter dieser Taste auf kurze Repetierzeit
22
      end 
23
    end
24
  end  
25
  else  // Taste ist nicht gedrückt
26
  begin
27
    wenn Entprellcounter nicht 0, dann do
28
    begin      
29
      dekrementiere Entprellcounter // wenn 0 dann Taste ungedrückt
30
    end
31
  end
32
end

So, das ist der Algorithmus in salopper Form geplaudert. Du brauchst pro 
Taste nur 2 Zählzellen, jeweils 1 Byte reicht aus. Als lange 
Repetierzeit ist zumeist ca. 0.8 Sekunden (bei 10ms Polling = 80 
Pollings) ok, als kurze Repetierzeit kommt eher 0.2 Sekunden (20 
Pollings) in Frage. Wenn deine Tastatur nicht elendig lange prellt, dann 
sind 40 ms Entprellzeit (4 Pollings) ausreichend.

Für deine 12 Tasten brauchst du also 24 Byte RAM zum individuellen 
Entprellen.

W.S.

von Flanke (Gast)


Lesenswert?

Ja, die Funktionsweise einer Entprellroutine ist mir nicht unbekannt. Wo 
ich allerdings einen Knoten im Hirn habe, ist die Art und Weise, wie ich 
es schaffe, eine Funktion zu schreiben, welcher ich einen Portpin (also 
den Taster, dessen Zustand ich auswerten möchte) übergebe und die 
zugehörige Variable im entsprechenden struct mit dem korrekten Wert 
beschreibe, so dass ich später an einer x-beliebigen Stelle im Programm 
den Zustand des Tasters zur Verfügung habe. Bei einem einzelnen Taster 
ist das völlig unproblematisch, da ich den portpin direkt in die 
Funktion schreiben kann und den Zustand dort direkt auswerten kann. In 
der Funktion, die ich brauche, muss aber an eben dieser Stelle ein 
"Platzhalter" stehen, dessen Wert ich der Funktion übergeben muss. Und 
genau da hapert es.

von Peter D. (peda)


Lesenswert?

Flanke schrieb:
> eine Funktion zu schreiben, welcher ich einen Portpin (also
> den Taster, dessen Zustand ich auswerten möchte) übergebe

Man kann sich natürlich wahnsinnig komplexe Strukturen ausdenken und 
viel RAM damit belegen.
Man kann es sich aber auch einfach machen und alle Tasten an den selben 
Port anschließen und den ganzen Port entprellen. Die ARM haben ja 
mindestens 16Bit breite Ports und beim AVR nimmt man 2 Ports, die man in 
ein uint16_t kombiniert.
Oder man nimmt einen ADC-Eingang und ein paar Widerstände:
Beitrag "Tastenmatrix auslesen über nur 2 Leitungen"

von Flanke (Gast)


Lesenswert?

Es hat geklappt! Der Knoten ist weg! Keine Ahnung, warum ich nicht schon 
eher drauf gekommen bin. Ich habe das oben genannte Struct. Dann 
definiere ich eine Funktion, der ich als Parameter die Hardwareadresse 
des Tasters übergebe, sowie einen Pointer auf ein Struct (z.B. struct 
Taste * taste). Innerhalb der Funktion kann ich z.B. mittels 
taste1->flanke_pos den entsprechenden Wert ändern. Dann kann ich beim 
Funktionsaufruf als Parameter die Portadresse und das Struct für die 
entsprechende Taste (z.B. &taste1) übergeben. Schon läuft es.

von Flanke (Gast)


Lesenswert?

Flanke schrieb:
> taste1->flanke_pos

falsch. Sollte heißen taste->flanke_pos

von kein c (Gast)


Lesenswert?

Du magst es kompliziert, oder?

Wie waers mit einem Array? Dann bekommt dein Taster einen Index den du 
uebergeben kannst.

von Flanke (Gast)


Lesenswert?

Eine einfachere Lösung mit gleicher Wirkung wäre natürlich auch schön. 
Kann ich mir das etwa so vorstellen?
1
//Array anlegen:
2
3
Taste[]={Portpin1,Portpin2,Portpin3....};
4
5
//Schleife zum Durchlaufen der Arrayelemente:
6
7
for (uint8_t index=0;index == 11;index++)
8
9
//Entprellroutine und Flankenauswertung für jeden Index

Und jetzt? Wie bekomme ich jetzt vier mögliche Zustände pro Taste 
unabhängig voneinander gespeichert, sodass ich jederzeit darauf 
zugreifen kann?

von Peter D. (peda)


Lesenswert?

Flanke schrieb:
> Wie bekomme ich jetzt vier mögliche Zustände pro Taste
> unabhängig voneinander gespeichert, sodass ich jederzeit darauf
> zugreifen kann?

Im einfachsten Fall mit paralleler Entprellung und Auswahl der Taste per 
Bitmaske, siehe mein Beispiel. Eine Bitmaske ist erheblich 
codesparender, als das umständliche Hantieren mit Arrayindexen.
Nimmt man für die Variablen uint64_t, lassen sich nach dieser Methode 
bis zu 64 Tasten entprellen.
Die loslassende Flanke brauche ich zwar nicht, aber die läßt sich 
einfach dazu basteln, siehe:
Beitrag "Re: Universelle Tastenabfrage"

Hier noch Beispiele mit Sonderfunktionen (lang/kurz, Repeat, 2 Tasten 
gleichzeitig).
Beitrag "Universelle Tastenabfrage"

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Hier noch ein generisches Beispiel, wie man Inputs wild durcheinander 
auf eine 16bit-Variable einliest:
1
#include <avr/io.h>
2
3
typedef uint16_t keyvar_t;
4
5
#define INKEY0  PINB, 0
6
#define INKEY1  PINC, 7
7
#define INKEY2  PINA, 4
8
9
#define _KEY_SET(port, bitin, bitset) port & 1<<bitin ? 1<<bitset : 0
10
#define KEY_SET(x,y) _KEY_SET( x, y )
11
12
keyvar_t keyinput( void )
13
{
14
  keyvar_t val = 0;
15
#ifdef INKEY0
16
  val |= KEY_SET( INKEY0, 0 );
17
#endif
18
#ifdef INKEY1
19
  val |= KEY_SET( INKEY1, 1 );
20
#endif
21
#ifdef INKEY2
22
  val |= KEY_SET( INKEY2, 2 );
23
#endif
24
#ifdef INKEY3
25
  val |= KEY_SET( INKEY3, 3 );
26
#endif
27
// usw.
28
  return val;
29
}

von Bernd K. (prof7bit)


Lesenswert?

Peter D. schrieb:
> Man kann es sich aber auch einfach machen und alle Tasten an den selben
> Port anschließen und den ganzen Port entprellen.

Du kannst getrost davon ausgehen daß es in der Mehrheit der Fälle 
schlichtweg unmöglich ist alle Leitungen an genau die Pins zu führen die 
man sich gerne wünscht weil einfach kein Platz auf der Platine ist für 
die daraus resultierenden Knäuel aus Leiterbahnen und Vias. Es ist 
oftmals ein stundenlanges oder manchmal auch tagelanges Getüftel bis man 
eine Anordnung gefunden hat bei der man endlich alle benötigten 
Spezialfunktionen an einen seiner jeweilig zur Auswahl stehenden 
Alternativpins angeschlossen hat und dann am Schluß die normalen GPIOs 
noch gerade so mit Hängen und Würgen irgendwie dazwischen bekommt ohne 
die Zahl der Lagen zu erhöhen oder gezwungen zu sein irgendwelchen 
Hardwarefunktionen nun doch in Software erledigen zu müssen weil man 
irgend eine Leitung beim besten Willen nicht an diesen einen Pin auf der 
anderen Seite hingeroutet bekommt.

Die Forderung "einfach [...] alle Tasten an den selben Port" ist also in 
der Praxis meist vollkommen unrealistisch, es sei denn die Kosten und 
die Größe des Produktes spielen keine Rolle und die Platine darf doppelt 
so groß sein als sie eigentlich müsste.

von MaWin (Gast)


Lesenswert?

W.S. schrieb:
> das ist der Algorithmus in salopper Form

Und falsch. Wenn eine Entprell-Routine gedrückt und nicht gedrückt 
unterschiedlich behandelt, ist prinzipiell etwas faul in ihr.

Peter D. schrieb:
> Man kann es sich aber auch einfach machen und alle Tasten an den selben
> Port anschließen und den ganzen Port entprellen.

Richtig.
1
 int tasten,gedrueckt;
2
 
3
 while(1)// die Programm-Hauptschleife
4
 {
5
   tasten=INPUT_PORT; // 12 Taster auf ein mal, liefern 1 wenn gedrückt (sonst ~PIND)
6
   gedrueckt=tasten&~gedrueckt;
7
   if(gedrueckt&1)
8
   {
9
     // Taster 1 wurde gerade runtergedrückt, mach was
10
   }
11
   if(gedrueckt&2)
12
   {
13
     // Taster 2 wurde gerade runtergedrückt, mach was
14
   }
15
   :
16
   // mach was sonst in der Programm-Hauptschleife passieren muß
17
   gedrueckt=tasten;
18
   _delay_ms(10); // damit sie bestimmt länger dauert als eventuelles Prellen
19
 }

von Joachim B. (jar)


Lesenswert?

MaWin schrieb:
> int tasten,gedrueckt;

warum int?

ich hätte uint16_t gewählt und sonst wie in PeDas Routine die Auswertung 
in uint16_t statt uint8_t gemacht.

voila 12 Tasten in einer VAR.

von Peter D. (peda)


Lesenswert?

Bernd K. schrieb:
> Die Forderung "einfach [...] alle Tasten an den selben Port" ist also in
> der Praxis meist vollkommen unrealistisch

Na übertreib mal nicht so maßlos.
Ich konnte bisher die Tasten immer so auf die Ports verteilen, daß das 
Lesen aller Tasten in eine Variable ohne großen Aufwand erfolgen konnte.

Aber auch eine völlig wirre Anordnung ist überhaupt kein Problem, wie 
mein Posting genau über Deinem zeigt.

Es ist der Entprellroutine übrigens völlig wurscht, wie man die 
Inputvariable einliest. Wie es mit dem ADC geht, habe ich ja schon 
gezeigt. Man kann sie aber auch über SPI- oder I2C-Expander einlesen. 
Die Routine ist darin völlig flexibel.

von UC (Gast)


Lesenswert?

Ich mach das gerne so, dass ich für jede Taste eine uint8_t nehme und 
dann die Abfrage per Interrupt alle x ms aufrufe.

Bei jedem Aufruf werden die Bits um 1 nach links geschoben und dann das 
letzte Bit entsprechend dem Zustand der Taste gedrückt.

Eine Flanke ist dann, wenn

(taste_xy == 0b01111111)

Somit ist die Taste gut entprellt.

Man kann auch 16 oder 32 Bit Variablen nehmen.

Wenn du uint8_t nimmst, brauchst 12 Byte RAM.

Taster aktiv wäre in diesem Fall:

(taster_xy)

Taster inaktiv:

(!taster_xy)

Negative Flanke:

(taster_xy == 0b10000000)

von W.S. (Gast)


Lesenswert?

MaWin schrieb:
> Und falsch. Wenn eine Entprell-Routine gedrückt und nicht gedrückt
> unterschiedlich behandelt, ist prinzipiell etwas faul in ihr.

Du schreibst mal wieder Unsinn: prinzipiell faulen Unsinn.

MaWin schrieb:
> while(1)// die Programm-Hauptschleife
>  {
Ja, Polling in der main-loop. Klasse!

Was du als falsch bezeichnest, ist genau das RICHTIGE. Schließlich ist 
ein µC nicht dazu gedacht, sich ausschließlich um das Tastenpolling zu 
kümmern.

Also laß derartige Einwürfe bleiben und lerne lieber etwas durch das 
Studium meiner Zeilen.



Flanke schrieb:
> Wo
> ich allerdings einen Knoten im Hirn habe, ist die Art und Weise, wie ich
> es schaffe, eine Funktion zu schreiben, welcher ich einen Portpin (also
> den Taster, dessen Zustand ich auswerten möchte) übergebe und die
> zugehörige Variable im entsprechenden struct mit dem korrekten Wert
> beschreibe, so dass ich später an einer x-beliebigen Stelle im Programm
> den Zustand des Tasters zur Verfügung habe.

Der Knoten besteht darin, daß du quasi diktatorisch und nicht 
ereignisorientiert denkst. Ich übersetze hier mal das Problem durch 
deine Denkweise in beschreibende Worte:

Du willst ein Programm schreiben, das an irgendeiner "x-beliebigen" 
Stelle eine Funktion aufrufen und ihr die Frage stellen will "Haben wir 
einen Tastendruck bei Taste 17? Ja oder nein - gib Antwort JETZT!"

So herum wird das auf ewig NICHTS.

Ein Tastendruck, so wie er von jedem normalen Menschen heutzutage 
gemeint ist, besteht NICHT darin, daß die Taste gedrückt IST, sondern 
darin, daß die Taste gedrückt WURDE, also daß es einen Übergang vom 
ungedrückten Zustand zum gedrückten Zustand gegeben hat (das ist das 
gesuchte Ereignis), UND daß es zwecks Entprellen eines geordneten 
Überganges vom gedrückten zurück zum ungedrückten Zustand bedarf.

Du brauchst also KEINE Funktion, der du ein Portpin übergibst, sondern 
ein Event-System, das dir Ereignisse der Art "Taste 17 wurde grad 
gedrückt" liefern kann, wobei der Lowlevel-Treiber, der die Tastatur 
überwacht und Drück-Ereignisse ermittelt und zwecks späterer Behandlung 
in einer Ereignis-Warteschlange speichert, auch das Entprellen vornimmt, 
ohne daß dabei die übrigen Schichten deiner Firmware sich drum kümmern 
müßten.

Und in die main-Loop (a la MaWin) gehört so etwas überhaupt nicht.

Nochwas:
Peter hat hier ja gemeint, daß man die Portbelegung den Tasten anpassen 
müßte, um sich dadurch das Einzel-Entprellen leichter zu machen.

Schön wär's, ist aber unrealistisch. Bernd hat's bereits ausreichend 
dargelegt.

Was man also machen kann, ist in der SysTick-Routine, die zumeist zum 
Tasten-Abfragen herangezogen wird, zunächst alle Tasten-Zustände in ein 
Wort zusammenzufassen (wie auch immer), um dann alle Bits in diesem Wort 
der Reihe nach auszuwerten. Das ist zunächst etwas wilde Bit-Schieberei 
oder noch etwas Komplizierteres, je nachdem, wo welche Taste denn 
angeschlossen ist, aber da führt kein Weg dran vorbei.

W.S.

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Peter hat hier ja gemeint, daß man die Portbelegung den Tasten anpassen
> müßte, um sich dadurch das Einzel-Entprellen leichter zu machen.

Dreh einem doch nicht die Worte im Munde rum, nirgends habe ich gesagt, 
man muß.
Ich habe nur gesagt, daß es den Code einfacher macht.
Eine Erweiterung für beliebige Zuordnung habe ich bereits gepostet:
Beitrag "Re: Flankenauswertung bei vielen Tasten"

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Schön wär's, ist aber unrealistisch. Bernd hat's bereits ausreichend
> dargelegt.

Er hat nichts dargelegt, sondern nur behauptet. Ich benutze allerdings 
keine 1-lagen Platine, da die in der Fertigung nicht günstiger sind als 
2-lagige.
Und ab 2 Lagen muß man nicht tagelang tüfteln, um ein paar Tasten 
anzuschließen. Oftmals sind die MCs so klein, daß man eh jeden IO 
erstmal auf ein Via legen muß. Große DIP, PLCC sind am Aussterben.

Man muß ja nicht absichtlich die Tasten wild anschließen, sondern kann 
sie auf wenige Ports konzentrieren. Dann reichen wenige and, or, 
schieben aus, um sie in eine Variable zu packen. Unrealistisch ist das 
jedenfalls nicht, ich mache es ja in meinen Projekten.

von W.S. (Gast)


Lesenswert?

Peter D. schrieb:
> Dreh einem doch nicht die Worte im Munde rum, nirgends habe ich gesagt,
> man muß.

Ich habe dir gar kein Wort irgendwo umgedreht, lies deine Bemerkung doch 
bitte nochmal:

Peter D. schrieb:
> Man kann es sich aber auch einfach machen und alle Tasten an den selben
> Port anschließen und den ganzen Port entprellen. Die ARM haben ja
> mindestens 16Bit breite Ports und beim AVR nimmt man 2 Ports, die man in
> ein uint16_t kombiniert.

Und Bernd hat darauf hingewiesen, daß das unrealistisch ist. Und genau 
das ist es: unrealistisch. Wohl weniger wegen des Pinouts, sondern eher 
wegen der Verteilung der diversen Peripheriecores auf die Pins. 
Heutzutage lebt man damit, daß bis zu 7 verschiedene Funktionalitäten 
auf ein Pin gelegt sind und daß man diese nicht einfach auf andere Pins 
verlegen kann. Da bleiben für etwaige Tasten eben nur noch die 
Lückenbüßer übrig.

Deine Idee "alle Tasten an den selben Port" ist tatsächlich nicht 
wirklich realistisch.

W.S.

von spess53 (Gast)


Lesenswert?

Hi

>Deine Idee "alle Tasten an den selben Port" ist tatsächlich nicht
>wirklich realistisch.

Na und. Mit etwas Hirnschmalz hat man das ruck-zuck in die passenden 
Variablen verfrachtet. Habe ich schon öfters gemacht.

MfG Spess

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Deine Idee "alle Tasten an den selben Port" ist tatsächlich nicht
> wirklich realistisch.

So generell gesagt ist das schlichtweg falsch.
In den meisten Fällen kann man 2..6 Tasten sehr wohl auf einen Port 
legen.
Aber auch 12 Tasten an einem ARM (z.B. LPC1768) mit 32Bit-Ports lassen 
noch viel Freiheit bei der Zuordnung.

Ich würde daher sagen:
Es kann manchmal Fälle geben, wo man Tasten auf mehrere Ports verteilen 
muß.

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.