mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Flankenauswertung bei vielen Tasten


Autor: Flanke (Gast)
Datum:

Bewertung
-1 lesenswert
nicht 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.:
 

struct Taste
{
uint8_t flanke_pos, flanke_neg, gedrueckt, inaktiv;
}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.

Autor: Stefan S. (chiefeinherjar)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Wie hast du die Entprellung denn bisher gemacht?

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

Autor: W.S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
mache Generaltest ob IRGENDEINE Taste gedrückt ist
wenn nicht, dann raus hier,
sonst: für alle Tasten do
begin
  wenn Taste jetzt gedrückt ist, dann do
  begin
    wenn Entprellcounter 0 ist,  // Taste war zuvor ungedrückt
    dann do
    begin
      schmeiße einen Event "TasteXYZgedrückt" in die Event-Warteschlange
      setze den Entprellcounter dieser Taste auf Anfangswert
      setze den Repetiercounter dieser Taste auf lange Repetierzeit
    end
    else
    begin
      setze den Entprellcounter dieser Taste auf Anfangswert
      dekrementiere den Repetierzähler
      wenn der Repetierzähler 0 ist, dann do
      begin
        schmeiße einen Event "TasteXYZgedrückt" in die Event-Warteschlange
        setze den Repetiercounter dieser Taste auf kurze Repetierzeit
      end 
    end
  end  
  else  // Taste ist nicht gedrückt
  begin
    wenn Entprellcounter nicht 0, dann do
    begin      
      dekrementiere Entprellcounter // wenn 0 dann Taste ungedrückt
    end
  end
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.

Autor: Flanke (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht 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"

Autor: Flanke (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Flanke (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Flanke schrieb:
> taste1->flanke_pos

falsch. Sollte heißen taste->flanke_pos

Autor: kein c (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du magst es kompliziert, oder?

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

Autor: Flanke (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Eine einfachere Lösung mit gleicher Wirkung wäre natürlich auch schön. 
Kann ich mir das etwa so vorstellen?
//Array anlegen:

Taste[]={Portpin1,Portpin2,Portpin3....};

//Schleife zum Durchlaufen der Arrayelemente:

for (uint8_t index=0;index == 11;index++)

//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?

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht 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
Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier noch ein generisches Beispiel, wie man Inputs wild durcheinander 
auf eine 16bit-Variable einliest:
#include <avr/io.h>

typedef uint16_t keyvar_t;

#define INKEY0  PINB, 0
#define INKEY1  PINC, 7
#define INKEY2  PINA, 4

#define _KEY_SET(port, bitin, bitset) port & 1<<bitin ? 1<<bitset : 0
#define KEY_SET(x,y) _KEY_SET( x, y )

keyvar_t keyinput( void )
{
  keyvar_t val = 0;
#ifdef INKEY0
  val |= KEY_SET( INKEY0, 0 );
#endif
#ifdef INKEY1
  val |= KEY_SET( INKEY1, 1 );
#endif
#ifdef INKEY2
  val |= KEY_SET( INKEY2, 2 );
#endif
#ifdef INKEY3
  val |= KEY_SET( INKEY3, 3 );
#endif
// usw.
  return val;
}

Autor: Bernd K. (prof7bit)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: MaWin (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.
 int tasten,gedrueckt;
 
 while(1)// die Programm-Hauptschleife
 {
   tasten=INPUT_PORT; // 12 Taster auf ein mal, liefern 1 wenn gedrückt (sonst ~PIND)
   gedrueckt=tasten&~gedrueckt;
   if(gedrueckt&1)
   {
     // Taster 1 wurde gerade runtergedrückt, mach was
   }
   if(gedrueckt&2)
   {
     // Taster 2 wurde gerade runtergedrückt, mach was
   }
   :
   // mach was sonst in der Programm-Hauptschleife passieren muß
   gedrueckt=tasten;
   _delay_ms(10); // damit sie bestimmt länger dauert als eventuelles Prellen
 }

Autor: Joachim B. (jar)
Datum:

Bewertung
-1 lesenswert
nicht 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.

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: UC (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)

Autor: W.S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht 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"

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: W.S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht 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ß.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.