# Custom HID Devices in CircuitPython

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/104/937/medium800/gaming_IMG_2214.jpg?1633036205)

## What is HID?

**HID** stands for "Human Interface Device". Keyboards, mice, digitizer tablets, joysticks, and game controllers are all HID devices. CircuitPython can emulate three standard HID devices by default: mouse, keyboard and consumer control. These are described in more detail in [CircuitPython Essentials Guide](https://learn.adafruit.com/circuitpython-essentials/circuitpython-hid-keyboard-and-mouse) and the [Customizing USB Devices Guide](https://learn.adafruit.com/customizing-usb-devices-in-circuitpython/hid-devices#standard-hid-devices-3096602-2).

All operating systems (e.g., Windows, macOS, or Linux) have built-in support for certain standard devices like keyboards and mice. But they vary about whether they support other less common devices. Often you need to install a driver to support a particular HID device, such as a specific game controller or a tablet.

## Custom HID Devices

There are so many kinds of devices you might want to implement in CircuitPython, it cannot provide built-in support for them all. So CircuitPython lets you define and implement your own custom HID devices . With the right device definition and built-in OS support or a compatible driver, you can implement an existing HID device, or define your own custom variation.

# Custom HID Devices in CircuitPython

## Report Descriptors

## Report Descriptors

To define an HID device, you need to supply an&nbsp;_HID report descriptor._ When you plug in an HID device, it sends its report descriptor(s) to the host computer. The report descriptor is binary data that specifies the device type and the details of the&nbsp;_reports_&nbsp;that the device sends and receives.

A report is binary data. A report sent from the device to the host computer is called an **IN** report. A report sent from the host to the device is an **OUT** report. **IN** and **OUT** are named from the perspective of the host.

For instance, a mouse report descriptor will declare that it is a device of type **Mouse** , with a certain number of **Buttons** and possibly a scroll **Wheel**. Every time you move the mouse, push a button, or move its scroll wheel, the mouse will send an IN report with data describing which buttons that are currently pushed, how far the mouse has moved in X and Y directions, and how much the scroll wheel has been turned.

Similarly, a keyboard will send IN reports saying which regular keys are pressed and which modifier keys (Shift, Ctrl, etc.) are pressed at the same time. In addition, most keyboards can _receive_ OUT reports back from the host computer, which tell the keyboard to turn on and off its LEDs, such as the shift-lock indicator.

## A Sample Report Descriptor

Below is a CircuitPython **boot.py** file that includes an example of a gamepad report descriptor.&nbsp;

The descriptor is a `bytes` string named `GAMEPAD_REPORT_DESCRIPTOR`. Note how the descriptor specifies a **Usage Page** , which is the general class of device, in this case, **Generic Desktop Controls,** and then a particular U **sage** , which is **Game Pad**. Then the descriptor specifies a **Report ID** , which here is 4. The report ID can be any value from 1 to 255, but must be unique for this report among all the reports in the devices presented by CircuitPython.

The descriptor then declares 16 on/off (0 or 1) **Buttons** , which fit in one bit each ( **Report Count** of 16 and **Report Size** of 1), and four joystick axes, which are Usages **X** , **Y** , **Z** , and **Rz**. Each joystick value varies from -127 to 127, and fits in 8 bits.

The rest of the code creates a `Device` based on the descriptor, and includes it in a list of devices that also includes the default keyboard, mouse, and consumer control devices that CircuitPython usually presents. The Device constructor specifies the Report ID's used, and how many bytes are in the IN and OUT reports for each Report ID. In this case the IN report is 6 bytes long, and there is no OUT report. See the [Customizing USB Devices Guide](https://learn.adafruit.com/customizing-usb-devices-in-circuitpython/hid-devices#standard-hid-devices-3096602-2)&nbsp;for more details about why this code needs to be in **boot.py** , and what `usb_hid.enable()` is doing.

```python
# boot.py
import usb_hid

# This is only one example of a gamepad descriptor.
# It may not suit your needs, or be supported on your host computer.

GAMEPAD_REPORT_DESCRIPTOR = bytes((
    0x05, 0x01,  # Usage Page (Generic Desktop Ctrls)
    0x09, 0x05,  # Usage (Game Pad)
    0xA1, 0x01,  # Collection (Application)
    0x85, 0x04,  #   Report ID (4)
    0x05, 0x09,  #   Usage Page (Button)
    0x19, 0x01,  #   Usage Minimum (Button 1)
    0x29, 0x10,  #   Usage Maximum (Button 16)
    0x15, 0x00,  #   Logical Minimum (0)
    0x25, 0x01,  #   Logical Maximum (1)
    0x75, 0x01,  #   Report Size (1)
    0x95, 0x10,  #   Report Count (16)
    0x81, 0x02,  #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0x05, 0x01,  #   Usage Page (Generic Desktop Ctrls)
    0x15, 0x81,  #   Logical Minimum (-127)
    0x25, 0x7F,  #   Logical Maximum (127)
    0x09, 0x30,  #   Usage (X)
    0x09, 0x31,  #   Usage (Y)
    0x09, 0x32,  #   Usage (Z)
    0x09, 0x35,  #   Usage (Rz)
    0x75, 0x08,  #   Report Size (8)
    0x95, 0x04,  #   Report Count (4)
    0x81, 0x02,  #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0xC0,        # End Collection
))

gamepad = usb_hid.Device(
    report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
    usage_page=0x01,           # Generic Desktop Control
    usage=0x05,                # Gamepad
    report_ids=(4,),           # Descriptor uses report ID 4.
    in_report_lengths=(6,),    # This gamepad sends 6 bytes in its report.
    out_report_lengths=(0,),   # It does not receive any reports.
)

usb_hid.enable(
    (usb_hid.Device.KEYBOARD,
     usb_hid.Device.MOUSE,
     usb_hid.Device.CONSUMER_CONTROL,
     gamepad)
)
```

## Creating a Report Descriptor

Writing your own HID report descriptor from scratch requires a lot of detailed knowledge, and is beyond the scope of this guide. However, you can often find the report descriptor for an existing HID device you want to emulate with a web search, and just use it as is, or modify it slightly for your purposes.&nbsp; If you have a device but don't have a published report descriptor for it, there are tools available to capture the report descriptor when you plug the device in. **usbhid-dump** is a such a tool you can use on Linux, and [here are some similar tools for Windows and macOS](https://todbot.com/blog/2021/01/29/get-hid-report-descriptors-with-win-hid-dump-mac-hid-dump/). There are many others.

There are many tutorials about HID report descriptors available, such as&nbsp;[this one](https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/). And&nbsp;[here's an online tool](http://eleccelerator.com/usbdescreqparser/)&nbsp;that can decipher existing report descriptors.

The [Microsoft Waratah tool](https://github.com/microsoft/hidtools/wiki) compiles HID report descriptors from a TOML-like description. It can make the process of writing report descriptors less error-prone.

You will also need to write a CircuitPython driver to handle your new device. The driver assembles a report and then passes it back to the host computer. You can see examples of HID devices drivers [in the adafruit\_hid](https://github.com/adafruit/Adafruit_CircuitPython_HID/tree/main/adafruit_hid) library. And the following pages in this guide will give you several examples of custom HID devices and drivers which you can just copy, or adapt for your own purposes.&nbsp;

# Custom HID Devices in CircuitPython

## Radial Controller

A radial controller is a HID device that transmits information about something that turns, such as a knob. Microsoft [supports certain kinds of radial controllers on Windows](https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/radial-implementation-guide), and sells the Microsoft Surface Dial, which is a radial controller with haptic feedback. A radial controller reports relative rotation changes as you turn its dial, and has a momentary switch you activate by pressing down.

The example below is a simple implementation of a radial controller, without haptic feedback, that works on Windows 10 or later. If you use it on Windows, the interactions will be similar to those [described by Microsoft for the Surface Dial](https://docs.microsoft.com/en-us/windows/apps/design/input/radialcontroller-walkthrough).

Info: 

## Software and Hardware Components
There are three pieces of software you use to implement this radial controller in CircuitPython. For hardware, you'll use a rotary encoder with a built-in switch.

1. The [adafruit\_radial\_controller library](https://github.com/adafruit/Adafruit_CircuitPython_Radial_Controller) ([documentation](https://circuitpython.readthedocs.io/projects/radial-controller/en/latest/)), which is in the Adafruit library bundle.
2. A **boot.py** file, which creates the device before USB starts.
3. A **code.py** file, which monitors a rotary encoder and passes state changes via the library. The **code.py** file also needs the `adafruit_hid` and `adafruit_debouncer` libraries.

This example is written for an [Adafruit Rotary Trinkey](https://learn.adafruit.com/adafruit-rotary-trinkey), but could be adapted for any board with a rotary encoder connected. You will need to change the pin names for other boards.

Warning: 

This screnshot shows what you'll see on your **CIRCUITPY** drive when you have everything you need, _except that you also need the_ **boot.py** _file,_ which is missing from this picture.

![CIRCUITPY](https://adafruit.github.io/Adafruit_CircuitPython_Bundle/radial_controller_radial_controller_rotary_trinkey.py.png)

The Rotary Trinkey will be easier to use if you mount it in something that can sit on your desk. For a finished project, you can make a 3D-printed enclosure reminiscent of the [Media Dial](https://learn.adafruit.com/media-dial/3d-printing) or the Microsoft Surface dial, with a base and a large top knob. But for a quick trial you can just mount the Trinkey in a food container or similar, as shown in the photos.

![gaming_IMG_2233.jpg](https://cdn-learn.adafruit.com/assets/assets/000/104/924/medium640/gaming_IMG_2233.jpg?1633018368)

![gaming_IMG_2234.jpg](https://cdn-learn.adafruit.com/assets/assets/000/104/925/medium640/gaming_IMG_2234.jpg?1633018400)

## Installing the Project Code

Use the **Download Project Bundle** button in either code block below to download the project code. Unzip the download, and copy **boot.py** , **code.py** and the **lib** folder to your board.

## **boot.py**

The **boot.py** file in this project creates the radial controller device, and sets it up to be available when **code.py** starts.

https://github.com/adafruit/Adafruit_CircuitPython_Radial_Controller/blob/main/examples/radial_controller_rotary_trinkey/boot.py

## **code.py**

The **code.py** program in this project first creates an `adafruit_radial_controller.``RadialController`. Then it loops forever, continuously checks the state of the switch on the rotary encoder, and whether the encoder position has changed. If so, it sends a report with the changed state information. The encoder change is relative to the previous position.

A single click of the encoder in either direction is a `rotaryio.IncrementalEncoder.position` change of just 1 or -1. But that value is not sent directly. Experimentation showed that such a small value is sometimes ignored by Windows unless many occur in quick succession. So this multiplies the actual position change by `DEGREE_TENTHS_MULTIPLIER`, which is 100 in the code. You can experiment with other multiplier values if you'd like. Try 20 or 50, for example.

The rotation value sent is an integer, in units of tenths of a degree. So for instance, a sending a 1 represents a 0.1 degree rotation. This is not actually the rotation angle of the physical encoder we're using, but it is what the Microsoft driver expects.

https://github.com/adafruit/Adafruit_CircuitPython_Radial_Controller/blob/main/examples/radial_controller_rotary_trinkey/code.py

# Custom HID Devices in CircuitPython

## N-Key Rollover (NKRO) Keyboard

by [Jeff Epler](https://learn.adafruit.com/users/jepler)

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&nbsp; **code.py** and **boot.py** files. To get everything you need, click on the&nbsp; **Download Project Bundle** &nbsp;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.

![Directory](https://adafruit.github.io/Adafruit_Learning_System_Guides/CircuitPython_MacroPad_NKRO.png)

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CircuitPython_MacroPad_NKRO/code.py

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CircuitPython_MacroPad_NKRO/boot.py

You can use [Microsoft's Keyboard ghosting interactive demonstration](https://www.microsoft.com/applied-sciences/projects/anti-ghosting-demo) (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.

![circuitpython_Screenshot_2021-07-20_10-32-31.png](https://cdn-learn.adafruit.com/assets/assets/000/103/512/medium640/circuitpython_Screenshot_2021-07-20_10-32-31.png?1626795770)

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.


## Featured Products

### Adafruit MacroPad RP2040 Starter Kit - 3x4 Keys + Encoder + OLED

[Adafruit MacroPad RP2040 Starter Kit - 3x4 Keys + Encoder + OLED](https://www.adafruit.com/product/5128)
Strap yourself in, we're launching in T-minus 10 seconds...Destination? A new Class M planet called MACROPAD! M here stands for Microcontroller because this 3x4 keyboard controller features the newest technology from the Raspberry Pi sector: say hello to the RP2040. It's a speedy...

Out of Stock
[Buy Now](https://www.adafruit.com/product/5128)
[Related Guides to the Product](https://learn.adafruit.com/products/5128/guides)
### Adafruit Rotary Trinkey - USB NeoPixel Rotary Encoder

[Adafruit Rotary Trinkey - USB NeoPixel Rotary Encoder](https://www.adafruit.com/product/4964)
It's half USB Key, half Adafruit Trinket, half rotary encoder_..._it's **Rotary Trinkey** , the circuit board with a Trinket M0 heart, a NeoPixel glow, and a rotary encoder body. [We were inspired by this...](https://twitter.com/todbot/status/1365107469345648643)

In Stock
[Buy Now](https://www.adafruit.com/product/4964)
[Related Guides to the Product](https://learn.adafruit.com/products/4964/guides)
### Rotary Encoder + Extras

[Rotary Encoder + Extras](https://www.adafruit.com/product/377)
This rotary encoder is the best of the best, it's a high-quality 24-pulse encoder, with detents and a nice feel. It is panel mountable for placement in a box, or you can plug it into a breadboard (just cut/bend the two mechanical side tabs.) We also include a nice soft-touch knob with an...

In Stock
[Buy Now](https://www.adafruit.com/product/377)
[Related Guides to the Product](https://learn.adafruit.com/products/377/guides)

## Related Guides

- [Adafruit Rotary Trinkey](https://learn.adafruit.com/adafruit-rotary-trinkey.md)
- [Adafruit MacroPad RP2040](https://learn.adafruit.com/adafruit-macropad-rp2040.md)
- [Raspberry Pi Rotary Encoder Animated Gif Player](https://learn.adafruit.com/python-rotary-animated-gif-player-two-different-ways.md)
- [Ableton Live MacroPad Launcher](https://learn.adafruit.com/ableton-live-macropad-launcher.md)
- [Scrambled Number Security Keypad](https://learn.adafruit.com/scrambled-number-security-keypad.md)
- [3D Printed Stand for MacroPad RP2040](https://learn.adafruit.com/3d-printed-stand-for-macropad-rp2040.md)
- [MP3 Playback on RP2040 with CircuitPython](https://learn.adafruit.com/mp3-playback-rp2040.md)
- [AdaBox 019](https://learn.adafruit.com/adabox019.md)
- [CircuitPython Rotary Trinkey Brightness Crank](https://learn.adafruit.com/circuitpython-rotary-trinkey-brightness-crank.md)
- [MacroPad Braille Keycaps](https://learn.adafruit.com/macropad-braille-keycaps.md)
- [MACROPAD Hotkeys](https://learn.adafruit.com/macropad-hotkeys.md)
- [An Introduction to RP2040 PIO with CircuitPython](https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython.md)
- [MacroPad 2FA TOTP Authentication Friend](https://learn.adafruit.com/macropad-2fa-totp-authentication-friend.md)
- [Minecraft Turbopad](https://learn.adafruit.com/minecraft-turbopad.md)
- [MacroPad Summer Olympics Hotkeys](https://learn.adafruit.com/macropad-olympic-hotkeys.md)
