[Geschlossen] Wie man (viele) Taster entprellt. Einfach und Schlampig.

Servus!

Seht das mal als eine Art Dokumentation:

Also: Wenn man in sein Projekt ein oder mehrere Taster einbauen möchte muss man diese alle einlesen und entprellen.

Ich handhabe das mittlerweile so, dass ich in meiner loop() irgendwo eine Funktion habe, welche entprellt und bei falling edge signal eine variable auf true setzt.

Im restlichen Programm muss ich nur diese Variable abfragen und anschließend wieder auf low setzten. Damit bleibt mein Code leserlich.

So weit so gut.

Jetzt kam mir gerade aber ein gewaltiger Geistesblitz:

Statt für jeden Taster eine "lastDebounceTime" Variable zu nutzen lese ich jetzt die Taster nur alle paar Millisekunden ein, damit bekomme ich das Prellen nicht mit.

Also schnell programmiert und siehe da läuft wie Butter:

#define BUTTON_PIN 2
#define LED_PIN LED_BUILTIN
#define READ_EVERY_N_MS 20

bool lastButtonState;
bool buttonPressed;
unsigned long lastRead;


void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
  //Taster einlesen auf falling edge und Entprellung
  read_Button();

  //Verarbeitung des Tastendrucks
  if (buttonPressed) { 
    buttonPressed = false;
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
  }
}

void read_Button() {
  if (millis() - lastRead >= READ_EVERY_N_MS) {
    lastRead = millis();

    //Taster auslesen:
    bool reading = !digitalRead(BUTTON_PIN);
    if (reading && !lastButtonState) {
      buttonPressed = true;
    }
    lastButtonState = reading;

    //Weitere Taster:
  }
}

Ist einfach und spart bei großen Anzahlen an Tastern sogar ordentlich RAM:

Debounce Example:
minimal 6 Bytes pro Taster (so wies drinsteht: 12 Bytes)

Mein Programm:
2 Bytes pro Taster, + einmalig 4 Bytes

Was ist eure Meinung dazu? Ich bin gespannt...

Edit: Code zwar nicht schön, aber nun ordentlich lauffähig.

Wenn es darum geht dass du duplizierten Code vermeidest heißt die Lösung "objektorientierte Programmierung"

Serenifly:
Wenn es darum geht dass du duplizierten Code vermeidest heißt die Lösung “objektorientierte Programmierung”

“Objektorientierte Programmierung” aha, interessant, das habe ich ja schon getan.

Aber wie meinst du das mit der vermeidung von dupliziertem Code?

Mir ging es bei dem Sketch zum Teil darum um das immer gleiche “drumrum” wo anders hin zu verschieben. Wenn man einen Taster ausliest will man ja zu 90% immer nur eine Aktion machen wenn er gedrückt wurde.

Verzweifler:
Was ist eure Meinung dazu?

So in der Art mache ich das auch, also Zustimmung. Allerdings wegen des Überlaufs von millis() lieber

millis() - nextRead >= READ_EVERY_N_MS;

Bei Reaktionsspielen darf die Verzögerung nur nach dem Drücken des Tasters aktiv sein.

Verzweifler:
... das habe ich ja schon getan.

Was ich von Dir sehe, ist prozeduraler Code, nix mit OOP. Bei OOP wäre read_Button die Methode einer Klasse.

Verzweifler:
Aber wie meinst du das mit der vermeidung von dupliziertem Code?

Weil du eventuell mehrere Taster auslesen willst. Dann verpackt man die Variablen und die Funktion in ein Objekt. Jeder Taster hat dann seine eigenen Daten. Ohne dass man mehr Code schreiben muss

Ouuu, das muss ich mir anschauen. Aber funktionieren tuts.

Zwecks millis() überlauf, ich habe immer meine Variante genommen, allerdings hab ich auch noch nie was so lange laufen lassen, dass das vorkam.

Was ich mich aber wunder:

unsigned long läuft bei 4.294.967.295 über.
wenn ich eine Variable aber bei 4.294.967.290 habe, also 5 ms davor,
und ich zähle 10 dazu habe ich doch 5
Oder nicht?

