Adafruit_CircuitPython_Debo.../adafruit_debouncer.py
Thomas Grenfell Smith 481d615920 black
2020-04-16 15:28:33 -04:00

161 lines
5.8 KiB
Python

# The MIT License (MIT)
#
# Copyright (c) 2019 Dave Astels for Adafruit Industries
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
`adafruit_debouncer`
====================================================
Debounces an arbitrary predicate function (typically created as a lambda) of 0
arguments. Since a very common use is debouncing a digital input pin, the
initializer accepts a DigitalInOut object instead of a lambda.
* Author(s): Dave Astels
Implementation Notes
--------------------
**Hardware:**
Not all hardware / CircuitPython combinations are capable of running the
debouncer correctly for an extended length of time. If this line works
on your microcontroller, then the debouncer should work forever:
``from time import monotonic_ns``
If it gives an ImportError, then the time values available in Python become
less accurate over the days, and the debouncer will take longer to react to
button presses.
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
# imports
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Debouncer.git"
import time
from micropython import const
_DEBOUNCED_STATE = const(0x01)
_UNSTABLE_STATE = const(0x02)
_CHANGED_STATE = const(0x04)
# Find out whether the current CircuitPython supports time.monotonic_ns(),
# which doesn't have the accuracy limitation.
if hasattr(time, "monotonic_ns"):
TICKS_PER_SEC = 1_000_000_000
MONOTONIC_TICKS = time.monotonic_ns
else:
TICKS_PER_SEC = 1
MONOTONIC_TICKS = time.monotonic
class Debouncer:
"""Debounce an input pin or an arbitrary predicate"""
def __init__(self, io_or_predicate, interval=0.010):
"""Make am instance.
:param DigitalInOut/function io_or_predicate: the DigitalIO or function to debounce
:param int interval: bounce threshold in seconds (default is 0.010, i.e. 10 milliseconds)
"""
self.state = 0x00
if hasattr(io_or_predicate, "value"):
self.function = lambda: io_or_predicate.value
else:
self.function = io_or_predicate
if self.function():
self._set_state(_DEBOUNCED_STATE | _UNSTABLE_STATE)
self._last_bounce_ticks = 0
self._last_duration_ticks = 0
self._state_changed_ticks = 0
# Could use the .interval setter, but pylint prefers that we explicitly
# set the real underlying attribute:
self._interval_ticks = interval * TICKS_PER_SEC
def _set_state(self, bits):
self.state |= bits
def _unset_state(self, bits):
self.state &= ~bits
def _toggle_state(self, bits):
self.state ^= bits
def _get_state(self, bits):
return (self.state & bits) != 0
def update(self):
"""Update the debouncer state. MUST be called frequently"""
now_ticks = MONOTONIC_TICKS()
self._unset_state(_CHANGED_STATE)
current_state = self.function()
if current_state != self._get_state(_UNSTABLE_STATE):
self._last_bounce_ticks = now_ticks
self._toggle_state(_UNSTABLE_STATE)
else:
if now_ticks - self._last_bounce_ticks >= self._interval_ticks:
if current_state != self._get_state(_DEBOUNCED_STATE):
self._last_bounce_ticks = now_ticks
self._toggle_state(_DEBOUNCED_STATE)
self._set_state(_CHANGED_STATE)
self._last_duration_ticks = now_ticks - self._state_changed_ticks
self._state_changed_ticks = now_ticks
@property
def interval(self):
"""The debounce delay, in seconds"""
return self._interval_ticks / TICKS_PER_SEC
@interval.setter
def interval(self, new_interval_s):
self._interval_ticks = new_interval_s * TICKS_PER_SEC
@property
def value(self):
"""Return the current debounced value."""
return self._get_state(_DEBOUNCED_STATE)
@property
def rose(self):
"""Return whether the debounced value went from low to high at the most recent update."""
return self._get_state(_DEBOUNCED_STATE) and self._get_state(_CHANGED_STATE)
@property
def fell(self):
"""Return whether the debounced value went from high to low at the most recent update."""
return (not self._get_state(_DEBOUNCED_STATE)) and self._get_state(
_CHANGED_STATE
)
@property
def last_duration(self):
"""Return the number of seconds the state was stable prior to the most recent transition."""
return self._last_duration_ticks / TICKS_PER_SEC
@property
def current_duration(self):
"""Return the number of seconds since the most recent transition."""
return (MONOTONIC_TICKS() - self._state_changed_ticks) / TICKS_PER_SEC