Taster entprellen (debounce) mit dem ESP32 und Micropython
Taster entprellen klingt ja eigentlich ziemlich trivial, ist aber unter realen Bedingungen deutlich vertrackter als man denkt und hat mich etliche Stunden gekostet.
Was das Prellen von Tastern und Schaltern ist und wie es dazu kommt erklärt die Wikipedia besser als ich, als da kurz nachlesen.
Entprellen mit Interrupt und Callbacks – toll unter Laborbedingungen
Im Internet findet man sehr schnell “Referenzlösungen” wie zum Beispiel https://gist.github.com/jedie/8564e62b0b8349ff9051d7c5a1312ed7 , https://kaspars.net/blog/micropython-button-debounce oder die anderen TopTen Treffer bei Google, wenn man nach “micropython esp32 button debounce” oder “micropython esp32 taster entprellen” sucht. Alle basieren auf der Idee einen Interrupt zu verwenden, um herauszufinden wann der Taster gedrückt wurde und rufen dann eine Callback Funktion auf. In etwa so:
from machine import Pin, Timer def on_pressed(timer): print('pressed') def debounce(pin): # Start or replace a timer for 200ms, and trigger on_pressed. timer.init(mode=Timer.ONE_SHOT, period=200, callback=on_pressed) # Register a new hardware timer. timer = Timer(0) # Setup the button input pin with a pull-up resistor. button = Pin(0, Pin.IN, Pin.PULL_UP) # Register an interrupt on rising button input. button.irq(debounce, Pin.IRQ_RISING)
Diese Lösungen funktionieren auch in einem normalen Büro Umfeld. Bei mir haben die Buttons aber verrückt gespielt als ich die Schaltung in der Hütte installiert hatte in der ich das Licht mittels LED Streifen schalten wollte und ein Elektromotor losgelaufen ist. Im Büro hatte ich ein paar mal den gleichen Effekt als ich mit dem Bürostuhl auf dem Büro-Teppich herumgerollt bin. Nachvollziehbar wurde es als ich ein Piezo Feuerzeug in 30cm Entfernung zur Schaltung gezündet habe. Der durch den Zünder des Piezo Feuerzeugs ausgelöste elektromagnetische Impuls (EMP) und die als Antennen fungierenden Leitungen lösen den Interrupt aus.
Natürlich kann man die Schaltung vor einem EMP durch Einkapselung der Aktoren in einen Faradayschen Käfig und entsprechende Schutzschaltungen (Galvanische Trennung, Überspannungsableiter) auf allen elektrischen Zuleitungen schützen. Jede dieser Lösungen wäre aber mit viel Aufwand verbunden. Also muss eine andere und simple Lösung her.
Was haben all diese Störungen auf der Leitung gemeinsam?
Sie sind so heftig, dass sie den Interrupt auslösen, aber sehr kurz (im Mikrosekunden (µs) Bereich).
Also wie umgehen wir diese Störungen?
Als erstes lassen wir die Lösung mit dem Interrupt fallen. Egal ob wir machine.Pin.IRQ_RISING oder machine.Pin.IRQ_FALLING verwenden. Der IRQ wird durch den EMP ausgelöst. Der IRQ taugt also nicht zur verlässlichen Erkennung.
Lösung: Wir fragen den Taster mit einem Hardware Timer ab.
Moment, wenn wir einen Timer mit 10ms laufen lassen, kostet uns das nicht jede Menge Rechenleistung?
Schließlich fragen wir den Timer in 99,x% der Fälle umsonst ab.
Kurzer Check: Das Entwicklungsboard das ich verwende ist mit dem ESP-WROOM-32 Modul ausgestattet, das wiederum den Tensilica Xtensa® Dual-Core 32-bit LX6 Mikroprozessor enthält. Dieser Prozessor ähnelt dem ESP8266, hat jedoch zwei CPU-Kerne (die einzeln gesteuert werden können), arbeitet mit einer einstellbaren Taktfrequenz von 80 bis 240 MHz und erreicht eine Leistung von bis zu 600 DMIPS (Dhrystone Million Instructions Per Second) laut Datenblatt. Also sind die paar Befehle im Timer, auch wenn Micropython kein Meilenstein an Geschwindigkeit ist, herzlich egal.
Meine Lösung als eigenständige funktionierende Klasse
Getestet im realen “Außeneinsatz” neben einem Elektromotor, der jede Menge Störungen verursacht.
Die Klasse kann eigenständig aufgerufen werden und kann auch auf GitHub heruntergeladen werden. https://github.com/hagen-gloetter/LED-Strips-ESP32 : bzw. https://github.com/hagen-gloetter/LED-Strips-ESP32/blob/main/code/class_debounce.py
import time from machine import Pin from machine import Timer import sys import machine led = machine.Pin(2, machine.Pin.OUT) print('Debounce Class loaded') class debounced_Button(): """ Class to debounce a hardware button """ def __init__(self, pin=10): self.pin = Pin(pin, mode=Pin.IN, pull=Pin.PULL_UP) self.status = "OFF" self.cnt = 0 self.debounceTime = 5 # x cycles have to be done self.oldstatus = self.status def get_status(self): # Button has to be pressed 5 cycles if self.pin.value() == 0: # PULL_UP -> 0 = pressed self.cnt += 1 if self.pin.value() == 1: # PULL_UP -> 1 = not pressed self.cnt = 0 if self.cnt == self.debounceTime: # button toggle self.status = self.togglebutton() self.oldstatus = self.status if self.cnt >= self.debounceTime*100: print("button longpress") self.cnt = self.debounceTime*2 # prevent buffer overflow return self.status def get_oldstatus(self): return self.oldstatus def togglebutton(self): if self.status == "OFF": self.status = "ON" else: self.status = "OFF" return self.status global Button1 global Button2 def ButtonDebounceTimer(timer0): global Button1 global Button2 b3 = Button1.get_oldstatus() b4 = Button2.get_oldstatus() b1 = Button1.get_status() b2 = Button2.get_status() if b3 != b1 or b4 != b2: print(f"b1={b1}, b2={b2}") def main(): global Button1 global Button2 Button1 = debounced_Button(13) Button2 = debounced_Button(10) viaTimer = False if viaTimer == True: print("Start Timer") timer2 = Timer(2) timer2.init(period=10, mode=Timer.PERIODIC, callback=ButtonDebounceTimer) if viaTimer == False: i = 0 while i == 0: b3 = Button1.get_oldstatus() b4 = Button2.get_oldstatus() b1 = Button1.get_status() b2 = Button2.get_status() time.sleep_ms(10) if b3 != b1 or b4 != b2: print(f"b1={b1}, b2={b2}") if __name__ == '__main__': sys.exit(main()) print('Button init done')
Hinterlasse einen Kommentar
An der Diskussion beteiligen?Hinterlasse uns deinen Kommentar!