Ja. Genau deshalb ist die Addition da auch schlecht. Eine Substraktion passt dagegen, da das Ergebnis in den aller meisten Fällen größer als die Intervall-Zeit ist

Aber millis() läuft ja auch über und kommt wieder bei der 5 vorbei.
Müsste doch funktionieren? Da muss ich mal Versuche anstellen.

Und mein 5 min Newbie Postlimit lässt grüßen :stuck_out_tongue:

Ich habe das millis-Problem mal an einem unsigned Char nachgebildet (ja, ich hätte auch byte nehmen können, ich wollte aber auf das unsigned hinweisen). Das dauert halt nicht so lange. Die erzwungenen Typecasts müssen sein, da der Compiler sonst in int rechnet.
Da kann man das gut sehen.

unsigned char aktuelleMillis = 0, letzteMillis = 0, dauer = 10;
int cnt = 0;
void setup() {
  Serial.begin(115200);
  Serial.println("Start");
  Serial.println("Initialwerte:");
  Serial.print("Dauer = "); Serial.println(dauer);
  Serial.print("letzte = "); Serial.println(letzteMillis);
  Serial.print("aktuell"); Serial.print('\t'); Serial.print("Diff."); 
  Serial.print('\t'); Serial.println("letze");
}

void loop() {
  if (cnt <= 300) { // Damit das nicht ewig läuft
    if ((unsigned char)(aktuelleMillis - letzteMillis) >= dauer) {
      // tu was
      Serial.print(aktuelleMillis); 
      Serial.print('\t'); Serial.print((unsigned char)(aktuelleMillis - letzteMillis));
      Serial.print('\t'); Serial.print(letzteMillis); Serial.println("\tAktion"); 
      letzteMillis = aktuelleMillis;
    }   
    else {
      Serial.print(aktuelleMillis); Serial.print('\t'); 
      Serial.println((unsigned char)(aktuelleMillis - letzteMillis));
      
    }  
    aktuelleMillis++;
    cnt++;
  }    
}

Gruß Tommy

Aber millis() läuft ja auch über

Richtig!
Wenn du addierst, hast du 2 Überläufe!
Der Millis Überlauf und der Überlauf bei der Addition.
Welche du beide kompensieren musst.
Das bedarf besonderer Aufmerksamkeit

Subtrahierst du, dann hast du nur einen Überlauf und einen Unterlauf, welche sich gegenseitig, ohne jeden Mehraufwand kompensieren.
Das geht automatisch und völlig schmerzfrei.

Mal kurz so getestet:

byte fakeMillis;
byte nextX;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);

}

