Skip to content

Commit dc539da

Browse files
committed
add Keys scanner, and use a common parent class
1 parent e957be7 commit dc539da

File tree

3 files changed

+131
-52
lines changed

3 files changed

+131
-52
lines changed

examples/mcp23017_scanner_keys.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2022 Neradoc
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
5+
import board
6+
from supervisor import ticks_ms
7+
from adafruit_mcp230xx.mcp23017 import MCP23017
8+
from mcp23017_scanner import McpKeysScanner
9+
10+
# MCP23017 port A/B pins
11+
PINS = [0, 1, 2, 3, 4, 10, 11, 12, 13, 14]
12+
13+
mcp = MCP23017(board.I2C())
14+
scanner = McpKeysScanner(mcp, PINS) # , irq=board.D5)
15+
16+
while True:
17+
t0 = ticks_ms()
18+
scanner.update()
19+
while event := scanner.events.get():
20+
key = event.key_number
21+
if event.pressed:
22+
print(f"Key pressed : {key}")
23+
if event.released:
24+
print(f"Key released: {key}")
25+
26+
# flood print the milliseconds passed:
27+
# print(ticks_ms() - t0)

examples/mcp23017_scanner_simpletest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
21
# SPDX-FileCopyrightText: Copyright (c) 2022 Neradoc
32
#
43
# SPDX-License-Identifier: Unlicense

mcp23017_scanner.py

Lines changed: 104 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,72 @@ def __len__(self) -> int:
136136
return len(self._outq) + len(self._inq)
137137

138138

