Forum: Mikrocontroller und Digitale Elektronik Tasterentprellen für Dummies


von Alexander (alecxs)


Lesenswert?

Ich komm mal wieder nicht klar mit eigentlich einfachsten Sachen. Ich 
habe mir mal eine Funktion geschrieben zum Entprellen von einem GPIO. 
Nun wollte ich die für was anderes übernehmen, hat sich aber als 
ungeeignet erwiesen.
1
// debounce GPIO input
2
void getPin(gpio_num_t pin, bool* state, const unsigned long dur) {
3
  unsigned long cur = millis() / 1000;
4
  static unsigned long lastRead = 0;
5
  static unsigned long lastHigh = 0;
6
  static unsigned long lastLow = 0;
7
  static bool high = false;
8
  static bool low = false;
9
  if (cur == lastRead) {
10
    return;
11
  }
12
  else {
13
    lastRead = cur;
14
  }
15
16
  if (digitalRead(pin) == HIGH) {
17
    if (!high) {
18
      lastHigh = cur;
19
      high = true;
20
    }
21
    if ((cur - lastHigh) > dur) {
22
      *state = true;
23
      low = false;
24
    }
25
  }
26
  else {
27
    if (!low) {
28
      lastLow = cur;
29
      low = true;
30
    }
31
    if ((cur - lastLow) > dur) {
32
      *state = false;
33
      high = false;
34
    }
35
  }
36
}
37
// usage
38
void loop() {
39
  getPin(PIN, &state, 5);
40
  if (state) {
41
    // do something
42
  }
43
}
Ein Problem was mir vorher nie aufgefallen war, es funktioniert nur für 
einen einzigen GPIO global, man darf die Funktion nicht mit 
verschiedenen GPIO im Code nutzen.

Zweitens sind es diesmal keine GPIO sondern bereits entprellte Software 
Buttons. Jetzt hab ich mir gedacht ich jage das durch ChatGPT, soll ja 
mittlerweile ganz brauchbar sein. Es hat vorgeschlagen die Buttons zu 
tracken, dazu sollte ich für jeden Funktionsaufruf einen Index als 
zweites Argument mitgeben. Das wollte ich nicht, stattdessen soll es ein 
Zeiger sein, anhand dessen Adresse eine eindeutige Identifizierung 
möglich ist.

Nun stehen die Buttons leider in einem Struct. Also erstmal neue globale 
Variablen angelegt und somit Redundanz - egal sei's drum.

Herausgekommen ist nun sowas. Es ist leider zu kompliziert und ich 
verstehs nicht.
1
int get_keyevent(bool* key) {
2
  struct KeyState {
3
    bool* ptr;
4
    unsigned long pressStart;
5
    bool active;
6
    bool reportedShort;
7
    bool reportedLong;
8
  };
9
10
  static KeyState states[10];
11
  static int count = 0;
12
13
  const unsigned long tMin = 50;    // ms
14
  const unsigned long tGap = 1000;  // ms
15
16
  unsigned long now = millis();
17
18
  // Lookup or register
19
  int index = -1;
20
  for (int i = 0; i < count; ++i) {
21
    if (states[i].ptr == key) {
22
      index = i;
23
      break;
24
    }
25
  }
26
  if (index == -1 && count < 10) {
27
    states[count] = { key, 0, false, false, false };
28
    index = count++;
29
  }
30
  if (index == -1) return 0;
31
32
  KeyState& s = states[index];
33
34
  // Rising edge: start tracking
35
  if (*key && !s.active) {
36
    s.active = true;
37
    s.pressStart = now;
38
    s.reportedShort = false;
39
    s.reportedLong = false;
40
  }
41
42
  // If still held
43
  if (*key && s.active) {
44
    unsigned long tHeld = now - s.pressStart;
45
46
    if (tHeld >= tMin && tHeld < tGap && !s.reportedShort) {
47
      s.reportedShort = true;
48
      return 1; // short press
49
    }
50
51
    if (tHeld >= tGap && !s.reportedLong) {
52
      s.reportedLong = true;
53
      return 2; // long press
54
    }
55
  }
56
57
  // Reset after release
58
  if (!*key && s.active) {
59
    s.active = false;
60
  }
61
62
  return 0;
63
}
64
// usage
65
void loop() {
66
  int event = get_keyevent(&softKey);
67
  if (event == 1) {
68
    // short press
69
  } else if (event == 2) {
70
    // long press
71
  }
72
}
Es kommen aber noch Anforderungen hinzu. Einerseits soll sich der 
Software-Button wie jede Taste einer PC-Tastatur anfühlen, also sofort 
anschlagen als Single und nach kurzer Verzögerung als Longpress. 
Zusätzlich soll aber eine "feststeckende" Taste erkannt und automatisch 
ignoriert werden. Nach Ablauf der Totzeit soll sie wieder erlaubt sein.

