|
| 1 | +# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries |
| 2 | +# SPDX-License-Identifier: MIT |
| 3 | +import array |
| 4 | +import time |
| 5 | + |
| 6 | +import board |
| 7 | +import rp2pio |
| 8 | +import usb_hid |
| 9 | +from keypad import Keys |
| 10 | +from adafruit_hid.consumer_control import ConsumerControl |
| 11 | +from adafruit_hid.keyboard import Keyboard |
| 12 | +from adafruit_hid.keyboard import Keycode |
| 13 | +from adafruit_pioasm import Program |
| 14 | +from adafruit_ticks import ticks_add, ticks_less, ticks_ms |
| 15 | +from next_keycode import ( |
| 16 | + cc_value, |
| 17 | + is_cc, |
| 18 | + next_modifiers, |
| 19 | + next_scancodes, |
| 20 | + shifted_codes, |
| 21 | + shift_modifiers, |
| 22 | +) |
| 23 | + |
| 24 | +# Customize the power key's keycode. You can change it to `Keycode.POWER` if |
| 25 | +# you really want to accidentally power off your computer! |
| 26 | +POWER_KEY_SENDS = Keycode.F1 |
| 27 | + |
| 28 | +# according to https://journal.spencerwnelson.com/entries/nextkb.html the |
| 29 | +# keyboard's timing source is a 455MHz crystal, and the serial data rate is |
| 30 | +# 1/24 the crystal frequency. This differs by a few percent from the "50us" bit |
| 31 | +# time reported in other sources. |
| 32 | +NEXT_SERIAL_BUS_FREQUENCY = round(455_000 / 24) |
| 33 | + |
| 34 | +pio_program = Program( |
| 35 | + """ |
| 36 | +top: |
| 37 | + set pins, 1 |
| 38 | + pull block ; wait for send request |
| 39 | + out x, 1 ; trigger receive? |
| 40 | + out y, 7 ; get count of bits to transmit (minus 1) |
| 41 | +
|
| 42 | +bitloop: |
| 43 | + out pins, 1 [7] ; send next bit |
| 44 | + jmp y--, bitloop [7] ; loop if bits left to send |
| 45 | +
|
| 46 | + set pins, 1 ; idle the bus after last bit |
| 47 | + jmp !x, top ; to top if no scancode expected |
| 48 | +
|
| 49 | + set pins, 1 ; mark bus as idle so keyboard will send |
| 50 | + set y, 19 ; 20 bits to receive |
| 51 | +
|
| 52 | + wait 0, pin 0 [7] ; wait for falling edge plus half bit time |
| 53 | +recvloop: |
| 54 | + in pins, 1 [7] ; sample in the middle of the bit |
| 55 | + jmp y--, recvloop [7] ; loop until all bits read |
| 56 | +
|
| 57 | + push ; send report to CircuitPython |
| 58 | +""" |
| 59 | +) |
| 60 | + |
| 61 | + |
| 62 | +def pack_message(bitcount, data, trigger_receive=False): |
| 63 | + if bitcount > 24: |
| 64 | + raise ValueError("too many bits in message") |
| 65 | + trigger_receive = bool(trigger_receive) |
| 66 | + message = ( |
| 67 | + (trigger_receive << 31) | ((bitcount - 1) << 24) | (data << (24 - bitcount)) |
| 68 | + ) |
| 69 | + return array.array("I", [message]) |
| 70 | + |
| 71 | + |
| 72 | +def pack_message_str(bitstring, trigger_receive=False): |
| 73 | + bitcount = len(bitstring) |
| 74 | + data = int(bitstring, 2) |
| 75 | + return pack_message(bitcount, data, trigger_receive=trigger_receive) |
| 76 | + |
| 77 | + |
| 78 | +def set_leds(i): |
| 79 | + return pack_message_str(f"0000000001110{i:02b}0000000") |
| 80 | + |
| 81 | + |
| 82 | +QUERY = pack_message_str("000001000", 1) |
| 83 | +RESET = pack_message_str("0111101111110000000000") |
| 84 | + |
| 85 | +BIT_BREAK = 1 << 11 |
| 86 | +BIT_MOD = 1 |
| 87 | + |
| 88 | + |
| 89 | +def is_make(report): |
| 90 | + return not bool(report & BIT_BREAK) |
| 91 | + |
| 92 | + |
| 93 | +def is_mod_report(report): |
| 94 | + return not bool(report & BIT_MOD) |
| 95 | + |
| 96 | + |
| 97 | +# keycode bits are backwards compared to other information sources |
| 98 | +# (bit 0 is first) |
| 99 | +def keycode(report): |
| 100 | + b = f"{report >> 12:07b}" |
| 101 | + b = "".join(reversed(b)) |
| 102 | + return int(b, 2) |
| 103 | + |
| 104 | + |
| 105 | +def modifiers(report): |
| 106 | + return (report >> 1) & 0x7F |
| 107 | + |
| 108 | + |
| 109 | +sm = rp2pio.StateMachine( |
| 110 | + pio_program.assembled, |
| 111 | + first_in_pin=board.MISO, |
| 112 | + pull_in_pin_up=1, |
| 113 | + first_set_pin=board.MOSI, |
| 114 | + set_pin_count=1, |
| 115 | + first_out_pin=board.MOSI, |
| 116 | + out_pin_count=1, |
| 117 | + frequency=16 * NEXT_SERIAL_BUS_FREQUENCY, |
| 118 | + in_shift_right=False, |
| 119 | + wait_for_txstall=False, |
| 120 | + out_shift_right=False, |
| 121 | + **pio_program.pio_kwargs, |
| 122 | +) |
| 123 | + |
| 124 | + |
| 125 | +class KeyboardHandler: |
| 126 | + def __init__(self): |
| 127 | + self.old_modifiers = 0 |
| 128 | + self.cc = ConsumerControl(usb_hid.devices) |
| 129 | + self.kbd = Keyboard(usb_hid.devices) |
| 130 | + |
| 131 | + def set_key_state(self, key, state): |
| 132 | + if state: |
| 133 | + if isinstance(key, tuple): |
| 134 | + old_report_modifier = self.kbd.report_modifier[0] |
| 135 | + self.kbd.report_modifier[0] = 0 |
| 136 | + self.kbd.press(*key) |
| 137 | + self.kbd.release_all() |
| 138 | + self.kbd.report_modifier[0] = old_report_modifier |
| 139 | + else: |
| 140 | + self.kbd.press(key) |
| 141 | + else: |
| 142 | + if isinstance(key, tuple): |
| 143 | + pass |
| 144 | + else: |
| 145 | + self.kbd.release(key) |
| 146 | + |
| 147 | + def handle_report(self, report_value): |
| 148 | + if report_value == 1536: # the "nothing happened" report |
| 149 | + return |
| 150 | + |
| 151 | + # Handle modifier changes |
| 152 | + mods = modifiers(report_value) |
| 153 | + changes = self.old_modifiers ^ mods |
| 154 | + self.old_modifiers = mods |
| 155 | + for i in range(7): |
| 156 | + bit = 1 << i |
| 157 | + if changes & bit: # Modifier key pressed or released |
| 158 | + self.set_key_state(next_modifiers[i], mods & bit) |
| 159 | + |
| 160 | + # Handle key press/release |
| 161 | + code = next_scancodes.get(keycode(report_value)) |
| 162 | + if mods & shift_modifiers: |
| 163 | + code = shifted_codes.get(keycode(report_value), code) |
| 164 | + make = is_make(report_value) |
| 165 | + if code: |
| 166 | + if is_cc(code): |
| 167 | + if make: |
| 168 | + self.cc.send(cc_value(code)) |
| 169 | + else: |
| 170 | + self.set_key_state(code, make) |
| 171 | + |
| 172 | +keys = Keys([board.SCK], value_when_pressed=False) |
| 173 | + |
| 174 | +handler = KeyboardHandler() |
| 175 | + |
| 176 | +recv_buf = array.array("I", [0]) |
| 177 | + |
| 178 | +time.sleep(0.1) |
| 179 | +sm.write(RESET) |
| 180 | +time.sleep(0.1) |
| 181 | + |
| 182 | +for _ in range(4): |
| 183 | + sm.write(set_leds(3)) |
| 184 | + time.sleep(0.1) |
| 185 | + sm.write(set_leds(0)) |
| 186 | + time.sleep(0.1) |
| 187 | + |
| 188 | +print("Keyboard ready!") |
| 189 | + |
| 190 | +try: |
| 191 | + while True: |
| 192 | + if (event := keys.events.get()): |
| 193 | + handler.set_key_state(POWER_KEY_SENDS, event.pressed) |
| 194 | + |
| 195 | + sm.write(QUERY) |
| 196 | + deadline = ticks_add(ticks_ms(), 100) |
| 197 | + while ticks_less(ticks_ms(), deadline): |
| 198 | + if sm.in_waiting: |
| 199 | + sm.readinto(recv_buf) |
| 200 | + value = recv_buf[0] |
| 201 | + handler.handle_report(value) |
| 202 | + break |
| 203 | + else: |
| 204 | + print("keyboard did not respond - resetting") |
| 205 | + sm.restart() |
| 206 | + sm.write(RESET) |
| 207 | + time.sleep(0.1) |
| 208 | +finally: # Release all keys before e.g., code is reloaded |
| 209 | + handler.kbd.release_all() |
0 commit comments