Skip to content

Commit 41978da

Browse files
authored
Merge pull request #2274 from jepler/commodore16keyboard
Commodore16keyboard
2 parents b81cdd0 + f180acd commit 41978da

File tree

4 files changed

+431
-1
lines changed

4 files changed

+431
-1
lines changed

.pylintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
7272

7373
# Template used to display messages. This is a python new-style format string
7474
# used to format the message information. See doc for all details
75-
msg-template='{path} {line}: {msg} ({symbol})'
75+
msg-template='{path}:{line}: {msg} ({symbol})'
7676

7777
# Set the output format. Available formats are text, parseable, colorized, json
7878
# and msvs (visual studio).You can also give a reporter class, eg
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
4+
# Commodore 16 to USB HID adapter with Adafruit KB2040
5+
#
6+
# Note that:
7+
# * This matrix is different than the (more common) Commodore 64 matrix
8+
# * There are no diodes, not even on modifiers, so there's only 2-key rollover.
9+
10+
import asyncio.core
11+
import board
12+
import keypad
13+
from adafruit_hid.keycode import Keycode as K
14+
from adafruit_hid.keyboard import Keyboard
15+
import usb_hid
16+
17+
# True to use a more POSITIONAL mapping, False to use a more PC-style mapping
18+
POSITIONAL = True
19+
20+
# Keyboard schematic from
21+
# https://archive.org/details/SAMS_Computerfacts_Commodore_C16_1984-12_Howard_W_Sams_Co_CC8/page/n9/mode/2up
22+
# 1 3 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # connector pins
23+
# R5 C7 R7 C4 R1 C5 C6 R3 R2 R4 C2 C1 R6 C3 C0 R0 # row/column in schematic
24+
# D2 D3 D4 D5 D6 D7 D8 D9 D10 MOSI MISO SCK A0 A1 A2 A3 # conencted to kb2040 at
25+
# results in the the following assignment of rows and columns:
26+
rows = [board.A3, board.D6, board.D10, board.D9, board.MOSI, board.D2, board.A0, board.D4]
27+
cols = [board.A2, board.SCK, board.MISO, board.A1, board.D5, board.D7, board.D8, board.D3]
28+
29+
# ROM listing of key values from ed7.src in
30+
# http://www.zimmers.net/anonftp/pub/cbm/src/plus4/ted_kernal_basic_src.tar.gz
31+
# shows key matrix arrangement (it's nuts)
32+
# del return £ f8 f1 f2 f3 @
33+
# 3 w a 4 z s e shift
34+
# 5 r d 6 c f t x
35+
# 7 y g 8 b h u v
36+
# 9 i j 0 m k o n
37+
# down p l up . : - ,
38+
# left * ; right escape = + /
39+
# 1 home control 2 space c=key q stop
40+
41+
# Implement an FN-key for some keys not present on the default keyboard
42+
class FnState:
43+
def __init__(self):
44+
self.state = False
45+
46+
def fn_event(self, event):
47+
self.state = event.pressed
48+
49+
def fn_modify(self, keycode):
50+
if self.state:
51+
return self.mods.get(keycode, keycode)
52+
return keycode
53+
54+
mods = {
55+
K.ONE: K.F1,
56+
K.TWO: K.F2,
57+
K.THREE: K.F3,
58+
K.FOUR: K.F4,
59+
K.FIVE: K.F5,
60+
K.SIX: K.F6,
61+
K.SEVEN: K.F7,
62+
K.EIGHT: K.F8,
63+
K.NINE: K.F9,
64+
K.ZERO: K.F10,
65+
K.F1: K.F11,
66+
K.F2: K.F12,
67+
K.UP_ARROW: K.PAGE_UP,
68+
K.DOWN_ARROW: K.PAGE_DOWN,
69+
K.LEFT_ARROW: K.HOME,
70+
K.RIGHT_ARROW: K.END,
71+
K.BACKSPACE: K.DELETE,
72+
K.F3: K.INSERT,
73+
}
74+
fn_state = FnState()
75+
76+
K_FN = fn_state.fn_event
77+
78+
# A tuple is special, it:
79+
# * Clears shift modifiers & pressed keys
80+
# * Presses the given sequence
81+
# * Releases all pressed keys
82+
# * Restores the original modifiers
83+
# It's mostly used to send a key that requires a shift keypress on a standard
84+
# keyboard (or which is mapped to a shifted key but requires that shift NOT
85+
# be pressed)
86+
#
87+
# A consequence of this is that the key will not repeat, even if it is held
88+
# down. So for example in the positional mapping, shift-1 will repeat "!"
89+
# but shift-7 will not repeat "'" and shift-0 will not repeat "^".
90+
K_AT = (K.SHIFT, K.TWO)
91+
K_PLUS = (K.SHIFT, K.EQUALS)
92+
K_ASTERISK = (K.SHIFT, K.EIGHT)
93+
K_COLON = (K.SHIFT, K.SEMICOLON)
94+
95+
# We need these mask values for the reasons discussed above
96+
MASK_LEFT_SHIFT = K.modifier_bit(K.LEFT_SHIFT)
97+
MASK_RIGHT_SHIFT = K.modifier_bit(K.RIGHT_SHIFT)
98+
MASK_ANY_SHIFT = (MASK_LEFT_SHIFT | MASK_RIGHT_SHIFT)
99+
100+
if POSITIONAL:
101+
keycodes = [
102+
K.BACKSPACE, K.ENTER, K.BACKSLASH, K.F8, K.F1, K.F2, K.F3, K_AT,
103+
K.THREE, K.W, K.A, K.FOUR, K.Z, K.S, K.E, K.LEFT_SHIFT,
104+
K.FIVE, K.R, K.D, K.SIX, K.C, K.F, K.T, K.X,
105+
K.SEVEN, K.Y, K.G, K.EIGHT, K.B, K.H, K.U, K.V,
106+
K.NINE, K.I, K.J, K.ZERO, K.M, K.K, K.O, K.N,
107+
K.DOWN_ARROW, K.P, K.L, K.UP_ARROW, K.PERIOD, K_COLON, K.MINUS, K.COMMA,
108+
K.LEFT_ARROW, K_ASTERISK, K.SEMICOLON, K.RIGHT_ARROW, K.ESCAPE, K.EQUALS, K_PLUS,
109+
K.FORWARD_SLASH, K.ONE, K_FN, K.LEFT_CONTROL, K.TWO, K.SPACE, K.ALT, K.Q, K.GRAVE_ACCENT,
110+
]
111+
112+
shifted = {
113+
K.TWO: (K.SHIFT, K.QUOTE), # double quote
114+
K.SIX: (K.SHIFT, K.SEVEN), # ampersand
115+
K.SEVEN: (K.QUOTE,), # single quote
116+
K.EIGHT: (K.SHIFT, K.NINE), # left paren
117+
K.NINE: (K.SHIFT, K.ZERO), # right paren
118+
K.ZERO: (K.SHIFT, K.SIX), # caret
119+
K_AT: (K.SHIFT, K.LEFT_BRACKET),
120+
K_PLUS: (K.SHIFT, K.RIGHT_BRACKET),
121+
K_COLON: (K.LEFT_BRACKET,),
122+
K.SEMICOLON: (K.RIGHT_BRACKET,),
123+
K.EQUALS: (K.TAB,),
124+
}
125+
else:
126+
# TODO clear/home, up/down positional arrows
127+
keycodes = [
128+
K.BACKSPACE, K.ENTER, K.LEFT_ARROW, K.F8, K.F1, K.F2, K.F3, K.LEFT_BRACKET,
129+
K.THREE, K.W, K.A, K.FOUR, K.Z, K.S, K.E, K.LEFT_SHIFT,
130+
K.FIVE, K.R, K.D, K.SIX, K.C, K.F, K.T, K.X,
131+
K.SEVEN, K.Y, K.G, K.EIGHT, K.B, K.H, K.U, K.V,
132+
K.NINE, K.I, K.J, K.ZERO, K.M, K.K, K.O, K.N,
133+
K.DOWN_ARROW, K.P, K.L, K.UP_ARROW, K.PERIOD, K.SEMICOLON, K.QUOTE, K.COMMA,
134+
K.BACKSLASH, K_ASTERISK, K.SEMICOLON, K.EQUALS, K.ESCAPE, K.RIGHT_ARROW, K.RIGHT_BRACKET,
135+
K.FORWARD_SLASH, K.ONE, K.HOME, K.LEFT_CONTROL, K.TWO, K.SPACE, K.ALT, K.Q, K.GRAVE_ACCENT,
136+
]
137+
138+
shifted = {
139+
}
140+
class AsyncEventQueue:
141+
def __init__(self, events):
142+
self._events = events
143+
144+
async def __await__(self):
145+
yield asyncio.core._io_queue.queue_read(self._events)
146+
return self._events.get()
147+
148+
def __enter__(self):
149+
return self
150+
151+
def __exit__(self, exc_type, exc_value, traceback):
152+
pass
153+
154+
class XKROFilter:
155+
"""Perform an X-key rollover algorithm, blocking ghosts if more than X keys are pressed at once
156+
157+
A key matrix without diodes can support 2-key rollover.
158+
"""
159+
def __init__(self, rollover=2):
160+
self._count = 0
161+
self._rollover = rollover
162+
self._real = [0] * 64
163+
self._ghost = [0] * 64
164+
165+
def __call__(self, event):
166+
self._ghost[event.key_number] = event.pressed
167+
if event.pressed:
168+
if self._count < self._rollover:
169+
self._real[event.key_number] = True
170+
yield event
171+
self._count += 1
172+
else:
173+
self._real[event.key_number] = False
174+
yield event
175+
self._count -= 1
176+
177+
twokey_filter = XKROFilter(2)
178+
179+
async def key_task():
180+
# Initialize Keyboard
181+
kbd = Keyboard(usb_hid.devices)
182+
183+
with keypad.KeyMatrix(rows, cols) as keys, AsyncEventQueue(keys.events) as q:
184+
while True:
185+
ev = await q
186+
for ev in twokey_filter(ev):
187+
keycode = keycodes[ev.key_number]
188+
if callable(keycode):
189+
keycode = keycode(ev)
190+
keycode = fn_state.fn_modify(keycode)
191+
if keycode is None:
192+
continue
193+
old_report_modifier = kbd.report_modifier[0]
194+
shift_pressed = old_report_modifier & MASK_ANY_SHIFT
195+
if shift_pressed:
196+
keycode = shifted.get(keycode, keycode)
197+
if isinstance(keycode, tuple):
198+
if ev.pressed:
199+
kbd.report_modifier[0] = old_report_modifier & ~MASK_ANY_SHIFT
200+
kbd.press(*keycode)
201+
kbd.release_all()
202+
kbd.report_modifier[0] = old_report_modifier
203+
elif ev.pressed:
204+
kbd.press(keycode)
205+
else:
206+
kbd.release(keycode)
207+
208+
209+
async def forever_task():
210+
while True:
211+
await asyncio.sleep(.1)
212+
213+
async def main():
214+
forever = asyncio.create_task(forever_task())
215+
key = asyncio.create_task(key_task())
216+
await asyncio.gather( # Don't forget the await!
217+
forever,
218+
key,
219+
)
220+
221+
asyncio.run(main())
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
4+
# Commodore 16 to USB HID adapter with Adafruit KB2040
5+
#
6+
# Note that:
7+
# * This matrix is different than the (more common) Commodore 64 matrix
8+
# * There are no diodes, not even on modifiers, so there's only 2-key rollover.
9+
# * This is a "physical" keymap, so that the functions of the keys are similar to the
10+
# function of a standard PC keyboard key in the same location.
11+
#
12+
# See the guide or the advanced code for more information about the key matrix
13+
14+
import board
15+
import keypad
16+
from adafruit_hid.keycode import Keycode as K
17+
from adafruit_hid.keyboard import Keyboard
18+
import usb_hid
19+
20+
rows = [board.A3, board.D6, board.D10, board.D9, board.MOSI, board.D2, board.A0, board.D4]
21+
cols = [board.A2, board.SCK, board.MISO, board.A1, board.D5, board.D7, board.D8, board.D3]
22+
23+
keycodes = [
24+
K.BACKSPACE, K.ENTER, K.LEFT_ARROW, K.F8, K.F1, K.F2, K.F3, K.LEFT_BRACKET,
25+
K.THREE, K.W, K.A, K.FOUR, K.Z, K.S, K.E, K.LEFT_SHIFT,
26+
K.FIVE, K.R, K.D, K.SIX, K.C, K.F, K.T, K.X,
27+
K.SEVEN, K.Y, K.G, K.EIGHT, K.B, K.H, K.U, K.V,
28+
K.NINE, K.I, K.J, K.ZERO, K.M, K.K, K.O, K.N,
29+
K.DOWN_ARROW, K.P, K.L, K.UP_ARROW, K.PERIOD, K.SEMICOLON, K.BACKSLASH, K.COMMA,
30+
K.MINUS, K.WINDOWS, K.QUOTE, K.EQUALS, K.ESCAPE, K.RIGHT_ARROW, K.RIGHT_BRACKET,
31+
K.FORWARD_SLASH, K.ONE, K.HOME, K.LEFT_CONTROL, K.TWO, K.SPACE, K.ALT, K.Q, K.GRAVE_ACCENT,
32+
]
33+
34+
kbd = Keyboard(usb_hid.devices)
35+
36+
with keypad.KeyMatrix(rows, cols) as keys:
37+
while True:
38+
if ev := keys.events.get():
39+
keycode = keycodes[ev.key_number]
40+
if ev.pressed:
41+
kbd.press(keycode)
42+
else:
43+
kbd.release(keycode)

0 commit comments

Comments
 (0)