Was den Singlepress angeht, so soll dieser erst nach 50 ms gültig sein. 
Schnelles tippen soll aber ebenfalls erlaubt sein. Keine Ahnung wie das 
mit dem Longpress vereinbar ist. Ich möchte später ein Tastenpaar 
auswerten; eine Taste wird gedrückt gehalten, währendessen die andere 
mehrmals getippt.

Einige Anforderungen widersprechen sich. Ich habe nun so viel gelesen 
und einen Knoten im Kopf.

Michael B. schrieb:
> Aber die Fähigkeit der Leser sinkt, heute muss man wohl Kindersprech in
> Teletubbisvideos für ein nicht weiter erklärtes 'shield', dass die
> Internas versteckt, anbieten damit man die Leser auf Legoniveau nicht
> überfordert.

Gibt's eine Arduino Library die das alles kann? Muss aber für 
Software-Buttons sein, nicht für GPIO

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?


von Andras H. (andras_h)


Angehängte Dateien:

Lesenswert?

Falls jemand Lust hat mit Tasters herumzuspielen.

von Alexander (alecxs)


Lesenswert?

Also brauche ich mehrere Funktionen für verschiedene Tastenabfragen. 
Mich beschlich schon so ein Verdacht.

Hab mir beides kurz angesehen und bin noch mehr verwirrt. Es hilft wohl 
nichts, werde wohl wieder bei Null anfangen und das Rad neu erfinden.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Alexander schrieb:
> Also brauche ich mehrere Funktionen für verschiedene Tastenabfragen.

Nein, Du brauchst mehrere Zustandsvariablen für mehrere Tastenabfrage. 
Der Code bleibt der gleiche - vorausgesetzt, er kann mit mehreren 
Zustandsvariablen umgehen.

Peter Daneggers Code ist hier ziemlich elegant, denn er kommt für jede 
Taste nur mit wenigen zusätzlichen Bits aus. Da Du keinen Timer benutzt 
und immer den Timstamp pollst, benötigst Du mindestens 3 x 32 bit pro 
Taste. Das ist nicht nur Speicherplatzverschwendung, sondern macht den 
Code für mehrere Tasten dann auch wesentlich ineffizienter - jedenfalls 
für 8-Bit-AVRs.

Siehe auch Entprellung.

: Bearbeitet durch Moderator
von Alexander (alecxs)


Lesenswert?

Mit dem pollen hab ich auch meine Probleme. Momentan verpasse ich manche 
Tastendrücke. Ganz blöd wird es wenn ich das Loslassen verpasse. Der AVR 
Code mag elegant sein, aber die Sprache ist mir zu low. Ich brauch was 
fertiges für Arduino. Es läuft auf einem ESP32 Ressourcen sind 
zweitrangig.

von Joachim B. (jar)


Lesenswert?

Frank M. schrieb:
> Peter Daneggers Code ist hier ziemlich elegant

+1

Frank M. schrieb:
> Da Du keinen Timer benutzt

außerdem
"Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang"

von Alexander (alecxs)


Angehängte Dateien:

Lesenswert?

Eine einzelne kleine Funktion hab ich jetzt nicht als "längeren 
Sourcecode" gesehen, aber gut..

Das schaue ich mir gerade an:

Beitrag "Re: Tasterverarbeitung mit ARDUINO-C"

: Bearbeitet durch User
von Andras H. (andras_h)


Lesenswert?

Alexander schrieb:
> Mit dem pollen hab ich auch meine Probleme.

Falls jemand den thread zu lange blockiert, wird pollen natürlich nie 
robust funktionieren. Auf Arduino wo die Leute irgendwie dazu tendieren 
sleep 100ms zu machen, da kann ja vieles dann nicht mehr richtig laufen. 
Vermutlich hast du irgendwo in einer library ein paar funktionen die zu 
lange blockieren.

von Alexander (alecxs)


Lesenswert?

Was hat's mit den ALL_KEYS auf sich, ist das ne Maske oder ne Abfrage?

von Peter D. (peda)


Lesenswert?

Alexander schrieb:
> Der AVR
> Code mag elegant sein, aber die Sprache ist mir zu low.

Viele Codezeilen kann man damit natürlich nicht schinden, so daß es 
möglichst unübersichtlich und sophisticated ausschaut.

Der Thread ist recht lang geworden und es sind auch ne Menge Anwender 
darunter, die ihn problemlos auf 32Bit Boliden portiert haben.

