To read data from a keyboard in CircuitPython, you must use a device which supports USB Host, such as the Metro RP2350 or Feather RP2040 USB Host.
Feather RP2040 USB Host Wiring
Connecting to the Feather RP2040 USB Host requires plugging in a USB C cable to the Feather's Host port and connecting the other end to a USB Hub breakout such as the CH334F.
Metro RP2350 USB Host Wiring
Connecting to the Metro RP2350 USB Host port requires soldering pins to the broken out USB Host connections as shown in this guide page. Make the following connections between the Metro USB Host pins and CH334F host connection opposite the USB C connector.
- GND to GND with Black or Blue
- D+ to D+ with Green
- D- to D- with White
- 5V to 5V with Red
Note that the data pins are swapped on the Metro compared to the CH334F breakout. On the Metro D- is next to 5V, whereas on the CH334F D- is next to GND.
Be sure to connect D- on the breakout to D- on the Metro, and D+ on the breakout to D+ on the Metro.
USB Host is under active development. As of CircuitPython version 10.0.0-alpha.2, the wired USB keyboard only works on CircuitPython when connected through a USB hub such as the CH334F.
Demo Code: sys.stdin
There are two possible ways to read data from a keyboard in CircuitPython. The less complex way is utilizing sys.stdin
. Python has the concept of standard input and output streams, similar to those in Linux/Unix and other operating systems. CircuitPython has this capability and through a lot of behind the scenes code, presents a USB keyboard as a sys.stdin
input device. Due to the way this works it is only possible to know when a key is pressed, not how long it remains pressed, nor when it is released.
The sys.stdin
method defaults to using the standard US layout for the keyboard. You can set a custom layout using the set_user_keymap()
function documented here.
The code to get USB Host Keyboard characters and echo them to serial out is as follows:
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries # # SPDX-License-Identifier: MIT import sys import supervisor # main loop while True: # check how many bytes are available available = supervisor.runtime.serial_bytes_available # if there are some bytes available if available: # read data from the keyboard input c = sys.stdin.read(available) # print the data that was read print(c, end="")
Demo Code: USB Host
The other way to read from the keyboard is more involved, the CircuitPython API for it it mimics PyUSB in CPython. The code must:
- Establish the USB connection
- Read USB Reports, sections of bytes sent when an action occurs on the peripheral like a key is pressed.
- Parse the reports and provide meaningful input to the program.
When you use this method it will disable the sys.stdin
method shown above. You can only use a single method at a time.
Using the USB Host method makes it possible to determine which keys are currently being pressed, which makes it possible for the code to know when keys are pressed, how long they're held down, and when they get released.
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries # # SPDX-License-Identifier: MIT import array import usb import adafruit_usb_host_descriptors #interface index, and endpoint addresses for USB Device instance kbd_interface_index = None kbd_endpoint_address = None keyboard = None # scan for connected USB devices for device in usb.core.find(find_all=True): # check for boot keyboard endpoints on this device kbd_interface_index, kbd_endpoint_address = ( adafruit_usb_host_descriptors.find_boot_keyboard_endpoint(device) ) # if a boot keyboard interface index and endpoint address were found if kbd_interface_index is not None and kbd_interface_index is not None: keyboard = device # detach device from kernel if needed if keyboard.is_kernel_driver_active(0): keyboard.detach_kernel_driver(0) # set the configuration so it can be used keyboard.set_configuration() if keyboard is None: raise RuntimeError("No boot keyboard endpoint found") buf = array.array("b", [0] * 8) def print_keyboard_report(report_data): # Dictionary for modifier keys (first byte) modifier_dict = { 0x01: "LEFT_CTRL", 0x02: "LEFT_SHIFT", 0x04: "LEFT_ALT", 0x08: "LEFT_GUI", 0x10: "RIGHT_CTRL", 0x20: "RIGHT_SHIFT", 0x40: "RIGHT_ALT", 0x80: "RIGHT_GUI", } # Dictionary for key codes (main keys) key_dict = { 0x04: "A", 0x05: "B", 0x06: "C", 0x07: "D", 0x08: "E", 0x09: "F", 0x0A: "G", 0x0B: "H", 0x0C: "I", 0x0D: "J", 0x0E: "K", 0x0F: "L", 0x10: "M", 0x11: "N", 0x12: "O", 0x13: "P", 0x14: "Q", 0x15: "R", 0x16: "S", 0x17: "T", 0x18: "U", 0x19: "V", 0x1A: "W", 0x1B: "X", 0x1C: "Y", 0x1D: "Z", 0x1E: "1", 0x1F: "2", 0x20: "3", 0x21: "4", 0x22: "5", 0x23: "6", 0x24: "7", 0x25: "8", 0x26: "9", 0x27: "0", 0x28: "ENTER", 0x29: "ESC", 0x2A: "BACKSPACE", 0x2B: "TAB", 0x2C: "SPACE", 0x2D: "MINUS", 0x2E: "EQUAL", 0x2F: "LBRACKET", 0x30: "RBRACKET", 0x31: "BACKSLASH", 0x33: "SEMICOLON", 0x34: "QUOTE", 0x35: "GRAVE", 0x36: "COMMA", 0x37: "PERIOD", 0x38: "SLASH", 0x39: "CAPS_LOCK", 0x4F: "RIGHT_ARROW", 0x50: "LEFT_ARROW", 0x51: "DOWN_ARROW", 0x52: "UP_ARROW", } # Add F1-F12 keys to the dictionary for i in range(12): key_dict[0x3A + i] = f"F{i + 1}" # First byte contains modifier keys modifiers = report_data[0] # Print modifier keys if pressed if modifiers > 0: print("Modifiers:", end=" ") # Check each bit for modifiers and print if pressed for bit, name in modifier_dict.items(): if modifiers & bit: print(name, end=" ") print() # Bytes 2-7 contain up to 6 key codes (byte 1 is reserved) keys_pressed = False for i in range(2, 8): key = report_data[i] # Skip if no key or error rollover if key in {0, 1}: continue if not keys_pressed: print("Keys:", end=" ") keys_pressed = True # Print key name based on dictionary lookup if key in key_dict: print(key_dict[key], end=" ") else: # For keys not in the dictionary, print the HID code print(f"0x{key:02X}", end=" ") if keys_pressed: print() elif modifiers == 0: print("No keys pressed") while True: # try to read data from the keyboard try: count = keyboard.read(kbd_endpoint_address, buf, timeout=10) # if there is no data it will raise USBTimeoutError except usb.core.USBTimeoutError: # Nothing to do if there is no data for this keyboard continue print_keyboard_report(buf)
The character mapping in the code above is for a US 104 key QWERTY keyboard. Other keyboard layouts have different mappings. You can often get the USB value map for other keyboards online or just hook up your keyboard and enter keys to determine which key maps to which character.
A guide to other keymaps may be found on the internet. One resource is https://github.com/hid-io/layouts
Page last edited April 25, 2025
Text editor powered by tinymce.