OneButton es una librería que facilita el control de pulsadores, permitiendo detectar y programar la respuesta del microcontrolador ante un clic, doble clic, inicio de pulsación larga, pulsación larga -mantenida- y fin de pulsación larga.
- La necesidad de que el PIN de entrada siempre tenga un estado de tensión (3.3 V ó 0 V) que se alternará en función de la posición del pulsador (abierto o cerrado).
- Los rebotes (bounce) que son las fluctuaciones de la señal entre el estado alto (HIGH) y el bajo (LOW) generadas al presionar o soltar un pulsador. Se deben corregir -para evitar detectar falsas pulsaciones– ya sea por hardware, por software o utilizando simultáneamente hardware y software.
RESISTENCIAS PULL UP / PULL DOWN
Como se ha dicho, es necesario conectar el PIN de entrada de datos a un estado de tensión (3.3V ó 0V).
El esquema del circuito a utilizar depende del estado que se quiere obtener con el pulsador abierto y con el pulsador cerrado, en cualquier caso es necesaria una resistencia (Pull Up o Pull Down) para evitar cortocircuitos.
- La resistencia PULL UP fuerza un estado ALTO –HIGH-(3.3 V) con el pulsador abierto y BAJO –LOW– (0 V -GND-) con el pulsador cerrado.
- La resistencia PULL DOWN fuerza un estado BAJO –LOW– (0 V -GND-) con el pulsador abierto y ALTO –HIGH– (3.3 V) con el pulsador cerrado.
El valor de la resistencia se determina por la intensidad necesaria para al accionar el pulsador y por el ruido en la medición (autoridad del Pull Up/Down). Con habitual utilizar resistencias entre 4.7 kΩ o 10 kΩ los microcontroladores ESP32.
Este es un ejemplo de conexión de un circuito con una resistencia Pull Up con entrada en el PIN 02:
Se puede evitar el uso de resistencias externas, habilitando por software las resistencias Pull Up y Pull Down de que disponen los microcontroladores. Su valor varía en cada modelo de microcontrolador pero tienen un rango de RUp = 30 … 80 kΩ (Pull Up) y RDown = 17 kΩ (Pull Down).
El anterior circuito se vería simplificado quedando así:
REBOTES
En electrónica, cuando se utiliza un pulsador en el cambio de señal genera «ruido» en un intervalo de unos microsegundos que le puede interpretar como múltiples «pulsaciones», siendo incierto el resultado final de la pulsación. Si el número total de «pulsaciones» que detecta el microcontrolador -fruto de los rebotes- es impar cambiará de estado y si es par mantendrá el mismo estado, por lo que resulta necesario evitarlas.
Se pueden evitar los rebotes por hardware, por software o combinando ambos sistemas.
- Para evitarlos hardware se utilizan dispositivos electrónicos para filtrar señal. Lo más sencillo es colocar un condensador del orden de 1 uF en paralelo con el pulsador, que permitirá filtrar la mayoría del “ruido”.
- Para evitarlos software se deberán ignorar las «pulsaciones» en el intervalo en que se produce el «ruido». Un intervalo de tiempo (threshold) de entre 100 y 200 ms suele ser adecuado, pero hay casos en los que se debe ajustar de forma que se elimine el rebote, pero no se ignoren posibles eventos cercanos «verdaderos» .
OneButton
A continuación se transcribe el código de la librería OneButton. Para utilizarla lo más práctico es que se guarde en el directorio /lib del microcontrolador.
Se puede hacer fácilmente con Thonny, únicamente es necesario copiar el código a un nuevo fichero y seleccionar Archivo → Guardar como → MicroPython Device → /lib/OneButton.py
# Librería para detectar pulsaciones de botones, dobles pulsaciones y patrones de pulsación larga en un solo botón. # Copyright (c) de Matthias Hertel, http://www.mathertel.de # Esta librería tiene una licecia de software libre BSD. Ver http://www.mathertel.de/License.aspx # Más informanción en: http://www.mathertel.de/Arduino # ----- # 02.10.2010 creado por Matthias Hertel para Arduino # 21.05.2016 portada a micropython por Thomas Gfüllner. Ver https://github.com/tgfuellner/micropython/commit/aae094389e925e87f7ad06a30156976a9e93dad5 # 22.11.2020 revisado y traducido por https://esploradores.com from machine import Pin from utime import ticks_ms, ticks_diff class Button: def __init__(self, pinNr, activeLow, externalResistor=False): if externalResistor: self._pin = Pin(pinNr, Pin.IN) else: self._pin = Pin(pinNr, Pin.IN, Pin.PULL_UP if activeLow else Pin.PULL_DOWN) self._debounceTicks = 50 # Milisegundos en la que permanece inactivo desde la pulsación (para evitar detectar rebotes). self._clickTicks = 500 # Milisegundos de intervalo entre pulsación y pulsación para detectar la doble pulsación. self._pressTicks = 2000 # Milisegundos que debe estar presionado el botón para que se detecte una pulsación larga. # Configura el ESTADO INICIAL: self._state = 0 # -> Estado de la máquina (0). self._isLongPressed = False # -> Pulsación larga no iniciada. if (activeLow): # -> Estado del botón (en función de como se conecte). # El botón une el PIN con el GND cuando se presiona (activeLow = True). self._buttonReleased = 1 # self._buttonPressed = 0 else: # El botón une el PIN con VCC (3.3V) cuando se presiona (activeLow = False). self._buttonReleased = 0 self._buttonPressed = 1 self._clickFunc = None # -> Funciones iniciales (antes de ser configuradas para su uso). self._doubleClickFunc = None self._longPressStartFunc = None self._duringLongPressFunc = None self._longPressStopFunc = None self._startTime = None # -> Tiempo de referencia # Configura los PARÁMETROS DE TIEMPOS DE EJECUCIÓN: def setClickTicks(self, ticks): # -> Milisegundos entre pulsación y pulsación para detectar la doble pulsación. self._clickTicks = ticks def setPressTicks(self, ticks): # -> Milisegundos que de estar presionado el botón para que se detecte una pulsación larga. self._pressTicks = ticks # FUNCIONES a las que se llamará cuando se presione el botón de la forma especificada. def attachClick(self, newFunction): # -> Cuando se realiza una PULSACIÓN self._clickFunc = newFunction def attachDoubleClick(self, newFunction): # -> Cuando se realiza una DOBLE PULSACIÓN self._doubleClickFunc = newFunction def attachLongPressStart(self, newFunction): # -> Cuando se INICIA una PULSACIÓN LARGA self._longPressStartFunc = newFunction def attachDuringLongPress(self, newFunction): # -> Mientra se MANTIENE una PULSACIÓN LARGA self._duringLongPressFunc = newFunction def attachLongPressStop(self, newFunction): # -> Cuando se FINALIZA una PULSACIÓN LARGA self._longPressStopFunc = newFunction # ----- Funciones de la máquina de estado finitito (FSM - Finite State Machine) http://www.mathertel.de/Arduino/OneButtonLibrary.aspx ----- def isLongPressed(self): return self._isLongPressed # Función para manejar los eventos de botón. def tick(self): # Detecta la información de entrada. buttonLevel = self._pin.value() now = ticks_ms() # Implementación de la máquina de estado. if self._state == 0: # Espera a que el botón seleccionado sea presionado. if (buttonLevel == self._buttonPressed): # Si se presiona... self._state = 1 # ... pasa al estado 1 self._startTime = now # Recuerda el tiempo de inicio elif self._state == 1: # Espera a que el botón seleccionado sea liberado. if (buttonLevel == self._buttonReleased) and (ticks_diff(now, self._startTime) < self._debounceTicks): # Si el botón se libera demasiado rápido se supone que es un rebote y... self._state = 0 # ... vuelve al estado 0 sin llamar a ninguna función. elif buttonLevel == self._buttonReleased: # Si el botón no se libera demasiado rápido, pero se libera antes de una pulsación larga... self._state = 2 # ... pasa al estado 2 elif (buttonLevel == self._buttonPressed) and (ticks_diff(now, self._startTime) > self._pressTicks): # Si la pulsación es larga... self._isLongPressed = True; if self._longPressStartFunc: # ... 1º ejecuta la función para INICIO de PULSACIÓN LARGA. self._longPressStartFunc(self) if self._duringLongPressFunc: # ... 2º ejecuta la función de MANTENER PULSACIÓN LARGA. self._duringLongPressFunc(self) self._state = 6 # ... 3º pasa al estado 6. else: # En caso contrario... pass # ... espera. Mantiene el estado 1. elif self._state == 2: # Verifica si el botón se pulsa por 2ª vez en el tiempo de espera de la 2ª pulsación o se agota el tiempo de espera. if ticks_diff(now, self._startTime) > self._clickTicks: # Si se agota el tiempo de espera sin recibir la 2ª puldación... if self._clickFunc: # ... 1º ejecuta la función de una PULSACIÓN. self._clickFunc(self) self._state = 0 # ... 2º pasa al estado 0. elif buttonLevel == self._buttonPressed: # Si recibe la 2ª pulsación... self._state = 3 # ... pasa al estado 3 elif self._state == 3: # Espera a que el botón después de la segunda pulsación sea liberado. if buttonLevel == self._buttonReleased: # Si se libera... if self._doubleClickFunc: # ... 1º ejecuta la función de DOBLE PULSACIÓN. self._doubleClickFunc(self) self._state = 0 # ... 2º pasa al estado 0. elif self._state == 6: # Espera a que el botón después de una pulsación larga sea liberado. if buttonLevel == self._buttonReleased: # Si se libera... self._isLongPressed = False if self._longPressStopFunc: # ... 1º ejecuta la función de FIN de PULSACIÓN LARGA. self._longPressStopFunc(self) self._state = 0 # ... 2º pasa al estado 0. else: # Si no se libera... self._isLongPressed = True # ... ejecuta la función de MANTENER PULACIÓN LARGA. if self._duringLongPressFunc: self._duringLongPressFunc(self)
La librería implementa una máquina de estado finito (FSM – Finite State Machine) con el siguiente diagrama de estados:
Cada vez que se llama a la función tick() se analiza la configuración (conexión pull up/down con resistencia externa/interna) y la situación de estado (clic, doble clic, inicio de pulsación larga, pulsación larga -mantenida- o fin de pulsación larga). Cuando es aplicable se llama a una función externa y/o se cambia el estado.
USO DE LA LIBRERÍA OneButton
Para escribir un script para gestionar uno o varios pulsadores se deben seguir los siguientes pasos:
- Importar la librería
- Definir las funciones que se desean crear. Las funciones posibles son las siguientes:
- Función para un clic (click).
- Función para una doble clic (dclick).
- Función para el inicio de pulsación larga (startLong).
- Función con pulsación larga -mantenida- (duringLong).
- Función para el fin de pulsación larga (stopLong).
- Crear un objeto para cada pulsador. Se deben definir tres parámetros:
- pinNr: número del pin que recibe la entrada de datos del botón.
- activeLow: estado de tensión con el pulsadora abierto y cerrado. Puede tomar dos valores:
- True → resistencia Pull Up (Pulsador abierto 3.3V – Pulsador cerrado 0V).
- False → resistencia Pull Down (Pulsador abierto 0V – Pulsador cerrado 3.3V).
- externalResistor: tipo de resistencia. Puede tomar dos valores:
- True → resistencia externa.
- False → resistencia interna.
- Crear un bucle en el que se verifique el estado de cada pulsador de forma repetitiva -utilizando tick()-.
Por ejemplo, el script para un pulsador con resistencia interna Pull Up (Pulsador abierto 3.3V – Pulsador cerrado OV) conectado con el PIN 02, para imprimir el tipo de pulsación cuando se utilice el pulsador, sería el siguiente:
import OneButton from utime import ticks_ms, ticks_diff def click(pin): print('Pulsación - %s' % pin.name) def dclick(pin): print('Doble pulsación') def startLong(pin): print('Inicio - pulsación larga') def duringLong(pin): print('Mantiene - pulsación larga') def stopLong(pin): print('Fin - pulsación larga') D2 = OneButton.Button(pinNr=2, activeLow=True, externalResistor=False) D2.name = 'PIN2' D2.attachClick(click) D2.attachDoubleClick(dclick) D2.attachLongPressStart(startLong) D2.attachDuringLongPress(duringLong) D2.attachLongPressStop(stopLong) check_time = 25 startTime = ticks_ms() while (True): if ticks_diff(ticks_ms(), startTime) > check_time: D2.tick() startTime = ticks_ms()
POSIBLES CIRCUITOS (pull up/down con/sin resistencia externa)
Estos son los posibles circuitos que se pueden construir, según se ha explicado (con independencia de que se quieran colocar algún elemento en el circuito para filtrar el «ruido» de la pulsación):
Y de esta forma podríamos instanciar objetos para cada uno de los circuitos:
# Circuito PULL-UP con RESISTENCIA EXTERNA PU_RE = OneButton.Button(pinNr=2, activeLow=True, externalResistor=True) # Circuito PULL-UP con RESISTENCIA INTERNA PU_RI = OneButton.Button(pinNr=2, activeLow=True, externalResistor=False) #_________________________________________________________________________ # Circuito PULL-DOWN con RESISTENCIA EXTERNA PD_RE = OneButton.Button(pinNr=2, activeLow=False, externalResistor=True) # Circuito PULL-DOWN con RESISTENCIA INTERNA PD_RI = OneButton.Button(pinNr=2, activeLow=False, externalResistor=False)
Ejemplo con 2 botones
import OneButton from utime import ticks_ms, ticks_diff def click(pin): print('Pulsación - %s' % pin.name) def dclick(pin): print('Pulsación doble - %s' % pin.name) def startLong(pin): print('Inicio - pulsación larga - %s' % pin.name) def duringLong(pin): print('Mantiene - pulsación larga - %s' % pin.name) def stopLong(pin): print('Fin - pulsación larga - %s' % pin.name) D2 = OneButton.Button(pinNr=2, activeLow=False, externalResistor=False) D2.name = 'PIN2' D2.attachClick(click) D2.attachDoubleClick(dclick) D2.attachLongPressStart(startLong) D2.attachDuringLongPress(duringLong) D2.attachLongPressStop(stopLong) D4 = OneButton.Button(pinNr=4, activeLow=False, externalResistor=False) D4.name = 'PIN4' D4.attachClick(click) D4.attachDoubleClick(dclick) D4.attachLongPressStart(startLong) D4.attachDuringLongPress(duringLong) D4.attachLongPressStop(stopLong) check_time = 25 startTime = ticks_ms() while (True): if ticks_diff(ticks_ms(), startTime) > check_time: D2.tick() D4.tick() startTime = ticks_ms()
Leave a Reply
Tu correo electrónico está seguro.
You must be logged in to post a comment.