Definiert man die Variablen als uint64_t, kann man bis zu 64 Tasten 
bearbeiten (eine Taste je Bit). Die jeweilige Taste wird bequem per 
Bitmaske adressiert, aufwendige Pointerarithmetic entfällt. Die 
entsprechenden Eventfunktionen gelten immer für alle Tasten. Man kann 
somit auch ein Event für mehrere Tasten zusammen in einem Aufruf 
löschen.

von Alexander (alecxs)


Lesenswert?

Ja hab ich schon gefunden. Werde das dann testen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Alexander schrieb:
> Das schaue ich mir gerade an:
>
> Beitrag "Re: Tasterverarbeitung mit ARDUINO-C"

Das ist im Grunde Peter Daneggers Routine auf Arduino umgeschrieben.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Alexander schrieb:
> Was hat's mit den ALL_KEYS auf sich, ist das ne Maske oder ne
> Abfrage?

Steht doch in debounce.h. Das ist eine Maske für alle abzufragenden 
Tasten. Im Beispiel sind es drei Tasten, die parallel abgefragt werden:
1
#define ALL_KEYS        (KEY0 | KEY1 | KEY2)

Das musst Du dann auf Deine Belange anpassen.

von Peter D. (peda)


Lesenswert?

Alexander schrieb:
> Was hat's mit den ALL_KEYS auf sich

Gute Frage, ich sehe nirgends eine Benutzung.

von Alexander (alecxs)


Lesenswert?

Also könnte ich das nutzen um Tastenkombinationen abzufragen?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Gute Frage, ich sehe nirgends eine Benutzung.

Stimmt auch wieder. Letztendlich sind die benutzten Taster (hier 3) 
durch die defines erklärt:
1
#define KEY0_IN         8
2
#define KEY1_IN         9
3
#define KEY2_IN        10
Offenbar wollte Falk hier ursprünglich den Weg über die Maske 
beschreiten. Egal, der Code ist genau das, was der TO will.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

@ TO:
Wenn du wirklich etwas sauberes programmieren möchtest, dann schreibe 
dir eine Klasse. Hast ja schließlich Arduino und kannst C++ verwenden. 
Damit haste dann saubere Datenkapselung, saubere Methodenaufrufe usw.. 
Die ganzen Unsicherheiten fallen weg. Verwende millis() für zeitlich 
gesteuerte Abfragen und lass die Timer in Ruhe. Du hast keinerlei 
Begrenzung für I/Os. Am Ende erstellst du für jeden Pin eine Instanz 
usw.

Ich würde das jedoch noch trennen. Ein Klasse für die Pin Steuerung. 
Eingang oder Ausgang und was man damit machen kann. Und eine Klasse zum 
Entprellen. In der Entprellklasse verwendest du die Pin Klasse. Alles 
ist und bleibt gekapselt, nichts kommt sich in die Quere. Es sind alle 
Funktionen/Methoden die Arduino bietet ausreichend.

: Bearbeitet durch User
von Alexander (alecxs)


Angehängte Dateien:

Lesenswert?

Ich oder ChatGPT? Ich weiß gar nicht ob ich das alles brauche, entprellt 
ist mein Input schon. Vermutlich hab ich nur ein polling Problem.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

die bools
1
rawA/B
 gehören nicht in die loop.

Das gehört alles in die Klasse und wird intern abgehandelt.
Und übergebe der Instanz die Pinnummer als Parameter.
Stichwort Konstruktor.
Wozu soll man eine Pinnummer woanders angeben wenn diese fest zur 
Instanz gehört. Einmal angeben und dann vergessen.

Am Ende sieht so aus
1
keyA.update();
2
keyB.update();
Die update Methode kümmert sich um alles was nötig ist intern. 
Datenkapselung!

Und du fragst nur noch ab ob ein Ereignis Pressed, Released usw. 
stattgefunden hat.
Wenn es noch mehr Instanzen werden sollte, kannste das mittels Array und 
for Schleife erschlagen.
Desweiteren fehlt noch eine
1
init()
 Methode in der bspw. für Eingänge Pullups aktiv werden.

Das alles ist weiter ausbaufähig damit man Methoden für Ausgänge 
betreffend nicht ausversehen für Eingänge verwenden kann. Das ist eine 
schöne Spielweise um sich das Verständnis für Klassen anzueignen. Wenn 
du das dann noch in eine eigene Bibliothek auslagerst, kannste das immer 
wieder verwenden.

Achte darauf das es keine Multikulti Klasse wird.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Alexander schrieb:
> Vermutlich hab ich nur ein polling Problem.

Nö, das ist doch der gleiche Schrunz, wie oben schon.
Ich habe mir schon was dabei gedacht, einen Interrupt zu benutzen. 
Dadurch geht kein Tastendruck verloren. Der 1ms Systick ist doch wie 
dazu gemacht, dort den Interrupttask mit aufzurufen.
Und das Main fragt einfach nur die Erreignisse ab und muß sich um keine 
Zeitbedingungen mehr kümmern.

Das kann man auch schön CPP-style in eine Klasse schreiben:
getkey.press
getkey.rpt
getkey.state
getkey.short
getkey.long

von Alexander (alecxs)


Lesenswert?

Danke Veit,

jetzt hab ich auch mal den Nutzen von Klassen verstanden. Nutze die zwar 
aus allen möglichen Libs aber denke selbst nur in C Funktionen, von 
allein wäre ich da nicht drauf gekommen. Ich könnte das jetzt noch 
weiter ausbauen, aber ich brauche es gar nicht.

Der Knoten im Kopf hat sich gelöst. Ich war die ganze Zeit auf das 
Entprellen von einzelnen Tasten aus, aber was ich wirklich brauche ist 
das Entprellen von Tastenkombinationen! Ich habe immer die Rising Edge 
als Start für die Entprellung genommen, und hatte immer Probleme die 
Tasten zu erwischen. Aber die Tasten interessieren mich ja gar nicht.

Ich werde die Tasten nun verUNDen und die Tastenkombination dann wie 
eine einzelne Taste behandeln und entprellen. Mal sehen ob ChatGPT da 
eine Klasse dafür zaubern kann, ansonsten mach ich es mit der Funktion 
aus dem OP.

von Veit D. (devil-elec)


Lesenswert?

Peter D. schrieb:

> Ich habe mir schon was dabei gedacht, einen Interrupt zu benutzen.
> Dadurch geht kein Tastendruck verloren. Der 1ms Systick ist doch wie
> dazu gemacht, dort den Interrupttask mit aufzurufen.

Ein Hinweis. Mit Arduino, was der TO verwendet, muss man sich keinen 
zusätzlichen 1ms Systemtick bauen. Tasterabfragen benötigen auch kein 
1ms Intervall. Wenn man sich mittels millis() ein 20 bis 40ms 
"Abfragetimer" programmiert, reicht das für Taster aus. Meinetwegen noch 
als 2. Parameter für die Instanzen.

> Und das Main fragt einfach nur die Ereignisse ab und muß sich um keine
> Zeitbedingungen mehr kümmern.

> Das kann man auch schön CPP-style in eine Klasse schreiben:
> getkey.press
> getkey.rpt
> getkey.state
> getkey.short
> getkey.long

Hier stimme ich zu. Das hat er schon angefangen so zu machen.

: Bearbeitet durch User
von Alexander (alecxs)


Lesenswert?

Peter D. schrieb:
> Ich habe mir schon was dabei gedacht, einen Interrupt zu benutzen.
> Dadurch geht kein Tastendruck verloren.

Den Wrapper von Falk könnte ich problemlos übernehmen, ich hab schon 
einen xTaskCreatePinnedToCore wo ich es dazu packen könnte. Aber meine 
Tasten sind im Prinzip schon entprellt und kommen mit Interrupt rein.

von Peter D. (peda)


Lesenswert?

Alexander schrieb:
> Ich war die ganze Zeit auf das
> Entprellen von einzelnen Tasten aus, aber was ich wirklich brauche ist
> das Entprellen von Tastenkombinationen!

Was meinst Du damit, ein Tresorschloß?
Aber auch da muß man die Tasten jede für sich erkennen und dann eben mit 
den Code-Array vergleichen.

von Alexander (alecxs)


Lesenswert?

Nein das wäre eine Zahlenkombination 1234. Eine Tastenkombination ist 
z.B. Strg + Alt + Entf. Wenn man immer nur die steigende Flanke der 
einzelnen Tasten auswertet ist es schwer die gleichzeitig zu treffen.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Das ist recht einfach. Die Steuertaste[n] müssen gedrückt gehalten 
werden und werden erst ausgewertet, sobald die Flanke der Bedientaste 
geliefert wird.
Dafür sind in dem langen Thread auch Beispiele. Und auch Beispiele für 
"gleichzeitiges" Drücken.

von Harald W. (wilhelms)


Lesenswert?

Peter D. schrieb:

> Beitrag "Universelle Tastenabfrage"

In der Artikelsammlung gibts auch einen längeren Beitrag
zu diesem Thema:

https://www.mikrocontroller.net/articles/Entprellung

: Bearbeitet durch User
von Alexander (alecxs)


Lesenswert?

Alexander schrieb:
> Ich werde die Tasten nun verUNDen und die Tastenkombination dann wie
> eine einzelne Taste behandeln und entprellen.

Funktioniert jetzt.

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.