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!