From 8a6d35c0c36b0c9ffdab4ccf992eaecc27acffbf Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 8 Sep 2022 15:56:08 -0500 Subject: [PATCH] more example --- .../{ => advanced}/code.py | 9 +- .../basic/code.py | 43 +++++ .../matrixwhisperer/code.py | 158 ++++++++++++++++++ 3 files changed, 205 insertions(+), 5 deletions(-) rename CircuitPython_Commodore_16_KB2040/{ => advanced}/code.py (96%) create mode 100644 CircuitPython_Commodore_16_KB2040/basic/code.py create mode 100644 CircuitPython_Commodore_16_KB2040/matrixwhisperer/code.py diff --git a/CircuitPython_Commodore_16_KB2040/code.py b/CircuitPython_Commodore_16_KB2040/advanced/code.py similarity index 96% rename from CircuitPython_Commodore_16_KB2040/code.py rename to CircuitPython_Commodore_16_KB2040/advanced/code.py index ad67e6b38..c26f3c263 100644 --- a/CircuitPython_Commodore_16_KB2040/code.py +++ b/CircuitPython_Commodore_16_KB2040/advanced/code.py @@ -8,7 +8,7 @@ # * There are no diodes, not even on modifiers, so there's only 2-key rollover. import asyncio.core -from board import * # pylint: disable=wildcard-import,unused-wildcard-import +import board import keypad from adafruit_hid.keycode import Keycode as K from adafruit_hid.keyboard import Keyboard @@ -22,10 +22,9 @@ POSITIONAL = True # 1 3 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # connector pins # R5 C7 R7 C4 R1 C5 C6 R3 R2 R4 C2 C1 R6 C3 C0 R0 # row/column in schematic # D2 D3 D4 D5 D6 D7 D8 D9 D10 MOSI MISO SCK A0 A1 A2 A3 # conencted to kb2040 at -# pylint: disable=undefined-variable -rows = [A3, D6, D10, D9, MOSI, D2, A0, D4] # give the following ... -cols = [A2, SCK, MISO, A1, D5, D7, D8, D3] -# pylint: enable=undefined-variable +# results in the the following assignment of rows and columns: +rows = [board.A3, board.D6, board.D10, board.D9, board.MOSI, board.D2, board.A0, board.D4] +cols = [board.A2, board.SCK, board.MISO, board.A1, board.D5, board.D7, board.D8, board.D3] # ROM listing of key values from ed7.src in # http://www.zimmers.net/anonftp/pub/cbm/src/plus4/ted_kernal_basic_src.tar.gz diff --git a/CircuitPython_Commodore_16_KB2040/basic/code.py b/CircuitPython_Commodore_16_KB2040/basic/code.py new file mode 100644 index 000000000..20f6bef32 --- /dev/null +++ b/CircuitPython_Commodore_16_KB2040/basic/code.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries +# SPDX-License-Identifier: MIT + +# Commodore 16 to USB HID adapter with Adafruit KB2040 +# +# Note that: +# * This matrix is different than the (more common) Commodore 64 matrix +# * There are no diodes, not even on modifiers, so there's only 2-key rollover. +# * This is a "physical" keymap, so that the functions of the keys are similar to the +# function of a standard PC keyboard key in the same location. +# +# See the guide or the advanced code for more information about the key matrix + +import board +import keypad +from adafruit_hid.keycode import Keycode as K +from adafruit_hid.keyboard import Keyboard +import usb_hid + +rows = [board.A3, board.D6, board.D10, board.D9, board.MOSI, board.D2, board.A0, board.D4] +cols = [board.A2, board.SCK, board.MISO, board.A1, board.D5, board.D7, board.D8, board.D3] + +keycodes = [ + K.BACKSPACE, K.ENTER, K.LEFT_ARROW, K.F8, K.F1, K.F2, K.F3, K.LEFT_BRACKET, + K.THREE, K.W, K.A, K.FOUR, K.Z, K.S, K.E, K.LEFT_SHIFT, + K.FIVE, K.R, K.D, K.SIX, K.C, K.F, K.T, K.X, + K.SEVEN, K.Y, K.G, K.EIGHT, K.B, K.H, K.U, K.V, + K.NINE, K.I, K.J, K.ZERO, K.M, K.K, K.O, K.N, + K.DOWN_ARROW, K.P, K.L, K.UP_ARROW, K.PERIOD, K.SEMICOLON, K.BACKSLASH, K.COMMA, + K.MINUS, K.KEYPAD_ASTERISK, K.QUOTE, K.EQUALS, K.ESCAPE, K.RIGHT_ARROW, K.RIGHT_BRACKET, + K.FORWARD_SLASH, K.ONE, K.HOME, K.LEFT_CONTROL, K.TWO, K.SPACE, K.ALT, K.Q, K.GRAVE_ACCENT, +] + +kbd = Keyboard(usb_hid.devices) + +with keypad.KeyMatrix(rows, cols) as keys: + while True: + if ev := keys.events.get(): + keycode = keycodes[ev.key_number] + if ev.pressed: + kbd.press(keycode) + else: + kbd.release(keycode) diff --git a/CircuitPython_Commodore_16_KB2040/matrixwhisperer/code.py b/CircuitPython_Commodore_16_KB2040/matrixwhisperer/code.py new file mode 100644 index 000000000..803555613 --- /dev/null +++ b/CircuitPython_Commodore_16_KB2040/matrixwhisperer/code.py @@ -0,0 +1,158 @@ +# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries +# SPDX-License-Identifier: MIT + +# KeyMatrix Whisperer +# +# Interactively determine a matrix keypad's row and column pins +# +# Wait until the program prints "press keys now". Then, press and hold a key +# until it registers. Repeat until all rows and columns are identified. If your +# keyboard matrix does NOT have dioes, you MUST take care to only press a +# single key at a time. +# +# How identification is performed: When a key is pressed _some_ pair of I/Os +# will be connected. This code repeatedly scans all possible pairs, recording +# them. The very first pass when no key is pressed is recorded as "junk" so it +# can be ignored. +# +# Then, the first I/O involved in the first non-junk press is arbitrarily +# recorded as a "row pin". If the matrix does not have diodes, this can +# actually vary from run to run or depending on the first key you pressed. The +# only net effect of this is that the row & column lists are exchanged. +# +# After enough key presses, you'll get a full list of "row" and "column" pins. +# For instance, on the Commodore 16 keyboard you'd get 8 row pins and 8 column pins. +# +# This doesn't help determine the LOGICAL ORDER of rows and columns or the +# physical layout of the keyboard. You still have to do that for yourself. + +import board +import microcontroller +from digitalio import DigitalInOut, Pull + +# List of pins to test, or None to test all pins +IO_PINS = None # [board.D0, board.D1] +# Which value(s) to set the driving pin to +values = [True] # [True, False] + +def discover_io(): + return [pin_maybe for name in dir(microcontroller.pin) if isinstance(pin_maybe := getattr(microcontroller.pin, name), microcontroller.Pin)] + +def pin_lookup(pin): + for i in dir(board): + if getattr(board, i) is pin: return i + for i in dir(microcontroller.pin): + if getattr(microcontroller.pin, i) is pin: return i + +# Find all I/O pins, if IO_PINS is not explicitly set above +if IO_PINS is None: + IO_PINS = discover_io() + +# Initialize all pins as inputs, make a lookup table to get the name from the pin +ios_lookup = dict([(pin_lookup(pin), DigitalInOut(pin)) for pin in IO_PINS]) +ios = ios_lookup.values() +ios_items = ios_lookup.items() +for io in ios: + io.switch_to_input(pull=Pull.UP) + +# Partial implementation of 'defaultdict' class from standard Python +# from https://github.com/micropython/micropython-lib/blob/master/python-stdlib/collections.defaultdict/collections/defaultdict.py +class defaultdict: + @staticmethod + def __new__(cls, default_factory=None, **kwargs): + # Some code (e.g. urllib.urlparse) expects that basic defaultdict + # functionality will be available to subclasses without them + # calling __init__(). + self = super(defaultdict, cls).__new__(cls) + self.d = {} + return self + + def __init__(self, default_factory=None, **kwargs): + self.d = kwargs + self.default_factory = default_factory + + def __getitem__(self, key): + try: + return self.d[key] + except KeyError: + v = self.__missing__(key) + self.d[key] = v + return v + + def __setitem__(self, key, v): + self.d[key] = v + + def __delitem__(self, key): + del self.d[key] + + def __contains__(self, key): + return key in self.d + + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + return self.default_factory() + +# Track combinations that were pressed, including ones during the "junk" scan +pressed_or_junk = defaultdict(set) +# Track combinations that were pressed, excluding the "junk" scan +pressed = defaultdict(set) +# During the first run, anything scanned is "junk". Could occur for unused pins. +first_run = True +# List of pins identified as rows and columns +rows = [] +cols = [] +# The first pin identified is arbitrarily called a 'row' pin. +row_arbitrarily = None + +while True: + changed = False + last_pressed = None + for value in values: + pull = [Pull.UP, Pull.DOWN][value] + for io in ios: + io.switch_to_input(pull=pull) + for name1, io1 in ios_items: + io1.switch_to_output(value) + for name2, io2 in ios_items: + if io2 is io1: continue + if io2.value == value: + if first_run: + pressed_or_junk[name1].add(name2) + pressed_or_junk[name2].add(name1) + elif name2 not in pressed_or_junk[name1]: + if row_arbitrarily is None: row_arbitrarily = name1 + pressed_or_junk[name1].add(name2) + pressed_or_junk[name2].add(name1) + if name2 not in pressed[name1]: + pressed[name1].add(name2) + pressed[name2].add(name1) + changed = True + if name2 in pressed[name1]: + last_pressed = (name1, name2) + print("Key registered. Release to continue") + while io2.value == value: pass + io1.switch_to_input(pull=pull) + if first_run: + print("Press keys now") + first_run = False + elif changed: + rows = set([row_arbitrarily]) + cols = set() + to_check = [row_arbitrarily] + for check in to_check: + for other in pressed[check]: + if other in rows or other in cols: continue + if check in rows: + cols.add(other) + else: + rows.add(other) + to_check.append(other) + + rows = sorted(rows) + cols = sorted(cols) + if changed or last_pressed: + print("Rows", len(rows), *rows) + print("Cols", len(cols), *cols) + print("Last pressed", *last_pressed) + print()