139-
class McpMatrixScanner:
139+
class McpScanner:
140+
141+
def __init__(self,
142+
mcp: any,
143+
irq: Optional[Pin] = None,
144+
):
145+
self.mcp = mcp
146+
self.keys_state = set()
147+
self.events = EventQueue()
148+
self.irq = None
149+
if irq:
150+
self.irq = DigitalInOut(irq)
151+
self.irq.switch_to_input(Pull.UP)
152+
153+
@property
154+
def key_count(self) -> int:
155+
"""The number of keys in the scanner."""
156+
return self._key_count
157+
158+
def update(self) -> None:
159+
"""
160+
Run the scan and create events in the event queue.
161+
"""
162+
timestamp = ticks_ms()
163+
# scan the matrix, find Neo
164+
current_state = self._scan_pins()
165+
# use set algebra to find released and pressed keys
166+
released_keys = self.keys_state - current_state
167+
pressed_keys = current_state - self.keys_state
168+
# create the events into the queue
169+
for key in released_keys:
170+
self.events.append(Event(key, False, timestamp))
171+
for key in pressed_keys:
172+
self.events.append(Event(key, True, timestamp))
173+
# end
174+
self.keys_state = current_state
175+
176+
def reset(self) -> None:
177+
"""
178+
Reset the internal state of the scanner to assume that all keys are now
179+
released. Any key that is already pressed at the time of this call will
180+
therefore cause a new key-pressed event to occur on the next scan.
181+
"""
182+
self.events.clear()
183+
self.keys_state.clear()
184+
185+
def deinit(self) -> None:
186+
"""Release the IRQ pin"""
187+
if self.irq:
188+
self.irq.deinit()
189+
self.irq = None
190+
# TODO: reset the mcp configuration
191+
192+
def __enter__(self) -> "McpScanner":
193+
"""No-op used by Context Managers."""
194+
return self
195+
196+
def __exit__(self, type_er, value, traceback) -> None:
197+
"""Automatically deinitializes when exiting a context."""
198+
self.deinit()
199+
200+
201+
class McpMatrixScanner(McpScanner):
140202
"""
203+
Class to scan a matrix of keys connected to the MCP chip.
204+
141205
Columns are on port A and inputs.
142206
Rows are on port B and outputs.
143207
"""
@@ -149,22 +213,17 @@ def __init__(
149213
column_pins: Iterable[int],
150214
irq: Optional[Pin] = None,
151215
):
216+
super().__init__(mcp, irq)
152217
self._key_count = len(column_pins) * len(row_pins)
153218
self.columns = column_pins
154219
self.rows = row_pins
155-
self.mcp = mcp
156-
self.keys_state = set()
157-
self.events = EventQueue()
158220
# set port A to output (columns)
159221
mcp.iodira = 0x00
160222
# set port B to input (rows) all pull ups
161223
mcp.iodirb = 0xFF
162224
mcp.gppub = 0xFF
163225
# set interrupts
164-
self.irq = None
165226
if irq:
166-
self.irq = DigitalInOut(irq)
167-
self.irq.switch_to_input(Pull.UP)
168227
# TODO: configure mcp based on row and column numbers
169228
# to leave the other pins free to use ?
170229
mcp.interrupt_enable = 0xFF00
@@ -174,12 +233,7 @@ def __init__(
174233
mcp.io_control = 0x44 # Interrupt as open drain and mirrored
175234
mcp.clear_ints()
176235

177-
@property
178-
def key_count(self) -> int:
179-
"""The number of keys in the scanner."""
180-
return self._key_count
181-
182-
def _scan_matrix(self) -> Set[int]:
236+
def _scan_pins(self) -> Set[int]:
183237
"""Scan the matrix and return the list of keys down"""
184238
pressed = set()
185239
num_cols = len(self.columns)
@@ -198,24 +252,6 @@ def _scan_matrix(self) -> Set[int]:
198252
self.mcp.gpioa = 0xFF
199253
return pressed
200254

201-
def update(self) -> None:
202-
"""
203-
Run the scan and create events in the event queue.
204-
"""
205-
timestamp = ticks_ms()
206-
# scan the matrix, find Neo
207-
current_state = self._scan_matrix()
208-
# use set algebra to find released and pressed keys
209-
released_keys = self.keys_state - current_state
210-
pressed_keys = current_state - self.keys_state
211-
# create the events into the queue
212-
for key in released_keys:
213-
self.events.append(Event(key, False, timestamp))
214-
for key in pressed_keys:
215-
self.events.append(Event(key, True, timestamp))
216-
# end
217-
self.keys_state = current_state
218-
219255
def key_number_to_row_column(self, key_number: int) -> Tuple[int]:
220256
"""Convert key number to row, column"""
221257
row = key_number // len(self.columns)
@@ -226,26 +262,43 @@ def row_column_to_key_number(self, row: int, column: int) -> int:
226262
"""Convert row, column to key number"""
227263
return row * len(self.columns) + column
228264

229-
def reset(self) -> None:
230-
"""
231-
Reset the internal state of the scanner to assume that all keys are now
232-
released. Any key that is already pressed at the time of this call will
233-
therefore cause a new key-pressed event to occur on the next scan.
234-
"""
235-
self.events.clear()
236-
self.keys_state.clear()
237265

238-
def deinit(self) -> None:
239-
"""Release the IRQ pin"""
240-
if self.irq:
241-
self.irq.deinit()
242-
self.irq = None
243-
# TODO: reset the mcp configuration
266+
class McpKeysScanner(McpScanner):
267+
"""
268+
Class to scan a key per pin of the MCP chip.
244269
245-
def __enter__(self) -> "McpMatrixScanner":
246-
"""No-op used by Context Managers."""
247-
return self
270+
Pins 0-7 are on port A. Pins 8-15 are on port B.
271+
"""
248272

249-
def __exit__(self, type_er, value, traceback) -> None:
250-
"""Automatically deinitializes when exiting a context."""
251-
self.deinit()
273+
def __init__(
274+
self,
275+
mcp: any,
276+
pins: Iterable[int],
277+
irq: Optional[Pin] = None,
278+
):
279+
super().__init__(mcp, irq)
280+
self._key_count = len(pins)
281+
self.pins = pins
282+
self.pin_bits = sum(1 << x for x in pins)
283+
# set port A and B to input all pull ups
284+
mcp.iodir = 0xFFFF & self.pin_bits
285+
mcp.gppu = 0xFFFF & self.pin_bits
286+
# set interrupts
287+
if irq:
288+
# set interrupts
289+
mcp.interrupt_enable = 0xFFFF & self.pin_bits
290+
mcp.default_value = 0xFFFF & self.pin_bits
291+
# compare input to default value (1) or previous value (0)
292+
mcp.interrupt_configuration = 0xFFFF & self.pin_bits
293+
mcp.io_control = 0x44 # Interrupt as open drain and mirrored
294+
mcp.clear_ints()
295+
296+
def _scan_pins(self) -> Set[int]:
297+
"""Scan the buttons and return the list of keys down"""
298+
pressed = set()
299+
inputs = self.mcp.gpio & self.pin_bits
300+
for scan in self.pins:
301+
for pin in self.pins:
302+
if inputs & (1 << pin) == 0:
303+
pressed.add(pin)
304+
return pressed

0 commit comments

Comments
 (0)