void loop() {
  fakeMillis = (millis() / 50) % 256;
  if(fakeMillis > nextX){
    nextX = fakeMillis + 50;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
  Serial.print(fakeMillis);
  Serial.print(" ");
  Serial.println(nextX);
}

Und.... Funktioniert NICHT!

Mein Zeitstempel "nextX" läuft nicht über! Sonst würde das gehen.

Also dann schreib ich mal meinen Code überläufsicher um, aber wieso "nextX" nicht überläuft interressiert mich.

Das funktioniert ja schließlich auch:

  nextX += 50;
  Serial.println(nextX);
  delay(200);

Danke an eure Antworten bis jetzt und gute Nacht!

Mein Zeitstempel "nextX" läuft nicht über!

Natürlich läuft der über.

fakeMillis = (millis() / 50) % 256;
Dein fakeMillis kann Werte von 0 bis 255 annehmen.

nextX = fakeMillis + 50;
49 = 255 + 50
Ein Überlauf, wie es klarer und deutlicher nicht geht.

combie:
Natürlich läuft der über.

fakeMillis = (millis() / 50) % 256;
Dein fakeMillis kann Werte von 0 bis 255 annehmen.

nextX = fakeMillis + 50;
49 = 255 + 50
Ein Überlauf, wie es klarer und deutlicher nicht geht.

Ja klar, theoretisch und sonst auch soll er überlaufen.

Aber das passiert nicht, obwohl es überhaupt gar nicht sein kann. Unglaublich aber es ist so.

Wer das nicht glauben kann soll einfach kurz meinen Sketch kurz probieren und sich das im Seriellen Monitor ansehen. Erster Wert ist "fakeMillis" und der zweite das nicht überlaufende "nextX"

Es gibt nur 3 Möglicheiten!

  1. Dein Compiler ist kaputt
  2. Mein Hirn ist kaputt
  3. Dein …

Dafür muss ich das nicht extra ausprobieren.
Das sehe ich so.
Das springt einen doch an.

Da du das nicht siehst, erkläre ich dir das:

Die Addition wird nur gemacht, wenn fakeMillis > nextX

Aber irgendwann kann fakeMillis nicht mehr größer als nextX werden.
Dann ist Ende mit Addition und nextX bleibt wie es ist.

Logisch, oder?

Du Fuchs! Nein, das ist mir nicht aufgefallen, da hab ich garnicht dran gedacht!

Abhilfe schafft da ein " = " also so:

if(fakeMillis >= nextX){...

Funktioniert zwar, aber die Led blinkt dann manchmal unregelmäßig. Weg damit.

Also Code umschreiben und mal an OOP rantasten.

Ciao.

Verzweifler:
Also Code umschreiben und mal an OOP rantasten.

Auch wenn es schöne, fertige Bibliotheken dafür gibt, habe ich das mal basierend auf einer Vorlage von combie als Ausgangspunkt weiterer Überlegungen getan:

unsigned long jetzt;

struct Taster {
  Taster(const byte pin): pin(pin), aktZustand(0), altZustand(0), vorhin(0) {}

  void init()
  {
    pinMode(pin, INPUT_PULLUP);
    aktZustand = digitalRead(pin);
    altZustand = aktZustand;
  }

  void aktualisieren()
  {
    altZustand = aktZustand;
    if (jetzt - vorhin >= 20)
    {
      aktZustand = digitalRead(pin);
      if (altZustand != aktZustand)
      {
        vorhin = jetzt;
      }
    }
  }

  bool zustand()
  {
    return aktZustand;
  }

  bool steigend()
  {
    return (!altZustand && aktZustand);
  }

  bool fallend()
  {
    return (altZustand && !aktZustand);
  }

  const byte pin;
  bool aktZustand;
  bool altZustand;
  unsigned long vorhin;
};

Taster taster[] {
  // Tasterpin
  {6},
  {7},
  {8},
  {9}
};

const byte led[] = {2, 3, 4, 5};

void setup() {
  for (Taster &t : taster) t.init();
  for (const byte &l : led) {
    pinMode(l, OUTPUT);
  }
}

void loop() {
  jetzt = millis();                      // aktuelen Zeitwert merken
  for (Taster &t : taster) t.aktualisieren();
  if (taster[0].fallend())
  {
    digitalWrite(led[0], !digitalRead(led[0]));
  }
  if (taster[1].fallend())
  {
    digitalWrite(led[1], !digitalRead(led[1]));
  }
  if (taster[2].steigend())
  {
    digitalWrite(led[2], !digitalRead(led[2]));
  }
  digitalWrite(led[3], taster[3].zustand());
}

Hallo,

schön das du dir die Mühe machst agmue. Ich würde diese Ansammlung von Daten und Methoden in eine class stecken statt in struct. Ist zwar eigentlich egal und vielleicht auch eine Glaubensfrage, weil gleich, aber class ist standardmäßig privat, struct public. struct würde ich persönlich nur für Datensammlung verwenden die zusammengehören. Noch eine persönliche Meinung. Wer solche struct und class schreiben kann, der sollte es gleich in eine Lib "auslagern", sonst müllt man sich sich das Hauptprogramm mit Codezeilen zu was wiederum den Überblick trübt. Wie gesagt ist nur meine eigene Meinung.

Doc_Arduino:
Wie gesagt ist nur meine eigene Meinung.

Danke für Deine Meinung!

Ich wollte keine perfekte Lösung, sondern nur einen Beitrag zum Thema liefern.

Ist es sinnvoll, auf eine der Variablen/Konstanten von außen zuzugreifen? Muß ich andererseits wirklich restriktiv sein?

Nach meinen ersten wackligen Gehversuchen in OOP gleich eine Bibliothek schreiben? Na mal sehen ::slight_smile: