Tasterentprellen für Dummies

OP #7887122
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

Moderator Persönliche Seite #7887144
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.

OP #7887147
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.

#7887174
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.

#7887208
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.

#7887241
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.

#7887252
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

OP #7887255
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.

#7887259
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren