The Xbox Adaptive Controller (XAC) has two USB ports on the side for use with USB joysticks -- these are designed to allow alternative hardware to replace the left and right thumbsticks of a typical gaming controller. Rather than be constrained by the commercially available USB joysticks, you can use the TRRS Trinkey to design custom solutions.

In this example, a typical X/Y dual potentiometer joystick breakout is read by two analog pins on the TRRS Trinkey. The Trinkey is running CircuitPython and emulating an XAC-compatible HID gamepad. You can substitute your own analog inputs, such as joystick/thumbstick modules, soft potentiometers, and more, as well as adjust the code to meet your needs.

Angled Shot of the Adafruit TRRS Trinkey.
It's half USB Key, half TRRS breakout... it's the Adafruit TRRS Trinkey specifically designed for Assistive Technology hackers and...
In Stock
2-Axis Joystick Thumbstick with breakout board
This mini-kit makes it easy to mount a PSP/Xbox-like thumb joystick to your project. The thumbstick is an analog joystick - more accurate and sensitive than just 'directional'...
In Stock
Angled shot of a A/V and RCA (Composite Video, Audio) Cable for Raspberry Pi.
This A/V RCA Cable for Raspberry Pi is a great way to turn your Pi 3, 2, A+ or B+'s output into a full on composite video and audio device.  Simply connect the cable to your...
In Stock
Angled shot showing dual potentiometers
Sometimes a simple analog control device can be the perfect tactile solution for your project, but they can be surprisingly hard to come by. Luckily we've found a low-cost,...
In Stock
Top shot of a feather, a humidity sensor, a potentiometer and an I2C quad board mounted on to the grid.
With most of our dev boards, sensors and feathers now sporting plug-and-play stemma QT ports it can be very fast to...
In Stock



Any of the four TRRS pins of the TRRS Trinkey can be assigned as digital in/out or analog in. We'll set the TIP and RING_1 as analog inputs to read the two joystick pots, and RING_2 and SLEEVE as ground and Vcc respectively. 

Cable Wiring

Use a multimeter's continuity tester to check the wire mapping of your TRRS cable (I used a TRRS to AV cable with the RCA jacks cut off, although you can also buy them pre-cut usually sold as "replacement 3.5mm TRRS cables"), then solder the following:

  • Cable TIP to Xout
  • Cable RING_1 to Yout
  • Cable RING_2 to GND
  • Cable SLEEVE to Vcc

Plug In Joystick

Plug in the joystick to the TRRS Trinkey.

CircuitPython Usage

To use with CircuitPython, you need to first install the xac_gamepad library and into the lib folder onto your CIRCUITPY drive. Then you need to update with the example script, and save the onto the root of the CIRCUITPY drive.

Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and files in a zip file. Extract the contents of the zip file, and copy the entire lib folder and the and files to your CIRCUITPY drive.

# SPDX-FileCopyrightText: 2024 by John Park for Adafruit Industries
# SPDX-License-Identifier: MIT
# adapted from Bill Binko's Chording Switches code
Xbox Adaptive Controller USB port joystick
Use a two axis joystick, or combo of pots, soft pots, etc.
wired to TRRS plug:
 Tip = X
 Ring 1 = Y
 Ring 2 = GND
 Sleeve = VCC
import time
import array
import board
import analogio
import digitalio
#Custom version of Gamepad compatible w/the XBox Adaptive Controller (XAC)
import xac_gamepad
# pylint: disable=wildcard-import, unused-wildcard-import
from XACsettings import *

gp = xac_gamepad.XACGamepad()

class RollingAverage:
    def __init__(self, size):
        # pylint: disable=c-extension-no-member
        self.buffer = array.array('d')
        for _ in range(size):
        self.pos = 0
    def addValue(self,val):
        self.buffer[self.pos] = val
        self.pos = (self.pos + 1) % self.size
    def average(self):
        return sum(self.buffer) / self.size

# Two analog inputs for TIP and RING_1
hor = analogio.AnalogIn(board.TIP)
vert = analogio.AnalogIn(board.RING_1)

# RING_2 as ground
ground = digitalio.DigitalInOut(board.RING_2)
ground.value = False

# SLEEVE as VCC (3.3V)
vcc = digitalio.DigitalInOut(board.SLEEVE)
vcc.value = True

def range_map(value, in_min, in_max, out_min, out_max):
    # pylint: disable=line-too-long
    return int(max(out_min,min(out_max,(value - in_min) * (out_max - out_min) // (in_max - in_min) + out_min)))

# These two are how much we should smooth the joystick - higher numbers smooth more but add lag
#We need two Rolling Average Objects to smooth our values
xAvg = RollingAverage(HOR_AVG_COUNT)
yAvg = RollingAverage(VERT_AVG_COUNT)


while True:
    x = range_map(hor.value, 540, 65000, 0, 255)
    y = range_map(vert.value, 65000, 540, 0, 255)

    #Calculate the rolling average for the X and Y
    lastXAvg = xAvg.average()
    lastYAvg = yAvg.average()

    #We know x and y, so do some smoothing
    #We need to send integers so calculate the average and truncate it
    newX = int(xAvg.average())
    newY = int(yAvg.average())

    #We only call move_joysticks if one of the values has changed from last time
    if (newX != lastXAvg or newY != lastYAvg):
        # print(hor.value, vert.value)  # print debug raw values
        # print((newX, newY,))  # print debug remapped, averaged values
    #Sleep to avoid overwhelming the XAC

Use It

Once you've coded the TRRS Trinkey you can plug it into either USB A port on the XAC and use it for gameplay on your Xbox or Windows machine. It will show up as either thumbstick in-game. Build a second one for the other stick if you like.

You can see it in action here:

This guide was first published on Jan 11, 2019. It was last updated on Jul 13, 2024.

This page (Adaptive Joysticks) was last updated on Jul 13, 2024.

Text editor powered by tinymce.