{"id":21196264,"url":"https://github.com/neildavis/teensy_hid_gamepad","last_synced_at":"2025-04-13T19:23:11.261Z","repository":{"id":102517256,"uuid":"475656682","full_name":"neildavis/teensy_hid_gamepad","owner":"neildavis","description":"Serial programmable USB HID Gamepad using CircuitPython","archived":false,"fork":false,"pushed_at":"2023-08-26T16:49:26.000Z","size":53,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T10:03:15.070Z","etag":null,"topics":["circuitpython","emulationstation","gamepad","gamepad-controller","joystick-device","mcu","python","retroarch","rp2040","usb-cdc","usb-hid","usb-hid-devices"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neildavis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-29T23:55:01.000Z","updated_at":"2024-12-11T17:12:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"b63df78d-50f6-40f1-a783-f33de1b185a3","html_url":"https://github.com/neildavis/teensy_hid_gamepad","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neildavis%2Fteensy_hid_gamepad","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neildavis%2Fteensy_hid_gamepad/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neildavis%2Fteensy_hid_gamepad/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neildavis%2Fteensy_hid_gamepad/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neildavis","download_url":"https://codeload.github.com/neildavis/teensy_hid_gamepad/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248766687,"owners_count":21158302,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["circuitpython","emulationstation","gamepad","gamepad-controller","joystick-device","mcu","python","retroarch","rp2040","usb-cdc","usb-hid","usb-hid-devices"],"created_at":"2024-11-20T19:35:09.254Z","updated_at":"2025-04-13T19:23:11.242Z","avatar_url":"https://github.com/neildavis.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# teensy_hid_gamepad\n\n## Overview\n\nA ___programmable___ USB HID Gamepad using\n[CircuitPython](https://circuitpython.org/)\nwith the following features:\n\n* Up to 16 [digital inputs](#digital-inputs) (GPIO) can be attached and mapped to USB HID Gamepad buttons.\n* Up to 4 [analog inputs](#analog-inputs)\n(16-bit ADC) can be attached and mapped to two USB HID Gamepad joystick axes.\n* [Rotary Encoder inputs](#rotary-encoder-inputs) mapped to gamepad button or volume button pairs\n* [USB _Consumer Control_](#usb-consumer-control)\nsupport for volume up/down/mute and power off.\n* [Programmable](#programmable-serial-interface)\nHID responses via USB CDC Serial comms using the same port as USB HID.\n* [Programmable](#programmable-serial-interface)\ndynamic re-mapping of digital inputs to buttons and analog inputs to joystick axis\n* Single command [automated configuration](#special-configuration-commands) for\n[EmulationStation](https://github.com/Aloshi/EmulationStation) and\n[RetroArch](https://www.retroarch.com/) as used in e.g.\n[RetroPie](https://retropie.org.uk/).\n* Open and hackable.\n\n## Target Hardware\n\nThis project was originally developed for a\n[Teensy 4.0](https://www.pjrc.com/store/teensy40.html)\nMCU (hence the name), but is now targeted at\n[RP2040](https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html)\ndevelopment boards like\n[Pimoroni Tiny2040](https://shop.pimoroni.com/products/tiny-2040?variant=39560012234835).\n\nIt should work on any MCU with enough ADC \u0026 GPIO inputs and a working CircuitPython port including USB HID \u0026 CDC serial support. Some minor changes for board I/O pins etc. may be required.\n\n## Installation\n\n1. Ensure you have\n[CircuitPython installed](https://learn.adafruit.com/welcome-to-circuitpython/installing-circuitpython)\nfor your MCU board.\n2. Install the required\n[libraries](https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries)\nfrom the [latest Adafruit CircuitPython Bundle](https://circuitpython.org/libraries):\n\n    * `adafruit_hid`\n    * `adafruit_datetime`\n3. Copy all of the `*.py` files in the root of this repository to the root of your `CIRCUITPY` drive/volume.\n\n## Modifying Code\n\nIf you need to modify any of the code, note that in [`code.py`](./code.py) the default 'auto-reload on save' functionality has been disabled by this line:\n\n```python\n# Disable auto reload\nsupervisor.runtime.autoreload = False\n```\n\nTo reload after saving, use the\n[CircuitPython serial console](https://learn.adafruit.com/welcome-to-circuitpython/kattni-connecting-to-the-serial-console) and press\n`CTRL+C` to interrupt the running program, and then `CTRL+D` to reload. Or just delete/comment out the line above.\n\n## Physical Inputs\n\nBoth analog \u0026 digital components can be used as inputs for the gamepad:\n\n### Analog Inputs\n\nBy default, two gamepad analog joysticks (with two axes each) are enabled on four ADC inputs (`A0`-`A3`) as found on the\n[Pimoroni Tiny2040](https://shop.pimoroni.com/products/tiny-2040?variant=39560012234835) board.\nThese can be changed and/or removed in these sections of code in [`config.py`](./config.py):\n\n```python\n# These are the default mappings of analog axes for joysticks:\ndefault_joystick_pins = {\n    'x'     : 'a0',\n    'y'     : 'a1',\n    'z'     : 'a2',\n    'r_z'   : 'a3',\n}\n```\n\nThe '`x`' \u0026 '`y`' axes correspond to the _left_ analog joystick, whilst '`z`' \u0026 '`r_z`' correspond to the _right_ analog joystick. The values are keys into the available analog axes defined in `analog_ins`.\nYou are free to modify these but just be sure they match up in both dictionaries:\n\n```python\nanalog_ins: dict[str,  Pin ] = {\n    'a0'    : board.A0,\n    'a1'    : board.A1,\n    'a2'    : board.A2,\n    'a3'    : board.A3,\n}\n```\n\nRemove unwanted entries and/or map to alternative inputs on your board as required.\n\n### Digital Inputs\n\nUp to 16 buttons and 3 volume controls can be mapped to GPIO inputs on the board.\n\nBy default, since the\n[Pimoroni Tiny2040](https://shop.pimoroni.com/products/tiny-2040?variant=39560012234835)\nboard has only 8 GPIO left (excluding the pins used as ADC for the analog joysticks)\nonly 6 gamepad buttons and 2 volume control buttons are mapped.\nThese can be changed and/or removed in these sections of code in [`config.py`](./config.py):\n\n```python\n# These are the default mappings of buttons to digital inputs\ndefault_button_pins = {\n    BUTTON_VOL_UP   : 'd0',\n    BUTTON_VOL_DOWN : 'd1',\n    BUTTON_START    : 'd2',\n    BUTTON_SELECT   : 'd3',\n    BUTTON_SOUTH_B  : 'd4',\n    BUTTON_WEST_Y   : 'd5',\n    BUTTON_EAST_A   : 'd6',\n    BUTTON_NORTH_X  : 'd7',\n}\n```\n\nThe keys identify the gamepad button action.\nThe values are keys into the available digital inputs defined in `digital_ins`.\nYou are free to modify these but just be sure they match up in both dictionaries:\n\n```python\ndigital_ins: dict[str, Pin] = {\n    'd0'    : board.GP0,\n    'd1'    : board.GP1,\n    'd2'    : board.GP2,\n    'd3'    : board.GP3,\n    'd4'    : board.GP4,\n    'd5'    : board.GP5,\n    'd6'    : board.GP6,\n    'd7'    : board.GP7,\n}\n```\n\nThe complete set of valid button keys can also be seen in [`config.py`](./config.py):\n\n```python\n# Enumerate all our digital io inputs as HID button IDs (0-15)\nBUTTON_WEST_Y       = 0\nBUTTON_SOUTH_B      = 1\nBUTTON_EAST_A       = 2\nBUTTON_NORTH_X      = 3\nBUTTON_SHOULDER_L   = 4\nBUTTON_SHOULDER_R   = 5\nBUTTON_TRIGGER_L    = 6\nBUTTON_TRIGGER_R    = 7\nBUTTON_SELECT       = 8\nBUTTON_START        = 9\nBUTTON_THUMB_L      = 10\nBUTTON_THUMB_R      = 11\nBUTTON_HAT_UP       = 12\nBUTTON_HAT_DOWN     = 13\nBUTTON_HAT_LEFT     = 14\nBUTTON_HAT_RIGHT    = 15\nBUTTON_MAX          = BUTTON_HAT_RIGHT\n# CC Volume handled by buttons outside gamepad button range\nBUTTON_VOL_UP       = ConsumerControlCode.VOLUME_INCREMENT\nBUTTON_VOL_DOWN     = ConsumerControlCode.VOLUME_DECREMENT\nBUTTON_VOL_MUTE     = ConsumerControlCode.MUTE\nBUTTON_POWER        = CC_POWER_CODE\n```\n\nThe names should be self explanatory. Note that 'Hat' is USB HID speak for what is commonly\nreferred to as a\n['D-Pad'](https://en.wikipedia.org/wiki/D-pad)\n\nThe numeric values 0-15 are those reported as Gamepad button IDs in the USB HID reports.\nThese have been assigned by reverse engineering of a\n[Logitech F310](https://www.logitechg.com/en-in/products/gamepads/f310-gamepad.940-000112.html)\ncontroller - which mimics an Xbox 360 controller - using Gamepad test software such as\n[this](https://greggman.github.io/html5-gamepad-test/).\nNote that the 'X' \u0026 'Y' pair and the 'A' \u0026 'B' pair are arranged in opposite order to the commonly\nused SNES controller. If this is an issue, just swap them around.\n\nThe values for volume are assigned to\n[`adafruit_hid.consumer_control_code.ConsumerControlCode`](https://docs.circuitpython.org/projects/hid/en/latest/_modules/adafruit_hid/consumer_control_code.html)\nvalues for convenience, given that they do not clash with the gamepad button range (0-15)\n\n### Rotary Encoder Inputs\n\nAn arbitrary number of rotary encoders can be used to map onto pairs of digital inputs\n(one digital input each for clockwise/ant-clockwise)\n\nBy default __no rotary encoders are configured__. However the code in [`config.py`](./config.py)\nincludes a commented-out example for a rotary encoder on inputs `d0` and `d1` to control volume:\n\n```python\ndefault_rotary_encoder_pins: dict[str: (str, str, int, int)] = {\n    'rot_vol': ('d0', 'd1', BUTTON_VOL_DOWN, BUTTON_VOL_UP),\n}\n```\n\nThe key is arbitrary, but must not conflict with any of the joystick axes (`x`, `y`, `z`, `r_z`)\nThe positional arguments in the tuple value are as follows:\n\n1. Digital input id for the 'Clock' (CLK) pin of the rotary encoder. This is a key into `digital_ins`\n2. Digital input id for the 'Data' (DT) pin of the rotary encoder. This is a key into `digital_ins`\n3. Button id for the rotary encoder decrement (anti-clockwise) movement.\n4. Button id for the rotary encoder increment (clockwise) movement.\n\n## USB Consumer Control\n\nIn addition to the USB HID Gamepad functionality, limited USB _Consumer Control_ functions\nare supported. These are currently limited to:\n\n* Volume Up (Increment)\n* Volume Down (Decrement)\n* Volume Mute (Toggle)\n* Power Off\n\nThe volume commands can be mapped to digital GPIO inputs as described [above](#digital-inputs).\nThe 'power off' functionality is achieved by holding the 'start' button for a period of time\nas determined in the code by this constant in [`config.py`](./config.py):\n\n```python\n# Holding 'Start' button for this period will send a Power Off command\nSTART_BUTTON_HOLD_FOR_SHUTDOWN_SECS = 3\n```\n\nThis functionality is also dependent on the host operating system acting upon the USB CC codes\nsent by the device. Most modern desktop OS like Windows, macOS \u0026 Linux desktop distros will\nsupport this.\n\nSome 'bare bones' Linux distros without a GUI desktop environment may need additional software\nto enable this functionality,\ne.g. [Raspberry Pi OS Lite](https://www.raspberrypi.com/software/operating-systems/). In these\ncases, my [daemon](https://github.com/neildavis/alsa_volume_from_usb_hid) project may work.\n\n## Programmable Serial Interface\n\nIn additional to the physical [analog](#analog-inputs) and [digital](#digital-inputs) inputs,\nUSB HID events can be synthesized using a programmable interface via USD CDC serial comms.\n\n### Motivation\n\nI have created a few custom USB HID input controllers for various projects such as my:\n\n* [Tiger / Grandstand / Sega - After Burner - Tabletop Arcade Conversion](https://www.youtube.com/watch?v=KxgmwC9LNg8)\n* [Tomy Demon Driver (1978) vs Sega Monaco GP (1979)](https://www.youtube.com/shorts/3PWjTkotoec)\n\nThese projects commonly have significantly fewer inputs than a full dual-shock style gamepad.\nThey also run on platforms using software such as\n[RetroPie](https://retropie.org.uk/),\n[RetroArch](https://www.retroarch.com/) and\n[EmulationStation](https://github.com/Aloshi/EmulationStation).\nThese software include useful features to simplify configuration of input devices by producing controller inputs\nin response to visual prompts. However, they can assume a full gamepad input set which is not available on my\ncustom controller. By synthesizing these events, we can make use of these convenient tools without having to\nresort to editing config files manually.\n\n### Synthesized input interface\n\nThe programmable serial interface makes use of the\n[second 'data' serial device](https://learn.adafruit.com/customizing-usb-devices-in-circuitpython/circuitpy-midi-serial#usb-serial-console-repl-and-data-3096590-12)\noffered by CircuitPython to receive input. See\n[Adafruit's docs](https://learn.adafruit.com/customizing-usb-devices-in-circuitpython/circuitpy-midi-serial#usb-serial-console-repl-and-data-3096590-12)\nto learn how to identify the appropriate serial (or 'COM') port for your OS.\n\nYou can make use of this interface using any commonly available serial terminal emulator software.\nPopular text-base ones include\n[Minicom](https://en.wikipedia.org/wiki/Minicom) or\n[Screen](https://en.wikipedia.org/wiki/GNU_Screen).\nThere are also GUI alternatives such as\n[PuTTY](https://en.wikipedia.org/wiki/PuTTY).\nA large (but non-exhaustive) list can be found\n[here](https://en.wikipedia.org/wiki/List_of_terminal_emulators).\n\nThe interface accepts commands as a line of text terminated by a CR (`0xD`) or LF (`0xA`).\nEach line may contain an arbitrary number of _`name=value`_ pairs separated by a semi-colon.\nThe available commands are listed in the following table:\n\n| Command Name (e.g.) | Valid Values (e.g.)| Description |\n|-|-|-|\n| `btn{N}` (e.g. `btn1`) | `1` | Press (and release) button `N`\n| `x`, `y`, `z`, `r_z` | `-16327` - `16327` | Set joystick axes analog values\n| `vol` | `-1`, `1`, `mute` | Volume. `1` increments, `-1` decrements, `mute` toggles 'mute' |\n| `{digital input}` (e.g. `d0`) | `{button id}` (e.g. `9` == '`Start`') | [Re]Map a digital input to a button ID |\n| `{analog input}` (e.g. `a0`) | `{joystick axis}` (e.g. `r_z`) | [Re]Map an analog input to a joystick axis |\n| `hold` | +ve floating point values | Time in seconds to hold the controls at specified values |\n| `pre` | +ve floating point values | Time in seconds to wait ___before___ synthesizing the inputs |\n| `post` | +ve floating point values | Time in seconds to wait ___after___ synthesizing the inputs |\n\nBy default, the specified input values are __held for half a second__. This can be changed by use of\nthe `hold` command.\n\n#### Examples\n\n| Command string | Actions |\n|-|-|\n|'`btn1=1;btn5=1;x=-16327`' | Press buttons 1 \u0026 5 and set left analog stick x-axis full left. |\n|'`r_z=8000`' | Move right analog joystick y-axis approx half way down. |\n|'`btn=1;hold=5`' | Press button 3 and hold it for five seconds. |\n|'`vol=-1;post=2.5`' | Decrement volume and wait for 2.5 seconds before processing any other events or commands. |\n|'`d0=9;a3=y`' | Remap digital input `d0` to button number `9` (`Start`) and remap analog input `a3` to left joystick `y` axis. |\n\n### Special Configuration Commands\n\nIn addition to the generic programmable serial interface described\n[above](#programmable-serial-interface)\nspecific commands are available to automate particular softwares' configuration procedures.\nThese commands are not 'compoundable' with the generic commands above and __must__ be entered alone.\n\n* '`conf_es`' : Performs a full input configuration sequence for\n[EmulationStation](https://github.com/Aloshi/EmulationStation)\n(Main Menu -\u003e Configure Input)\n* '`conf_ra`' : Performs a full input configuration sequence for\n[RetroArch](https://www.retroarch.com/)\n(Main Menu -\u003e Settings -\u003e Input -\u003e Port N Controls -\u003e Set All Controls)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneildavis%2Fteensy_hid_gamepad","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneildavis%2Fteensy_hid_gamepad","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneildavis%2Fteensy_hid_gamepad/lists"}