Skip to content

Instantly share code, notes, and snippets.

@MMcM
Last active January 18, 2022 03:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MMcM/44691c27d8dfa10b3937bdfbda1f06bd to your computer and use it in GitHub Desktop.
Save MMcM/44691c27d8dfa10b3937bdfbda1f06bd to your computer and use it in GitHub Desktop.
USB_VID = 0x239A
USB_PID = 0x8058
USB_PRODUCT = "Serpente"
USB_MANUFACTURER = "arturo182"
CHIP_VARIANT = SAMD21E18A
CHIP_FAMILY = samd21
SPI_FLASH_FILESYSTEM = 1
EXTERNAL_FLASH_DEVICE_COUNT = 1
EXTERNAL_FLASH_DEVICES = GD25Q32C
LONGINT_IMPL = NONE
CIRCUITPY_AUDIOBUSIO = 0
CIRCUITPY_FREQUENCYIO = 0
CIRCUITPY_GAMEPAD = 0
# Save some more flash space
CIRCUITPY_ANALOGIO = 0
CIRCUITPY_PULSEIO = 0
CIRCUITPY_ROTARYIO = 0
CIRCUITPY_RTC = 0
CIRCUITPY_USB_MIDI = 0
CIRCUITPY_TOUCHIO = 0
SUPEROPT_GC = 0
CFLAGS_BOARD = --param max-inline-insns-auto=15
ifeq ($(TRANSLATION), zh_Latn_pinyin)
RELEASE_NEEDS_CLEAN_BUILD = 1
CFLAGS_INLINE_LIMIT = 35
endif
ifeq ($(TRANSLATION), ja)
RELEASE_NEEDS_CLEAN_BUILD = 1
CFLAGS_INLINE_LIMIT = 35
endif
ifeq ($(TRANSLATION), de_DE)
RELEASE_NEEDS_CLEAN_BUILD = 1
CFLAGS_INLINE_LIMIT = 35
SUPEROPT_VM = 0
endif
FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HID
# Four reed switches from an old Soviet calculator connected to a Serpente.
import board
from tiny_kbd import Keyboard, ScanDirection, KC
keyboard = Keyboard()
keyboard.rows = ( board.D0, )
keyboard.cols = ( board.D1, board.D2, board.D3, board.D4 )
keyboard.scan_direction = ScanDirection.ROWS
# Proof of concept of various tiny-kbd.KC possibilities.
keyboard.keymap = [
[
[ KC.VOLUME_INCREMENT, KC.VOLUME_DECREMENT, KC.ESCAPE, KC.MO(1) ],
],
[
[ KC.SEND_STRING('hello\n'), KC.SEND_STRING('goodbye\n'), KC.ALT(KC.TAB), KC.TRANSPARENT ],
],
]
if __name__ == '__main__':
keyboard.run()
# CircuitPython keyboard matrix for smaller memory devices such as Cortex M0.
from micropython import const
from adafruit_hid.keyboard import Keyboard as Keyboard_HID
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
import digitalio
import usb_hid
class Action:
TRANSPARENT = const(-1)
KEYBOARD_CODE = const(0)
CONSUMER_CODE = const(1)
STRING = const(2)
LAYER_MOMENTARY = const(8)
LAYER_MOMENTARY_MODIFIER = const(9)
LAYER_TOGGLE = const(10)
LAYER_SET = const(11)
TRANSPARENT_ACTION = (Action.TRANSPARENT,)
class ScanDirection:
ROWS = const(0)
COLUMNS = const(1)
def _as_io(x):
# Allow MCP230xx as well as board GPIO.
return x if x.__class__.__name__ is 'DigitalInOut' else digitalio.DigitalInOut(x)
class MatrixScanner:
def __init__(self, rows, cols, direction):
if direction == ScanDirection.ROWS:
self.outputs = [_as_io(x) for x in rows]
self.inputs = [_as_io(x) for x in cols]
self.event = lambda o,i,s: (o, i, s)
elif direction == ScanDirection.COLUMNS:
self.outputs = [_as_io(x) for x in cols]
self.inputs = [_as_io(x) for x in rows]
self.event = lambda o,i,s: (i, o, s)
else:
raise ValueError('invalid scan direction: %s' % direction)
self.num_out = len(self.outputs)
if self.num_out == 0:
self.num_out = 1 # For direct case, all inputs.
self.num_in = len(self.inputs)
num_bytes = (self.num_in + 7) // 8
self.key_states = [bytearray(num_bytes) for i in range(self.num_out)]
for pin in self.outputs:
pin.switch_to_input()
for pin in self.inputs:
pin.switch_to_input(pull=digitalio.Pull.UP)
def __iter__(self):
self.out_index = 0
self.out_selected = False
return self
def __next__(self):
while True:
if not self.out_selected:
if self.out_index >= self.num_out:
raise StopIteration
if self.out_index < len(self.outputs):
self.outputs[self.out_index].switch_to_output(value=False) # Ground
self.in_index = 0
self.selected_key_states = self.key_states[self.out_index]
self.out_selected = True
pressed = not self.inputs[self.in_index].value
byte_index = self.in_index // 8
byte_mask = 1 << (self.in_index % 8)
was_pressed = (self.selected_key_states[byte_index] & byte_mask) != 0
if pressed == was_pressed:
event = None
else:
self.selected_key_states[byte_index] ^= byte_mask
event = self.event(self.out_index, self.in_index, pressed)
self.in_index += 1
if self.in_index >= self.num_in:
if self.out_index < len(self.outputs):
self.outputs[self.out_index].switch_to_input() # Hi-Z
self.out_index += 1
self.out_selected = False
if event is not None:
return event
class _Keymap:
def __init__(self, layers):
self.layers = layers
self.layer_mask = 1
def __getitem__(self, rc):
row, col = rc
lidx = len(self.layers) - 1
while lidx >= 0:
if self.layer_mask & (1 << lidx):
layer = self.layers[lidx]
cols = layer[row]
key = cols[col]
if key != TRANSPARENT_ACTION:
return key
lidx -= 1
return None
def add(self, l):
self.layer_mask |= (1 << l)
def remove(self, l):
self.layer_mask &= ~(1 << l)
def toggle(self, l):
self.layer_mask ^= (1 << l)
def set(self, l):
self.layer_mask = (1 << l) | 1
class Keyboard:
direct = None
rows = None
cols = None
scan_direction = None
keymap = None
matrix_scanner = MatrixScanner
layout = None
def run(self):
if self.direct is not None:
if self.rows is not None or self.cols is not None or self.scan_direction is not None:
raise ValueError('cannot set both direct and rows/cols')
matrix = self.matrix_scanner((), self.direct, ScanDirection.ROWS)
elif self.rows is not None and self.cols is not None and self.scan_direction is not None:
matrix = self.matrix_scanner(self.rows, self.cols, self.scan_direction)
else:
raise ValueError('must set all of rows and cols and scan_direction')
if self.keymap is None:
raise ValueError('must specify keymap')
self._keymap = _Keymap(self.keymap)
self.keyboard_hid = Keyboard_HID(usb_hid.devices)
self.consumer_control = None
while True:
for row, col, pressed in matrix:
key = self._keymap[row, col]
if key is not None:
self.action(key, pressed)
def action(self, key, pressed):
if isinstance(key, int):
if pressed:
self.keyboard_hid.press(key)
else:
self.keyboard_hid.release(key)
elif isinstance(key, str):
if pressed:
if self.layout is None:
self.layout = KeyboardLayoutUS(self.keyboard_hid)
self.layout.write(key)
elif isinstance(key, tuple):
self.extended_action(key, pressed)
else:
raise ValueError('invalid key binding: %s of type %s' % (key, type(key)))
def extended_action(self, key, pressed):
action = key[0]
if action == Action.KEYBOARD_CODE:
keys = key[1:]
if pressed:
self.keyboard_hid.press(*keys)
else:
self.keyboard_hid.release(*keys)
elif action == Action.CONSUMER_CODE:
if self.consumer_control is None:
self.consumer_control = ConsumerControl(usb_hid.devices)
if pressed:
self.consumer_control.send(key[1])
elif action == Action.STRING:
self.action(key[1], pressed)
elif action == Action.LAYER_MOMENTARY:
if pressed:
self._keymap.add(key[1])
else:
self._keymap.remove(key[1])
elif action == Action.LAYER_MOMENTARY_MODIFIER:
if pressed:
self._keymap.add(key[1])
self.keyboard_hid.press(keys[2:])
else:
self.keyboard_hid.release(keys[2:])
self._keymap.remove(key[1])
elif action == Action.LAYER_TOGGLE:
self._keymap.toggle(key[1])
elif action == Action.LAYER_SET:
self._keymap.set(key[1])
else:
raise ValueError('invalid extended key binding: %s of type %s' % (key, action))
class _Layers:
def MO(self, l):
return (Action.LAYER_MOMENTARY, l)
def LM(self, l, *mod):
return (Action.LAYER_MOMENTARY, l) + mod
def TG(self, l):
return (Action.LAYER_TOGGLE, l)
def TO(self, l):
return (Action.LAYER_SET, l)
class _Modifiers:
def _mod(self, mod, kc):
if isinstance(kc, tuple):
if kc[0] == Action.KEYBOARD_CODE:
# https://github.com/micropython/micropython/issues/1329
# return (kc[0], mod, *kc[1:])
return (kc[0], mod) + kc[1:]
if isinstance(kc, int):
return (Action.KEYBOARD_CODE, mod, kc)
raise ValueError('base key code not recognized: %s' % kc)
def LCTL(self, kc):
return self._mod(Keycode.LEFT_CONTROL, kc)
CTL = LCTL
def LSFT(self, kc):
return self._mod(Keycode.LEFT_SHIFT, kc)
SFT = LSFT
def LALT(self, kc):
return self._mod(Keycode.LEFT_ALT, kc)
ALT = LALT
LOPT = LALT
OPT = LOPT
def LGUI(self, kc):
return self._mod(Keycode.LEFT_GUI, kc)
GUI = LGUI
LCMD = LGUI
CMD = LCMD
LWIN = LGUI
WIN = LWIN
def RCTL(self, kc):
return self._mod(Keycode.RIGHT_CONTROL, kc)
def RSFT(self, kc):
return self._mod(Keycode.RIGHT_SHIFT, kc)
def RALT(self, kc):
return self._mod(Keycode.RIGHT_ALT, kc)
ROPT = RALT
def RGUI(self, kc):
return self._mod(Keycode.RIGHT_GUI, kc)
RCMD = RGUI
RWIN = RGUI
class _KeysNamespace(_Modifiers, _Layers):
def __init__(self):
self.NO = None
self.XXXXXXX = None
self.TRANSPARENT = TRANSPARENT_ACTION
self._______ = TRANSPARENT_ACTION
def SEND_STRING(self, s):
return (Action.STRING, str(s))
def __getattr__(self, a):
# Lookup in HID maps, which are frozen in flash.
kc = getattr(Keycode, a, None)
if kc is not None:
return kc # Could be KEYBOARD_CODE, but save a little space for ordinary case.
cc = getattr(ConsumerControlCode, a, None)
if cc is not None:
return (CONSUMER_CODE, cc)
raise AttributeError("KC has no attribute '%s'" % a)
KC = _KeysNamespace()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment