Code

Published on 2020-08-15 in Dorsch 40k Keyboard.

I know it’s not pretty, but it works. Mostly. For now.

———- more ———-

import board
import digitalio
import usb_hid
import time


COLS = (board.A6, board.A1, board.A4, board.A3, board.D6,
        board.SCL, board.SDA, board.D12, board.D10, board.D13)
ROWS = (board.MOSI, board.AREF, board.D11, board.D5)
LEDS = (board.A2, board.A5, board.TX, board.RX, board.D9)


class Keyboard:
    def __init__(self, matrix, cols=COLS, rows=ROWS):
        self.matrix = matrix
        self.cols = [digitalio.DigitalInOut(pin) for pin in cols]
        self.rows = [digitalio.DigitalInOut(pin) for pin in rows]
        for col in self.cols:
            col.switch_to_output(value=0)
        for row in self.rows:
            row.switch_to_input(pull=digitalio.Pull.DOWN)
        for self.device in usb_hid.devices:
            if self.device.usage == 0x06 and self.device.usage_page == 0x01:
                break
        else:
            raise RuntimeError("no HID keyboard device")
        self.debounce = bytearray(len(cols))
        self.last_state = bytearray(len(cols))
        self.current_layer = 0
        self.pressed_keys = set()
        self.last_held = 0
        self.release_next = 0

    def scan(self):
        if self.release_next:
            try:
                self.pressed_keys.remove(self.release_next)
            except KeyError:
                pass
            self.release_next = 0
        for x, col in enumerate(self.cols):
            col.value = 1
            debounce_bits = 0
            for y, row in enumerate(self.rows):
                state = row.value
                debounce_bits |= state << y
                if state != bool(self.debounce[x] & (1 << y)):
                    continue
                last_state = bool(self.last_state[x] & (1 << y))
                if state:
                    self.last_state[x] |= 1 << y
                else:
                    self.last_state[x] &= ~(1 << y)
                if state == last_state:
                    continue
                if state:
                    self.press(x, y)
                else:
                    self.release(x, y)
            col.value = 0
            self.debounce[x] = debounce_bits

    def press(self, x, y):
        code = self.matrix[self.current_layer][y][x]
        if code == 0x0800:
            self.current_layer = 1
            return
        if self.last_held:
            if code & 0xff00 == 0x0800:
                self.current_layer = 1
            else:
                self.pressed_keys.add(self.last_held & 0xff00)
            self.last_held = 0
        if code & 0xff00 and (code & 0xff) != 0:
            self.last_held = code
            return
        self.pressed_keys.add(code)

    def release(self, x, y):
        code = self.matrix[self.current_layer][y][x]
        if self.last_held == code:
            self.pressed_keys.add(code & 0xff)
            self.release_next = code & 0xff
            try:
                self.pressed_keys.remove(code & 0xff00)
            except KeyError:
                pass
            if code & 0xff00 == 0x0800:
                self.current_layer = 0
            self.last_held = 0
            return
        if code == 0x0800:
            self.current_layer = 0
            return
        for layer in 0, 1:
            for mask in 0xffff, 0xff00, 0x00ff:
                try:
                    self.pressed_keys.remove(self.matrix[layer][y][x] & mask)
                except KeyError:
                    pass

    def send_report(self, pressed_keys):
        report = bytearray(8)
        report_mod_keys = memoryview(report)[0:1]
        report_no_mod_keys = memoryview(report)[2:]
        keys = 0
        for code in pressed_keys:
            if code == 0:
                continue
            elif code == 0x0800:
                continue
            elif code & 0xff00 and code & 0xff == 0:
                modifier = (code >> 8) - 1
                report_mod_keys[0] |= 1 << modifier
            elif keys < 6:
                report_no_mod_keys[keys] = code
                keys += 1
        self.device.send_report(report)

    def run(self):
        last_pressed_keys = set()
        while True:
            self.scan()
            if self.pressed_keys != last_pressed_keys:
                self.send_report(self.pressed_keys)
                last_pressed_keys = set(self.pressed_keys)
                print(last_pressed_keys, self.last_held, self.release_next)
            time.sleep(0.01)

And then there is the key definition:

import keyboard
from micropython import const

_A = const(4)
_B = const(5)
_C = const(6)
_D = const(7)
_E = const(8)
_F = const(9)
_G = const(10)
_H = const(11)
_I = const(12)
_J = const(13)
_K = const(14)
_L = const(15)
_M = const(16)
_N = const(17)
_O = const(18)
_P = const(19)
_Q = const(20)
_R = const(21)
_S = const(22)
_T = const(23)
_U = const(24)
_V = const(25)
_W = const(26)
_X = const(27)
_Y = const(28)
_Z = const(29)

_1 = const(30)
_2 = const(31)
_3 = const(32)
_4 = const(33)
_5 = const(34)
_6 = const(35)
_7 = const(36)
_8 = const(37)
_9 = const(38)
_0 = const(39)

_ENT = const(40) # Enter
_ESC = const(41) # Esc
_BS = const(42)  # Backspace
_TAB = const(43) # Tab
_SPC = const(44) # Space
_MN = const(45) # Minus -/_
_EQ = const(46) # Equal =/+
_LB = const(47) # Left bracket [/{
_RB = const(48) # Right bracket ]/}
_BSL = const(49) # Backslash \/|
_SC = const(51) # Semicolon ;/:
_QT = const(52) # Quote '/"
_GR = const(53) # Grave `/~
_CM = const(54) # Comma ,/<
_DT = const(55) # Dot ./>
_SL = const(56) # Slash //?
_CAPS = const(57) # Caps lock

_F1 = const(58)
_F2 = const(59)
_F3 = const(60)
_F4 = const(61)
_F5 = const(62)
_F6 = const(63)
_F7 = const(64)
_F8 = const(65)
_F9 = const(66)
_F10 = const(67)
_F11 = const(68)
_F12 = const(69)

_PS = const(70) # Print screen
_SCL = const(71) # Scroll lock
_PA = const(72) # Pause

_INS = const(73) # Insert
_HOME = const(74) # Home
_PGUP = const(75) # Page up
_DEL = const(76) # Delete
_END = const(77) # End
_PGDN = const(78) # Page down
_AR = const(79) # Arrow right
_AL = const(80) # Arrow left
_AD = const(81) # Arrow down
_AU = const(82) # Arrow up

_LCT = const(0x0100) # Left control
_LSH = const(0x0200) # Left shift
_LAL = const(0x0300) # Left alternate
_LSP = const(0x0400) # Left super
_RCT = const(0x0500) # Right control
_RSH = const(0x0600) # Right shift
_RAL = const(0x0700) # Right alternate
_FN = const(0x0800) # Function

MATRIX = (
    (_Q,    _W,   _E,   _R,   _T,   _Y,   _U,   _I,   _O,    _P),
    (_A,    _S,   _D,   _F,   _G,   _H,   _J,   _K,   _L,    _ENT),
    (_Z,    _X,   _C,   _V,   _B,   _N,   _M,   _CM,  _AU,   _SL),
    (_LSH | _CAPS,
     _LCT | _ESC,
     _LAL | _INS,
     _FN | _TAB,
     _BS,
     _SPC,
     _RAL | _DEL,
     _AL,
     _AD,
     _AR),
), (
    (_1,    _2,   _3,   _4,   _5,   _6,   _7,   _8,   _9,    _0),
    (_B,   _F2,  _F3,  _F4,  _F5,  _MN,  _EQ,  _GR,  _QT,   _SC),
    (_F6,  _F7,  _F8,  _F9,  _F10, _LB,  _RB,  _DT,  _PGUP, _BSL),
    (_CAPS,
     _ESC,
     _INS,
     _TAB,
     _F11,
     _F12,
     _DEL,
     _HOME,
     _PGDN,
     _END),
)


keyboard.Keyboard(MATRIX).run()