[{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/813/original/fruit-jam-gamepad-tester-demo.png?1747625840","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eOverview\u003c/h2\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n        \u003cp\u003eThis is a visual gamepad tester for CircuitPython on Fruit Jam. The tester can detect and map buttons for several common gamepad types, including the Adafruit SNES-like gamepad. The tester can also display hexdumps of input event reports from HID boot-compatible keyboards and mice. Other HID devices may also work as long as their report lengths are short enough to fit on the screen (about 8 bytes).\u003c/p\u003e\n\u003cp\u003eI wrote the code on CircuitPython 10.0.0-alpha.4 through 10.0.0-alpha.6 on a rev B Fruit Jam prototype. Using a Metro RP2350 with USB hub would probably work too. I haven't tried that, so it's possible you might need to make minor tweaks to the code for pin names (e.g. boot button and DVI out).\u003c/p\u003e\n\u003cp\u003eTo learn more about the spritesheet animation I use for the gamepad visualizer, check out my \u003ca href=\"https://adafruit-playground.com/u/SamBlenny/pages/feather-tft-gamepad-tester-with-sprites\" target=\"_blank\"\u003eFeather TFT Gamepad Tester with Sprites\u003c/a\u003e guide from last year. This version of the tester for Fruit Jam is based on the Feather TFT version. For Fruit Jam, I added support for the larger display and more gamepad types.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003e\u003cspan\u003eTransparency note: Adafruit provided the Fruit Jam rev B prototype board I used for this guide (Thanks Adafruit!). Adafruit makes good stuff, please support them by buying from the Adafruit shop.\u003c/span\u003e\u003c/p\u003e\n      \n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eDemo Video\u003c/h2\u003e\n\u003cp\u003eThis is a demo video showing DVI and serial console output from the Fruit Jam as I try the code with the pile of test devices strewn across my desk (so many cables!).\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://www.youtube.com/watch?v=-Aollz3TVfI"},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eParts\u003c/h2\u003e\n\u003cp\u003eI wrote this for Fruit Jam, but a Metro RP2350 with a USB hub would probably also work. For more information on setting up a Metro RP2350 as a pseudo-Fruit Jam, you can check out some of the recent guides by \u003ca href=\"https://learn.adafruit.com/match3-game-on-metro-rp2350\" target=\"_blank\"\u003eTim C.\u003c/a\u003e and \u003ca href=\"https://learn.adafruit.com/minesweeper-on-metro-rp2350\" target=\"_blank\"\u003eM. LeBlanc-Williams\u003c/a\u003e.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/6285","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/6267","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/4449","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5999","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/6035","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/6055","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eUpdating CircuitPython\u003c/h2\u003e\n\u003cp\u003eAs I write this (May 19, 2025), USB support for Fruit Jam and Metro RP250 is under active development. My gamepad tester code was most recently tested on CircuitPython 10.0.0-alpha.6 for Fruit Jam. I'm not sure what will change by the time 10.0.0 is released. To keep your board up to date, you can use the \"DOWNLOAD .UF2 NOW\" buttons on the appropriate download page of circuitpython.org:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003ca href=\"https://circuitpython.org/board/adafruit_metro_rp2350/\" target=\"_blank\"\u003eMetro RP2350 Download\u003c/a\u003e page\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://circuitpython.org/board/adafruit_fruit_jam/\" target=\"_blank\"\u003eFruit Jam Download\u003c/a\u003e page\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eTo install the UF2 File:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eConnect your board to a computer with a USB data cable (\u003cem\u003echarge-only cables won't work! \u003c/em\u003e)\u003c/li\u003e\n\u003cli\u003ePress and hold the board's boot button (Button 1 on Fruit Jam)\u003c/li\u003e\n\u003cli\u003ePress and release the reset button\u003c/li\u003e\n\u003cli\u003eRelease the boot button\u003c/li\u003e\n\u003cli\u003eWhen the removable drive named \u003cstrong\u003eRP2350\u003c/strong\u003e appears, copy the UF2 file onto it. After the copy finishes, you should see the \u003cstrong\u003eRP2350\u003c/strong\u003e drive disappear and soon after that a new \u003cstrong\u003eCIRCUITPY\u003c/strong\u003e drive should appear.\u003c/li\u003e\n\u003c/ol\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eCircuitPython Code\u003c/h2\u003e\n\u003cp\u003eYou can view the code at the \u003ca href=\"https://github.com/samblenny/fruit-jam-gamepad-tester\" target=\"_blank\"\u003esamblenny/fruit-jam-gamepad-tester\u003c/a\u003e GitHub repository. To download a zip archive project bundle with the code and all the necessary libraries, use the \"Download Project Bundle\" button:\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"button","content":"https://github.com/samblenny/fruit-jam-gamepad-tester/releases/download/v0.2.1/fruit-jam-gamepad-tester-4d26163.zip","metadata":{"class":"btn btn-large btn-block btn-primary","button_type":"primary","target":"_blank","zip_folder":"false"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eInstall Project Bundle\u003c/h3\u003e\n\u003cp class=\"p2\"\u003e\u003cspan class=\"s1\"\u003eTo copy the project bundle files to your CIRCUITPY drive:\u003c/span\u003e\u003c/p\u003e\n\u003col class=\"ol1\"\u003e\n\u003cli class=\"li3\"\u003e\u003cspan class=\"s1\"\u003eDownload the project bundle .zip file using the \"Download Project Bundle\" button above.\u003c/span\u003e\u003c/li\u003e\n\u003cli class=\"li3\"\u003e\u003cspan class=\"s1\"\u003eExpand the zip file by opening it, or use unzip in a Terminal. The zip archive should expand to a folder. When you open the folder, it should contain a README.txt file and a CircuitPython 9.x folder. (\u003cstrong\u003eyes, 9.x works for the 10.0.0-alpha builds\u003c/strong\u003e)\u003c/span\u003e\u003c/li\u003e\n\u003cli class=\"li3\"\u003e\u003cspan class=\"s1\"\u003eOpen the CircuitPython 9.x folder and copy all of its contents to your CIRCUITPY drive.\u003c/span\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp class=\"p2\"\u003e\u003cspan class=\"s1\"\u003eTo learn more about copying libraries to your\u003cspan\u003e\u0026nbsp;\u003c/span\u003e\u003cstrong\u003eCIRCUITPY\u003c/strong\u003e\u003cspan\u003e\u0026nbsp;\u003c/span\u003edrive, check out the\u0026nbsp;\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries\" target=\"_blank\"\u003e\u003cspan class=\"s2\"\u003eCircuitPython Libraries\u003c/span\u003e\u003c/a\u003e\u0026nbsp;section of the\u0026nbsp;\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython\" target=\"_blank\"\u003e\u003cspan class=\"s2\"\u003eWelcome to CircuitPython!\u003c/span\u003e\u003c/a\u003e\u0026nbsp;learn guide.\u003c/span\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n        \u003ch3\u003eUnderstanding the Code\u003c/h3\u003e\n\u003cp\u003eHere's an outline of the major files, functions, and classes, along with their role in making the Fruit Jam gamepad tester work:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cstrong\u003e\u003ccode\u003eCIRCUITPY/code.py\u003c/code\u003e:\u003c/strong\u003e\u0026nbsp;\n\u003cul\u003e\n\u003cli\u003eThe \u003cstrong\u003e\u003ccode\u003emain()\u003c/code\u003e\u003c/strong\u003e function initializes the display and sets up a GUI with a sprite-based gamepad visualizer and some \u003ccode\u003ebitmap_label\u003c/code\u003e objects for displaying text status messages. The main loop, starting with \u003ccode\u003ewhile True:\u003c/code\u003e, scans for USB devices, configures the first input device it finds, then polls for input events. To stop the loop and rescan the bus, you can press Button 1. The loop also stops for\u0026nbsp;\u003ccode\u003eUSBError\u003c/code\u003e, which happens sometimes when a device is unplugged. When input events arrive, the main loop prints the data to the console and DVI display and updates the gamepad visualizer widget.\u003c/li\u003e\n\u003cli\u003eThe \u003cstrong\u003e\u003ccode\u003eupdate_GUI()\u003c/code\u003e\u003c/strong\u003e function decodes a 16-bit bitfield of button states provided by the main loop and uses them to swap sprites in a \u003ccode\u003eTileGrid\u003c/code\u003e. The button bitfield is in the format for the third and fourth bytes of a raw USB input event report for an XInput gamepad. Input events from other gamepad types get normalized into this form by button mapping functions.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003e\u003ccode\u003eCIRCUITPY/gamepad.py\u003c/code\u003e:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eThe \u003ccode\u003eUP\u003c/code\u003e, \u003ccode\u003eDOWN\u003c/code\u003e, \u003ccode\u003eLEFT\u003c/code\u003e, \u003ccode\u003eRIGHT\u003c/code\u003e, \u003ccode\u003eSTART\u003c/code\u003e, \u003ccode\u003eSELECT\u003c/code\u003e, \u003ccode\u003eL\u003c/code\u003e, \u003ccode\u003eR\u003c/code\u003e, \u003ccode\u003eA\u003c/code\u003e, \u003ccode\u003eB\u003c/code\u003e, \u003ccode\u003eX\u003c/code\u003e, and \u003ccode\u003eY\u003c/code\u003e constants provide bit masks for each of the buttons in the 16-bit XInput format bitfield. For examples of how to use the bitmask constants, check out the \u003ccode\u003eupdate_GUI()\u003c/code\u003e function in \u003ccode\u003ecode.py\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eThe \u003cstrong\u003e\u003ccode\u003efind_usb_device()\u003c/code\u003e\u003c/strong\u003e function uses the \u003ca href=\"https://docs.circuitpython.org/en/latest/shared-bindings/usb/core/index.html\" target=\"_blank\"\u003eusb.core\u003c/a\u003e API to loop through devices connected to the USB bus. For each of those devices, it uses the \u003ccode\u003eusb_descriptor.Descriptor\u003c/code\u003e class from \u003ccode\u003eCIRCUITPY/usb_descriptor.py\u003c/code\u003e to check the vendor ID, product ID, and other device properties to identify different types of gamepads and HID devices. When \u003ccode\u003efind_usb_device()\u003c/code\u003e locates an input device, it creates a \u003ccode\u003eScanResult\u003c/code\u003e class instance and returns the scan result to the main loop from \u003ccode\u003ecode.py\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eThe \u003cstrong\u003e\u003ccode\u003eScanResult\u003c/code\u003e\u003c/strong\u003e class holds a reference to the \u003ccode\u003eusb.core.Device\u003c/code\u003e object for an input device along with its device type and the the \u003ccode\u003eusb_descriptor.Descriptor\u003c/code\u003e object. The main point of a \u003ccode\u003eScanResult\u003c/code\u003e object is to hold all the stuff you need in order to initialize an \u003ccode\u003eInputDevice\u003c/code\u003e object. The point of splitting things into two classes is to let the main loop look at the \u003ccode\u003eScanResult\u003c/code\u003e object to decide if it wants to start reading input from that device or not.\u003c/li\u003e\n\u003cli\u003eThe \u003cstrong\u003e\u003ccode\u003eInputDevice\u003c/code\u003e\u003c/strong\u003e class implements device drivers for various types of gamepads. The \u003cstrong\u003e\u003ccode\u003einput_event_generator()\u003c/code\u003e\u003c/strong\u003e method provides an abstraction layer for event detection and button mapping. It creates an iterator that can be used with a\u0026nbsp;\u003ccode\u003efor\u003c/code\u003e loop to get input change events without having to worry about button mappings or the specifics of low-level USB protocols.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003e\u003ccode\u003eCIRCUITPY/usb_descriptor.py\u003c/code\u003e\u003c/strong\u003e:\n\u003cul\u003e\n\u003cli\u003eThe \u003cstrong\u003e\u003ccode\u003eDescriptor\u003c/code\u003e\u003c/strong\u003e class provides parsing for USB device descriptors and configuration descriptors. The descriptor information makes it possible to identify gamepads either by vendor and product IDs, or by looking at details of interfaces (e.g. to identify different types of HID device).\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n      ","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eCIRCUITPY/code.py\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://github.com/samblenny/fruit-jam-gamepad-tester/blob/v0.2.1/code.py"},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eCIRCUITPY/gamepad.py\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://github.com/samblenny/fruit-jam-gamepad-tester/blob/v0.2.1/gamepad.py"},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eCIRCUITPY/usb_descriptor.py\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://github.com/samblenny/fruit-jam-gamepad-tester/blob/v0.2.1/usb_descriptor.py"},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eUsage\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003eConnect your board's DVI output into an HDMI monitor or USB capture card (EVGA XR1 Lite works)\u003c/li\u003e\n\u003cli\u003eConnect your board to a computer over USB and use a serial monitor to connect to the board's USB serial port.\u003c/li\u003e\n\u003cli\u003eConnect a USB input device to one of your board's USB host ports and mash some buttons.\u003c/li\u003e\n\u003cli\u003eUnplug the device. If the board doesn't show the \"Scanning USB bus...\" message, it may not have noticed the unplug event. In that case, press Button 1 (or the boot button) to begin a new scan.\u003c/li\u003e\n\u003c/ol\u003e\n      \n\n\n\n\n\n","metadata":{}}]