by Jeff Epler
CircuitPython's standard USB keyboard descriptor only supports pressing up to 6 non-modifier keys at a time, called 6-Key Rollover or 6KRO. This example shows how you can use an alternate USB descriptor to enable unlimited rollover (also called N-Key Rollover or NKRO) using the Adafruit MacroPad.
Your project will use a specific set of CircuitPython libraries and code.py and boot.py files. 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 MacroPad board CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary. Once installed, the folder structure on CIRCUITPY should look like the image below.
Then, because this code requires a modified USB descriptor to be set in boot.py, click the reset button once to make those settings active. (To reverse them later, remove boot.py and reset the board again). You can check boot_out.txt which will contain the line enabled HID with custom keyboard device
when boot.py is properly installed.

# SPDX-FileCopyrightText: 2021 Jeff Epler for Adafruit Industries # # SPDX-License-Identifier: MIT import keypad import board import usb_hid from adafruit_hid.keyboard import Keyboard, find_device from adafruit_hid.keycode import Keycode key_pins = ( board.KEY1, board.KEY2, board.KEY3, board.KEY4, board.KEY5, board.KEY6, board.KEY7, board.KEY8, board.KEY9, board.KEY10, board.KEY11, board.KEY12, ) keys = keypad.Keys(key_pins, value_when_pressed=False, pull=True) class BitmapKeyboard(Keyboard): def __init__(self, devices): device = find_device(devices, usage_page=0x1, usage=0x6) try: device.send_report(b'\0' * 16) except ValueError: print("found keyboard, but it did not accept a 16-byte report. check that boot.py is installed properly") self._keyboard_device = device # report[0] modifiers # report[1:16] regular key presses bitmask self.report = bytearray(16) self.report_modifier = memoryview(self.report)[0:1] self.report_bitmap = memoryview(self.report)[1:] def _add_keycode_to_report(self, keycode): modifier = Keycode.modifier_bit(keycode) if modifier: # Set bit for this modifier. self.report_modifier[0] |= modifier else: self.report_bitmap[keycode >> 3] |= 1 << (keycode & 0x7) def _remove_keycode_from_report(self, keycode): modifier = Keycode.modifier_bit(keycode) if modifier: # Set bit for this modifier. self.report_modifier[0] &= ~modifier else: self.report_bitmap[keycode >> 3] &= ~(1 << (keycode & 0x7)) def release_all(self): for i in range(len(self.report)): self.report[i] = 0 self._keyboard_device.send_report(self.report) kbd = BitmapKeyboard(usb_hid.devices) keymap = [ Keycode.ONE, Keycode.TWO, Keycode.THREE, Keycode.Q, Keycode.W, Keycode.E, Keycode.A, Keycode.S, Keycode.D, Keycode.Z, Keycode.X, Keycode.C] while True: ev = keys.events.get() if ev is not None: key = keymap[ev.key_number] if ev.pressed: kbd.press(key) else: kbd.release(key)
# SPDX-FileCopyrightText: 2021 Jeff Epler for Adafruit Industries # # SPDX-License-Identifier: MIT import usb_hid REPORT_ID = 0x4 REPORT_BYTES = 16 bitmap_keyboard_descriptor = bytes(( 0x05, 0x01, # Usage Page (Generic Desktop), 0x09, 0x06, # Usage (Keyboard), 0xA1, 0x01, # Collection (Application), 0x85, REPORT_ID, # Report ID # bitmap of modifiers 0x75, 0x01, # Report Size (1), 0x95, 0x08, # Report Count (8), 0x05, 0x07, # Usage Page (Key Codes), 0x19, 0xE0, # Usage Minimum (224), 0x29, 0xE7, # Usage Maximum (231), 0x15, 0x00, # Logical Minimum (0), 0x25, 0x01, # Logical Maximum (1), 0x81, 0x02, # Input (Data, Variable, Absolute), ;Modifier byte # LED output report 0x95, 0x05, # Report Count (5), 0x75, 0x01, # Report Size (1), 0x05, 0x08, # Usage Page (LEDs), 0x19, 0x01, # Usage Minimum (1), 0x29, 0x05, # Usage Maximum (5), 0x91, 0x02, # Output (Data, Variable, Absolute), 0x95, 0x01, # Report Count (1), 0x75, 0x03, # Report Size (3), 0x91, 0x03, # Output (Constant), # bitmap of keys 0x95, (REPORT_BYTES-1)*8, # Report Count (), 0x75, 0x01, # Report Size (1), 0x15, 0x00, # Logical Minimum (0), 0x25, 0x01, # Logical Maximum(1), 0x05, 0x07, # Usage Page (Key Codes), 0x19, 0x00, # Usage Minimum (0), 0x29, (REPORT_BYTES-1)*8-1, # Usage Maximum (), 0x81, 0x02, # Input (Data, Variable, Absolute), 0xc0 # End Collection )) bitmap_keyboard = usb_hid.Device( report_descriptor=bitmap_keyboard_descriptor, usage_page=0x1, usage=0x6, report_ids=(REPORT_ID,), in_report_lengths=(REPORT_BYTES,), out_report_lengths=(1,), ) usb_hid.enable( ( bitmap_keyboard, usb_hid.Device.MOUSE, usb_hid.Device.CONSUMER_CONTROL, ) ) print("enabled HID with custom keyboard device")
You can use Microsoft's Keyboard ghosting interactive demonstration (it's a web page, so it should work on all kinds of computers) to see that you can press all the keys at the same time.
In boot.py, the special USB HID descriptor is established. code.py includes a version of the Keyboard
class that works with the NKRO descriptor, as well as lines to establish the Keys
object and to react to presses & releases by sending HID reports to the connected PC.
Page last edited January 22, 2025
Text editor powered by tinymce.