|
0 |
from __future__ import annotations
|
|
1 |
|
|
2 |
import board
|
|
3 |
from digitalio import DigitalInOut, Pull
|
|
4 |
from ..matrix import mapping_left_right
|
|
5 |
|
|
6 |
class Matrix:
|
|
7 |
'''
|
|
8 |
A Scanner for Keyboard Matrices with Diodes in both directions.
|
|
9 |
|
|
10 |
In a bidirectional matrix, each (col, row) crossing can be used twice -
|
|
11 |
once with a ROW2COL diode ("A"), and once with a COL2ROW diode ("B").
|
|
12 |
|
|
13 |
The raw key numbers returned by this scanner are based on this layout ("up_down"):
|
|
14 |
|
|
15 |
C1 C2 C3
|
|
16 |
+-----------
|
|
17 |
R1| A0 A1 A2
|
|
18 |
R2| A3 A4 A5
|
|
19 |
+-----------
|
|
20 |
R1| B6 B7 B8
|
|
21 |
R1| B9 B10 B11
|
|
22 |
|
|
23 |
If the physical layout of the matrix is different, you can pass a function
|
|
24 |
for `mapping`. The function is passed `len_cols` and `len_rows` and should
|
|
25 |
return a `coord_mapping` list.
|
|
26 |
Various common mappings are provided in this module, see:
|
|
27 |
- `kmk.scanners.bidirectional.mapping_left_right`
|
|
28 |
- `kmk.scanners.bidirectional.mapping_left_right_mirrored`
|
|
29 |
- `kmk.scanners.bidirectional.mapping_up_down`
|
|
30 |
- `kmk.scanners.bidirectional.mapping_up_down_mirrored`
|
|
31 |
- `kmk.scanners.bidirectional.mapping_interleave_rows`
|
|
32 |
- `kmk.scanners.bidirectional.mapping_interleave_cols`
|
|
33 |
|
|
34 |
:param cols: A sequence of pins that are the columns for matrix A.
|
|
35 |
:param rows: A sequence of pins that are the rows for matrix A.
|
|
36 |
:param mapping: A coord_mapping generator function, see above.
|
|
37 |
'''
|
|
38 |
|
|
39 |
def __init__(self, cols, rows, mapping=mapping_left_right):
|
|
40 |
self.len_cols = len(cols)
|
|
41 |
self.len_rows = len(rows)
|
|
42 |
self.half_size = self.len_cols * self.len_rows
|
|
43 |
self.keys = self.half_size * 2
|
|
44 |
|
|
45 |
self.coord_mapping = mapping(self.len_cols, self.len_rows)
|
|
46 |
|
|
47 |
# A pin cannot be both a row and column, detect this by combining the
|
|
48 |
# two tuples into a set and validating that the length did not drop
|
|
49 |
#
|
|
50 |
# repr() hackery is because CircuitPython Pin objects are not hashable
|
|
51 |
unique_pins = {repr(c) for c in cols} | {repr(r) for r in rows}
|
|
52 |
assert (
|
|
53 |
len(unique_pins) == self.len_cols + self.len_rows
|
|
54 |
), 'Cannot use a pin as both a column and row'
|
|
55 |
del unique_pins
|
|
56 |
|
|
57 |
# __class__.__name__ is used instead of isinstance as the MCP230xx lib
|
|
58 |
# does not use the digitalio.DigitalInOut, but rather a self defined one:
|
|
59 |
# https://github.com/adafruit/Adafruit_CircuitPython_MCP230xx/blob/3f04abbd65ba5fa938fcb04b99e92ae48a8c9406/adafruit_mcp230xx/digital_inout.py#L33
|
|
60 |
|
|
61 |
self.cols = [
|
|
62 |
x if x.__class__.__name__ == 'DigitalInOut' else DigitalInOut(x)
|
|
63 |
for x in cols
|
|
64 |
]
|
|
65 |
self.rows = [
|
|
66 |
x if x.__class__.__name__ == 'DigitalInOut' else DigitalInOut(x)
|
|
67 |
for x in rows
|
|
68 |
]
|
|
69 |
|
|
70 |
self.state = bytearray(self.keys)
|
|
71 |
|
|
72 |
def scan_for_changes(self):
|
|
73 |
for (inputs, outputs, flip) in [
|
|
74 |
(self.rows, self.cols, False),
|
|
75 |
(self.cols, self.rows, True),
|
|
76 |
]:
|
|
77 |
for pin in outputs:
|
|
78 |
pin.switch_to_input()
|
|
79 |
|
|
80 |
for pin in inputs:
|
|
81 |
pin.switch_to_input(pull=Pull.DOWN)
|
|
82 |
|
|
83 |
for oidx, opin in enumerate(outputs):
|
|
84 |
opin.switch_to_output(value=True)
|
|
85 |
|
|
86 |
for iidx, ipin in enumerate(inputs):
|
|
87 |
if flip:
|
|
88 |
ba_idx = oidx * len(inputs) + iidx + self.half_size
|
|
89 |
else:
|
|
90 |
ba_idx = iidx * len(outputs) + oidx
|
|
91 |
|
|
92 |
# cast to int to avoid
|
|
93 |
#
|
|
94 |
# >>> xyz = bytearray(3)
|
|
95 |
# >>> xyz[2] = True
|
|
96 |
# Traceback (most recent call last):
|
|
97 |
# File "<stdin>", line 1, in <module>
|
|
98 |
# OverflowError: value would overflow a 1 byte buffer
|
|
99 |
#
|
|
100 |
# I haven't dived too far into what causes this, but it's
|
|
101 |
# almost certainly because bool types in Python aren't just
|
|
102 |
# aliases to int values, but are proper pseudo-types
|
|
103 |
new_val = int(ipin.value)
|
|
104 |
old_val = self.state[ba_idx]
|
|
105 |
|
|
106 |
if old_val != new_val:
|
|
107 |
self.state[ba_idx] = new_val
|
|
108 |
yield self.coord_mapping.index(ba_idx), new_val
|
|
109 |
|
|
110 |
opin.switch_to_input()
|
|
111 |
|
|
112 |
matrix = Matrix(
|
|
113 |
[board.GP8, board.GP4, board.GP0, board.GP6, board.GP7, board.GP9],
|
|
114 |
[board.GP5, board.GP1, board.GP2, board.GP3, board.GP10],
|
|
115 |
)
|
|
116 |
|
|
117 |
pixels_pin = board.GP11
|
|
118 |
display_pins = { "sda": board.GP14, "scl": board.GP15 }
|
|
119 |
|
|
120 |
midi_pins = { "tx": board.GP16, "rx": None }
|
|
121 |
audio_pins = { "left_channel": board.GP12, "right_channel": board.GP13 }
|
|
122 |
i2c_pins = None
|