Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files.
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Drag the contents of the uncompressed bundle directory onto your board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries # SPDX-License-Identifier: MIT import time import array import board import digitalio import rp2pio import usb_hid import adafruit_pioasm from adafruit_hid.keyboard import Keyboard from adafruit_hid.keycode import Keycode as K KBD_NRESET = board.MISO KBD_DATA = board.RX KBD_CLOCK = board.SCK # Note that KBD_CLOCK must be 1 GPIO# above KBD_DATA KBD_NBUSY = board.MOSI tandy1000_keycodes = [ None, K.ESCAPE, K.ONE, K.TWO, K.THREE, K.FOUR, K.FIVE, K.SIX, K.SEVEN, K.EIGHT, K.NINE, K.ZERO, K.MINUS, K.EQUALS, K.BACKSPACE, K.TAB, K.Q, K.W, K.E, K.R, K.T, K.Y, K.U, K.I, K.O, K.P, K.LEFT_BRACKET, K.RIGHT_BRACKET, K.ENTER, K.LEFT_CONTROL, K.A, K.S, K.D, K.F, K.G, K.H, K.J, K.K, K.L, K.SEMICOLON, K.QUOTE, K.UP_ARROW, K.LEFT_SHIFT, K.LEFT_ARROW, K.Z, K.X, K.C, K.V, K.B, K.N, K.M, K.COMMA, K.PERIOD, K.FORWARD_SLASH, K.RIGHT_SHIFT, K.PRINT_SCREEN, K.LEFT_ALT, K.SPACE, K.CAPS_LOCK, K.F1, K.F2, K.F3, K.F4, K.F5, K.F6, K.F7, K.F8, K.F9, K.F10, K.KEYPAD_NUMLOCK, K.PAUSE, K.KEYPAD_SEVEN, K.KEYPAD_EIGHT, K.KEYPAD_NINE, K.DOWN_ARROW, K.KEYPAD_FOUR, K.KEYPAD_FIVE, K.KEYPAD_SIX, K.RIGHT_ARROW, K.KEYPAD_ONE, K.KEYPAD_TWO, K.KEYPAD_THREE, K.KEYPAD_ZERO, K.KEYPAD_MINUS, (K.LEFT_CONTROL, K.PAUSE), K.KEYPAD_PLUS, K.KEYPAD_PERIOD, K.KEYPAD_ENTER, K.HOME, K.F11, K.F12 ] LOCK_KEYS = (K.CAPS_LOCK, K.KEYPAD_NUMLOCK) LOCK_STATE = { K.CAPS_LOCK: False, K.KEYPAD_NUMLOCK: False, } KEYPAD_NUMLOCK_LOOKUP = [ { K.KEYPAD_PLUS: K.INSERT, K.KEYPAD_MINUS: K.DELETE, K.KEYPAD_SEVEN: K.BACKSLASH, K.KEYPAD_EIGHT: (K.LEFT_SHIFT, K.GRAVE_ACCENT), K.KEYPAD_NINE: K.PAGE_UP, K.KEYPAD_FOUR: (K.LEFT_SHIFT, K.BACKSLASH), #K.KEYPAD_FIVE: #K.KEYPAD_SIX: K.KEYPAD_ONE: K.END, K.KEYPAD_TWO: K.GRAVE_ACCENT, K.KEYPAD_THREE: K.PAGE_DOWN, K.KEYPAD_ZERO: K.ZERO, K.KEYPAD_PERIOD: K.PERIOD, }, { K.KEYPAD_PLUS: (K.LEFT_SHIFT, K.EQUALS), K.KEYPAD_MINUS: K.MINUS, K.KEYPAD_SEVEN: K.SEVEN, K.KEYPAD_EIGHT: K.EIGHT, K.KEYPAD_NINE: K.NINE, K.KEYPAD_FOUR: K.FOUR, K.KEYPAD_FIVE: K.FIVE, K.KEYPAD_SIX: K.SIX, K.KEYPAD_ONE: K.ONE, K.KEYPAD_TWO: K.TWO, K.KEYPAD_THREE: K.THREE, K.KEYPAD_ZERO: K.ZERO, K.KEYPAD_PERIOD: K.PERIOD, } ] # Assert busy busy_out = digitalio.DigitalInOut(KBD_NBUSY) busy_out.switch_to_output(False, digitalio.DriveMode.OPEN_DRAIN) # Reset the keyboard reset_out = digitalio.DigitalInOut(KBD_NRESET) reset_out.switch_to_output(False, digitalio.DriveMode.OPEN_DRAIN) time.sleep(.1) reset_out.value = True program = adafruit_pioasm.Program(""" wait 1 pin 1 in pins, 1 wait 0 pin 1 """) sm = rp2pio.StateMachine(program.assembled, first_in_pin = KBD_DATA, in_pin_count = 2, pull_in_pin_up = 0b11, auto_push=True, push_threshold=8, in_shift_right=True, frequency=8_000_000, **program.pio_kwargs) buf = array.array('B', [0]) MASK_LEFT_SHIFT = K.modifier_bit(K.LEFT_SHIFT) MASK_RIGHT_SHIFT = K.modifier_bit(K.RIGHT_SHIFT) MASK_ANY_SHIFT = (MASK_LEFT_SHIFT | MASK_RIGHT_SHIFT) # Now ready to get keystrokes kbd = Keyboard(usb_hid.devices) busy_out.value = True while True: sm.readinto(buf, swap=False) val = buf[0] pressed = (val & 0x80) == 0 key_number = val & 0x7f if key_number > len(tandy1000_keycodes): # invalid keycode -- reset the keyboard reset_out.switch_to_output(False, digitalio.DriveMode.OPEN_DRAIN) time.sleep(.1) reset_out.value = True continue keycode = tandy1000_keycodes[key_number] if keycode is None: continue keycode = KEYPAD_NUMLOCK_LOOKUP[LOCK_STATE[K.KEYPAD_NUMLOCK]].get(keycode, keycode) if pressed: if keycode in LOCK_KEYS: LOCK_STATE[keycode] = True elif LOCK_STATE[K.CAPS_LOCK] and K.A <= keycode <= K.Z: old_report_modifier = kbd.report_modifier[0] kbd.report_modifier[0] = (old_report_modifier & ~MASK_RIGHT_SHIFT) ^ MASK_LEFT_SHIFT kbd.press(keycode) kbd.release_all() kbd.report_modifier[0] = old_report_modifier continue elif isinstance(keycode, tuple): old_report_modifier = kbd.report_modifier[0] kbd.report_modifier[0] = 0 kbd.press(*keycode) kbd.release_all() kbd.report_modifier[0] = old_report_modifier else: kbd.press(keycode) else: if keycode in LOCK_KEYS: LOCK_STATE[keycode] = False elif isinstance(keycode, tuple): pass else: kbd.release(keycode)
How it works
Preliminaries
After the required import
lines, there are lines to set the I/O pins used, and to map from Tandy keycodes to USB keycodes:
mport time import array import board import digitalio import rp2pio import usb_hid import adafruit_pioasm from adafruit_hid.keyboard import Keyboard from adafruit_hid.keycode import Keycode as K KBD_NRESET = board.MISO KBD_DATA = board.RX KBD_CLOCK = board.SCK # Note that KBD_CLOCK must be 1 GPIO# above KBD_DATA KBD_NBUSY = board.MOSI tandy1000_keycodes = [...]
Locking keys
The keyboard handles the caps lock and num lock key states internally. For instance, when caps lock is first pressed, the keyboard turns the light on and sends a "caps lock is pressed" message to CircuitPython. When caps lock is pressed again, the keyboard turns the light off and sends a "caps lock is released" message.
There's no way for CircuitPython to set the state of the caps lock or num lock LEDs. So, instead, the CircuitPython code tracks whether caps lock or num lock are active, and then modifies the events it sends back to the computer accordingly.
These lines configure which keys are locking keys, and the behavior of keypad keys depending on the numlock state:
LOCK_KEYS = (K.CAPS_LOCK, K.KEYPAD_NUMLOCK) LOCK_STATE = { K.CAPS_LOCK: False, K.KEYPAD_NUMLOCK: False, } KEYPAD_NUMLOCK_LOOKUP = [...]
Initialize the keyboard
To initialize the keyboard, first assert the "busy" and "reset" signals. Then, release the "reset" signal to allow the keyboard to initialize itself. The "busy" signal will remain asserted until CircuitPython is ready to receive key events.
These signals are "active low" and "open drain", which means that to assert the signal, the correct value to send to the pin is False.
# Assert busy busy_out = digitalio.DigitalInOut(KBD_NBUSY) busy_out.switch_to_output(False, digitalio.DriveMode.OPEN_DRAIN) # Reset the keyboard reset_out = digitalio.DigitalInOut(KBD_NRESET) reset_out.switch_to_output(False, digitalio.DriveMode.OPEN_DRAIN) time.sleep(.1) reset_out.value = True
Configure the PIO peripheral
These lines configure a program within the PIO peripheral. The program waits for the clock input to become true, then copies the data value, and waits for the clock input to become false.
The peripheral is configured to wait for 8 bits of data before making all 8 bits available to CircuitPython in a buffer.
program = adafruit_pioasm.Program(""" wait 1 pin 1 in pins, 1 wait 0 pin 1 """) sm = rp2pio.StateMachine(program.assembled, first_in_pin = KBD_DATA, in_pin_count = 2, pull_in_pin_up = 0b11, auto_push=True, push_threshold=8, in_shift_right=True, frequency=8_000_000, **program.pio_kwargs)
Main loop
Finally, the program is ready to receive key events from the keyboard and translate them to USB. First, read one value from the PIO peripheral and turn it into a boolean 'pressed' value and a Tandy key number value.
If the key number is not valid, something has gone wrong. Reset the keyboard and try again. Otherwise, event processing continues below.
# Now ready to get keystrokes kbd = Keyboard(usb_hid.devices) busy_out.value = True while True: sm.readinto(buf, swap=False) val = buf[0] pressed = (val & 0x80) == 0 key_number = val & 0x7f if key_number > len(tandy1000_keycodes): # invalid keycode -- reset the keyboard reset_out.switch_to_output(False, digitalio.DriveMode.OPEN_DRAIN) time.sleep(.1) reset_out.value = True continue keycode = tandy1000_keycodes[key_number] if keycode is None: continue
Next, translate the keycode based on the caps lock state. Then, if the event is a press, process it one of several ways:
- If it's a lock key, just update the state of the lock key.
- If caps lock is on, and the keycode is alphabetic, then send it shifted.
- If the key code is a tuple, send it as multiple events. This is used, for instance, to send the backslash key when num lock is off and numpad "7" is pressed.
- Otherwise send a simple USB key press event.
Otherwise, if the event is a release, it's processed in one of the following ways:
- If it's a lock key, just update the state of the lock key.
- If it's a tuple, there's nothing to do.
- Otherwise send a simple USB key release event.
... keycode = KEYPAD_NUMLOCK_LOOKUP[LOCK_STATE[K.KEYPAD_NUMLOCK]].get(keycode, keycode) if pressed: if keycode in LOCK_KEYS: LOCK_STATE[keycode] = True elif LOCK_STATE[K.CAPS_LOCK] and K.A <= keycode <= K.Z: old_report_modifier = kbd.report_modifier[0] kbd.report_modifier[0] = (old_report_modifier & ~MASK_RIGHT_SHIFT) ^ MASK_LEFT_SHIFT kbd.press(keycode) kbd.release_all() kbd.report_modifier[0] = old_report_modifier continue elif isinstance(keycode, tuple): old_report_modifier = kbd.report_modifier[0] kbd.report_modifier[0] = 0 kbd.press(*keycode) kbd.release_all() kbd.report_modifier[0] = old_report_modifier else: kbd.press(keycode) else: if keycode in LOCK_KEYS: LOCK_STATE[keycode] = False elif isinstance(keycode, tuple): pass else: kbd.release(keycode)
Page last edited January 19, 2025
Text editor powered by tinymce.