1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
3 # SPDX-License-Identifier: MIT
5 `keypad` - Support for scanning keys and key matrices
6 ===========================================================
7 See `CircuitPython:keypad` in CircuitPython for more details.
9 * Author(s): Melissa LeBlanc-Williams
14 from collections import deque
19 """A key transition event."""
21 def __init__(self, key_number=0, pressed=True):
23 Create a key transition event, which reports a key-pressed or key-released transition.
25 :param int key_number: the key number
26 :param bool pressed: ``True`` if the key was pressed; ``False`` if it was released.
28 self._key_number = key_number
29 self._pressed = pressed
34 return self._key_number
39 ``True`` if the event represents a key down (pressed) transition.
40 The opposite of `released`.
47 ``True`` if the event represents a key up (released) transition.
48 The opposite of `pressed`.
50 return not self._pressed
52 def __eq__(self, other):
54 Two `Event` objects are equal if their `key_number`
55 and `pressed`/`released` values are equal.
57 return self.key_number == other.key_number and self.pressed == other.pressed
60 """Returns a hash for the `Event`, so it can be used in dictionaries, etc.."""
61 return hash(self._key_number)
64 """Return a textual representation of the object"""
65 return "<Event: key_number {} {}>".format(
66 self.key_number, "pressed" if self._pressed else "released"
72 A queue of `Event` objects, filled by a `keypad` scanner such as `Keys` or `KeyMatrix`.
74 You cannot create an instance of `_EventQueue` directly. Each scanner creates an
75 instance when it is created.
78 def __init__(self, max_events):
79 self._events = deque([], max_events)
80 self._overflowed = False
84 Return the next key transition event. Return ``None`` if no events are pending.
86 Note that the queue size is limited; see ``max_events`` in the constructor of
87 a scanner such as `Keys` or `KeyMatrix`.
88 If a new event arrives when the queue is full, the event is discarded, and
89 `overflowed` is set to ``True``.
91 :return: the next queued key transition `Event`
92 :rtype: Optional[Event]
96 return self._events.popleft()
98 def get_into(self, event):
99 """Store the next key transition event in the supplied event, if available,
101 If there are no queued events, do not touch ``event`` and return ``False``.
103 The advantage of this method over ``get()`` is that it does not allocate storage.
104 Instead you can reuse an existing ``Event`` object.
106 Note that the queue size is limited; see ``max_events`` in the constructor of
107 a scanner such as `Keys` or `KeyMatrix`.
109 :return ``True`` if an event was available and stored, ``False`` if not.
114 next_event = self._events.popleft()
115 # pylint: disable=protected-access
116 event._key_number = next_event._key_number
117 event._pressed = next_event._pressed
118 # pylint: enable=protected-access
123 Clear any queued key transition events. Also sets `overflowed` to ``False``.
126 self._overflowed = False
129 """``True`` if `len()` is greater than zero.
130 This is an easy way to check if the queue is empty.
132 return len(self._events) > 0
135 """Return the number of events currently in the queue. Used to implement ``len()``."""
136 return len(self._events)
139 def overflowed(self):
141 ``True`` if an event could not be added to the event queue because it was full. (read-only)
142 Set to ``False`` by `clear()`.
144 return self._overflowed
146 def keypad_eventqueue_record(self, key_number, current):
147 """Record a new event"""
148 if len(self._events) == self._events.maxlen:
149 self._overflowed = True
151 self._events.append(Event(key_number, current))
155 def __init__(self, interval, max_events, scanning_function):
156 self._interval = interval
157 self._last_scan = time.monotonic()
158 self._events = _EventQueue(max_events)
159 self._scanning_function = scanning_function
160 self._scan_thread = threading.Thread(target=self._scanning_loop, daemon=True)
161 self._scan_thread.start()
165 """The EventQueue associated with this Keys object. (read-only)"""
170 if self._scan_thread.is_alive():
171 self._scan_thread.join()
174 """No-op used by Context Managers."""
177 def __exit__(self, exception_type, exception_value, traceback):
179 Automatically deinitializes when exiting a context. See
180 :ref:`lifetime-and-contextmanagers` for more info.
184 def _scanning_loop(self):
186 remaining_delay = self._interval - (time.monotonic() - self._last_scan)
187 if remaining_delay > 0:
188 time.sleep(remaining_delay)
189 self._last_scan = time.monotonic()
190 self._scanning_function()
193 class Keys(_KeysBase):
194 """Manage a set of independent keys."""
197 self, pins, *, value_when_pressed, pull=True, interval=0.02, max_events=64
200 Create a `Keys` object that will scan keys attached to the given sequence of pins.
201 Each key is independent and attached to its own pin.
203 An `EventQueue` is created when this object is created and is available in the
206 :param Sequence[microcontroller.Pin] pins: The pins attached to the keys.
207 The key numbers correspond to indices into this sequence.
208 :param bool value_when_pressed: ``True`` if the pin reads high when the key is pressed.
209 ``False`` if the pin reads low (is grounded) when the key is pressed.
210 All the pins must be connected in the same way.
211 :param bool pull: ``True`` if an internal pull-up or pull-down should be
212 enabled on each pin. A pull-up will be used if ``value_when_pressed`` is ``False``;
213 a pull-down will be used if it is ``True``.
214 If an external pull is already provided for all the pins, you can set
215 ``pull`` to ``False``.
216 However, enabling an internal pull when an external one is already present is not
218 it simply uses slightly more current.
219 :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
220 ``interval`` is in float seconds. The default is 0.020 (20 msecs).
221 :param int max_events: maximum size of `events` `EventQueue`:
222 maximum number of key transition events that are saved.
224 If a new event arrives when the queue is full, the oldest event is discarded.
226 self._digitalinouts = []
228 dio = digitalio.DigitalInOut(pin)
231 digitalio.Pull.DOWN if value_when_pressed else digitalio.Pull.UP
233 self._digitalinouts.append(dio)
235 self._currently_pressed = [False] * len(pins)
236 self._previously_pressed = [False] * len(pins)
237 self._value_when_pressed = value_when_pressed
239 super().__init__(interval, max_events, self._keypad_keys_scan)
242 """Stop scanning and release the pins."""
244 for dio in self._digitalinouts:
248 """Reset the internal state of the scanner to assume that all keys are now released.
249 Any key that is already pressed at the time of this call will therefore immediately cause
250 a new key-pressed event to occur.
252 self._currently_pressed = self._previously_pressed = [False] * self.key_count
256 """The number of keys that are being scanned. (read-only)"""
257 return len(self._digitalinouts)
259 def _keypad_keys_scan(self):
260 for key_number, dio in enumerate(self._digitalinouts):
261 self._previously_pressed[key_number] = self._currently_pressed[key_number]
262 current = dio.value == self._value_when_pressed
263 self._currently_pressed[key_number] = current
264 if self._previously_pressed[key_number] != current:
265 self._events.keypad_eventqueue_record(key_number, current)
268 class KeyMatrix(_KeysBase):
269 """Manage a 2D matrix of keys with row and column pins."""
271 # pylint: disable=too-many-arguments
276 columns_to_anodes=True,
281 Create a `Keys` object that will scan the key matrix attached to the given row and
283 There should not be any external pull-ups or pull-downs on the matrix:
284 ``KeyMatrix`` enables internal pull-ups or pull-downs on the pins as necessary.
286 The keys are numbered sequentially from zero. A key number can be computed
287 by ``row * len(column_pins) + column``.
289 An `EventQueue` is created when this object is created and is available in the `events`
292 :param Sequence[microcontroller.Pin] row_pins: The pins attached to the rows.
293 :param Sequence[microcontroller.Pin] column_pins: The pins attached to the colums.
294 :param bool columns_to_anodes: Default ``True``.
295 If the matrix uses diodes, the diode anodes are typically connected to the column pins,
296 and the cathodes should be connected to the row pins. If your diodes are reversed,
297 set ``columns_to_anodes`` to ``False``.
298 :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
299 ``interval`` is in float seconds. The default is 0.020 (20 msecs).
300 :param int max_events: maximum size of `events` `EventQueue`:
301 maximum number of key transition events that are saved.
303 If a new event arrives when the queue is full, the oldest event is discarded.
305 self._row_digitalinouts = []
306 for row_pin in row_pins:
307 row_dio = digitalio.DigitalInOut(row_pin)
308 row_dio.switch_to_input(
309 pull=(digitalio.Pull.UP if columns_to_anodes else digitalio.Pull.DOWN)
311 self._row_digitalinouts.append(row_dio)
313 self._column_digitalinouts = []
314 for column_pin in column_pins:
315 col_dio = digitalio.DigitalInOut(column_pin)
316 col_dio.switch_to_input(
317 pull=(digitalio.Pull.UP if columns_to_anodes else digitalio.Pull.DOWN)
319 self._column_digitalinouts.append(col_dio)
320 self._currently_pressed = [False] * len(column_pins) * len(row_pins)
321 self._previously_pressed = [False] * len(column_pins) * len(row_pins)
322 self._columns_to_anodes = columns_to_anodes
324 super().__init__(interval, max_events, self._keypad_keymatrix_scan)
326 # pylint: enable=too-many-arguments
330 """The number of keys that are being scanned. (read-only)"""
331 return len(self._row_digitalinouts) * len(self._column_digitalinouts)
334 """Stop scanning and release the pins."""
336 for row_dio in self._row_digitalinouts:
338 for col_dio in self._column_digitalinouts:
343 Reset the internal state of the scanner to assume that all keys are now released.
344 Any key that is already pressed at the time of this call will therefore immediately cause
345 a new key-pressed event to occur.
347 self._previously_pressed = self._currently_pressed = [False] * self.key_count
349 def _row_column_to_key_number(self, row, column):
350 return row * len(self._column_digitalinouts) + column
352 def _keypad_keymatrix_scan(self):
353 for row, row_dio in enumerate(self._row_digitalinouts):
354 row_dio.switch_to_output(
355 value=(not self._columns_to_anodes),
356 drive_mode=digitalio.DriveMode.PUSH_PULL,
358 for col, col_dio in enumerate(self._column_digitalinouts):
359 key_number = self._row_column_to_key_number(row, col)
360 self._previously_pressed[key_number] = self._currently_pressed[
363 current = col_dio.value != self._columns_to_anodes
364 self._currently_pressed[key_number] = current
365 if self._previously_pressed[key_number] != current:
366 self._events.keypad_eventqueue_record(key_number, current)
367 row_dio.value = self._columns_to_anodes
368 row_dio.switch_to_input(
371 if self._columns_to_anodes
372 else digitalio.Pull.DOWN
377 class ShiftRegisterKeys(_KeysBase):
378 """Manage a set of keys attached to an incoming shift register."""
393 Create a `Keys` object that will scan keys attached to a parallel-in serial-out
394 shift register like the 74HC165 or CD4021.
395 Note that you may chain shift registers to load in as many values as you need.
397 Key number 0 is the first (or more properly, the zero-th) bit read. In the
398 74HC165, this bit is labeled ``Q7``. Key number 1 will be the value of ``Q6``, etc.
400 An `EventQueue` is created when this object is created and is available in the
403 :param microcontroller.Pin clock: The shift register clock pin.
404 The shift register should clock on a low-to-high transition.
405 :param microcontroller.Pin data: the incoming shift register data pin
406 :param microcontroller.Pin latch:
407 Pin used to latch parallel data going into the shift register.
408 :param bool value_to_latch: Pin state to latch data being read.
409 ``True`` if the data is latched when ``latch`` goes high
410 ``False`` if the data is latched when ``latch goes low.
411 The default is ``True``, which is how the 74HC165 operates. The CD4021 latch is
412 the opposite. Once the data is latched, it will be shifted out by toggling the
414 :param int key_count: number of data lines to clock in
415 :param bool value_when_pressed: ``True`` if the pin reads high when the key is pressed.
416 ``False`` if the pin reads low (is grounded) when the key is pressed.
417 :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
418 ``interval`` is in float seconds. The default is 0.020 (20 msecs).
419 :param int max_events: maximum size of `events` `EventQueue`:
420 maximum number of key transition events that are saved.
422 If a new event arrives when the queue is full, the oldest event is discarded.
424 clock_dio = digitalio.DigitalInOut(clock)
425 clock_dio.switch_to_output(
426 value=False, drive_mode=digitalio.DriveMode.PUSH_PULL
428 self._clock = clock_dio
430 data_dio = digitalio.DigitalInOut(data)
431 data_dio.switch_to_input()
432 self._data = data_dio
434 latch_dio = digitalio.DigitalInOut(latch)
435 latch_dio.switch_to_output(value=True, drive_mode=digitalio.DriveMode.PUSH_PULL)
436 self._latch = latch_dio
437 self._value_to_latch = value_to_latch
439 self._currently_pressed = [False] * key_count
440 self._previously_pressed = [False] * key_count
441 self._value_when_pressed = value_when_pressed
442 self._key_count = key_count
444 super().__init__(interval, max_events, self._keypad_shiftregisterkeys_scan)
447 """Stop scanning and release the pins."""
455 Reset the internal state of the scanner to assume that all keys are now released.
456 Any key that is already pressed at the time of this call will therefore immediately cause
457 a new key-pressed event to occur.
459 self._currently_pressed = self._previously_pressed = [False] * self._key_count
463 """The number of keys that are being scanned. (read-only)"""
464 return self._key_count
468 """The ``EventQueue`` associated with this `Keys` object. (read-only)"""
471 def _keypad_shiftregisterkeys_scan(self):
472 self._latch.value = self._value_to_latch
473 for key_number in range(self._key_count):
474 self._clock.value = False
475 self._previously_pressed[key_number] = self._currently_pressed[key_number]
476 current = self._data.value == self._value_when_pressed
477 self._currently_pressed[key_number] = current
478 self._clock.value = True
479 if self._previously_pressed[key_number] != current:
480 self._events.keypad_eventqueue_record(key_number, current)
482 self._latch.value = not self._value_to_latch