[{"element_type":"markdown","content":"\u003cp\u003eThis demonstrates a visual USB gamepad tester using using an Adafruit\nUSB Host FeatherWing, an ESP32-S3 TFT Feather, and sprites created\nin Krita.\u003c/p\u003e\n","metadata":{"markdown":"This demonstrates a visual USB gamepad tester using using an Adafruit\nUSB Host FeatherWing, an ESP32-S3 TFT Feather, and sprites created\nin Krita."}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/103/original/feather-tft-gamepad.jpeg?1724028160","metadata":{"caption":""}},{"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  \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\u003eVideo Demo\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\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://youtu.be/pUZS2N6Ij_4?si=Arn4VJhXm4HyQ3a5"},{"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  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eSpritesheet\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\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":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/104/original/8x-combo-spritesheet.png?1724028315","metadata":{"caption":""}},{"element_type":"markdown","content":"\u003ch2\u003eHardware\u003c/h2\u003e\n\n\u003ch3\u003eParts\u003c/h3\u003e\n","metadata":{"markdown":"## Hardware\n\n\n### Parts\n"}},{"element_type":"parts","content":"\n  \u003cdiv class=\"parts-details\"\u003e\n    \u003cdiv\u003e\n      \u003cspan class=\"parts-quantity\"\u003e1\u003c/span\u003e\u003cspan\u003e x \u003c/span\u003e\n        \u003ca href=\"https://www.8bitdo.com/sn30-pro-usb-gamepad/\" class=\"parts-name\" target=\"_blank\"\u003e8BitDo SN30 Pro USB gamepad\u003c/a\u003e\n      \u003cdiv class=\"parts-description\"\u003eUSB wired gamepad\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"parts-action\"\u003e\n  \u003cdiv\u003e\n      \u003ca href=\"https://www.8bitdo.com/sn30-pro-usb-gamepad/\" class=\"parts-url btn-secondary\" target=\"_blank\"\u003eBuy Now\u003c/a\u003e\n\n  \u003c/div\u003e\n\u003c/div\u003e\n\n  \u003cdiv class=\"clearfix\"\u003e\u003c/div\u003e\n","metadata":{"name":"8BitDo SN30 Pro USB gamepad","url":"https://www.8bitdo.com/sn30-pro-usb-gamepad/","description":"USB wired gamepad","quantity":"1"}},{"element_type":"product","content":"https://www.adafruit.com/product/5483","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5858","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/2890","metadata":{}},{"element_type":"parts","content":"\n  \u003cdiv class=\"parts-details\"\u003e\n    \u003cdiv\u003e\n      \u003cspan class=\"parts-quantity\"\u003e1\u003c/span\u003e\u003cspan\u003e x \u003c/span\u003e\n        \u003cspan class=\"parts-name\"\u003eTamiya Universal Plate Set #70157\u003c/span\u003e\n      \u003cdiv class=\"parts-description\"\u003e3mm thick, 160x60mm ABS plates with 3mm holes on 5mm grid\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"parts-action\"\u003e\n  \u003cdiv\u003e\n\n  \u003c/div\u003e\n\u003c/div\u003e\n\n  \u003cdiv class=\"clearfix\"\u003e\u003c/div\u003e\n","metadata":{"name":"Tamiya Universal Plate Set #70157","url":null,"description":"3mm thick, 160x60mm ABS plates with 3mm holes on 5mm grid","quantity":"1"}},{"element_type":"parts","content":"\n  \u003cdiv class=\"parts-details\"\u003e\n    \u003cdiv\u003e\n      \u003cspan class=\"parts-quantity\"\u003e1\u003c/span\u003e\u003cspan\u003e x \u003c/span\u003e\n        \u003cspan class=\"parts-name\"\u003eM2.5 Nylon Standoff Set\u003c/span\u003e\n      \u003cdiv class=\"parts-description\"\u003emisc. M2.5 machine screws, standoffs, and nuts\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"parts-action\"\u003e\n  \u003cdiv\u003e\n\n  \u003c/div\u003e\n\u003c/div\u003e\n\n  \u003cdiv class=\"clearfix\"\u003e\u003c/div\u003e\n","metadata":{"name":"M2.5 Nylon Standoff Set","url":null,"description":"misc. M2.5 machine screws, standoffs, and nuts","quantity":"1"}},{"element_type":"markdown","content":"\u003ch3\u003eTools and Consumables\u003c/h3\u003e\n\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003eSoldering iron\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eSolder\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eBreadboard (Adafruit \u003ca href=\"https://www.adafruit.com/product/65\"\u003e#65\u003c/a\u003e,\n\u003ca href=\"https://www.adafruit.com/product/239\"\u003e#239\u003c/a\u003e,\nor similar)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eSoldering Vise (Adafruit \u003ca href=\"https://www.adafruit.com/product/3197\"\u003e#3197\u003c/a\u003e or\nsimilar)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eFlush diagonal cutters\n(Adafruit \u003ca href=\"https://www.adafruit.com/product/152\"\u003e#152\u003c/a\u003e or similar)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eAdhesive tape with clean-removable adhesive (Kapton tape, 3M Scotch 35\nelectrical tape, blue painter's tape, or whatever)\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n","metadata":{"markdown":"### Tools and Consumables\n\n- Soldering iron\n\n- Solder\n\n- Breadboard (Adafruit [#65](https://www.adafruit.com/product/65),\n  [#239](https://www.adafruit.com/product/239),\n  or similar)\n\n- Soldering Vise (Adafruit [#3197](https://www.adafruit.com/product/3197) or\n  similar)\n\n- Flush diagonal cutters\n  (Adafruit [#152](https://www.adafruit.com/product/152) or similar)\n\n- Adhesive tape with clean-removable adhesive (Kapton tape, 3M Scotch 35\n  electrical tape, blue painter's tape, or whatever)\n"}},{"element_type":"markdown","content":"\u003ch3\u003ePinouts\u003c/h3\u003e\n\n\u003ctable\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003cth\u003eTFT feather\u003c/th\u003e\n\u003cth\u003eUSB Host\u003c/th\u003e\n\u003cth\u003eST7789 TFT\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eSCK\u003c/td\u003e\n\u003ctd\u003eSCK\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eMOSI\u003c/td\u003e\n\u003ctd\u003eMOSI\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eMISO\u003c/td\u003e\n\u003ctd\u003eMISO\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD9\u003c/td\u003e\n\u003ctd\u003eIRQ\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD10\u003c/td\u003e\n\u003ctd\u003eCS\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTFT_CS\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003eCS\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTFT_DC\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003eDC\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2\u003eAssemble the Hardware\u003c/h2\u003e\n\n\u003cp\u003eIf you are unfamiliar with soldering headers, you might want to read:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e\u003ca href=\"https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools\"\u003eAdafruit Guide To Excellent Soldering\u003c/a\u003e\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e\u003ca href=\"https://learn.adafruit.com/how-to-solder-headers\"\u003eHow To Solder Headers\u003c/a\u003e\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3\u003eOrder of Soldering\u003c/h3\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eThe TFT Feather and USB Host Featherwing each come with two strips of\n16-position male header. Since feather boards have 16 holes on one side and\n12 holes on the other, use your flush cutters to trim 4 pins off the header\nstrips for the 12-hole sides.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eAssemble the USB Host Featherwing with pin headers on a breadboard, then\nsolder the headers in place.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eLocate the set of regular (non-stacking) female headers from your\nFeatherwing Doubler kit. Remove the USB host featherwing from\nthe breadboard, then put female headers onto the pins of the USB\nhost featherwing.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eUsing the USB host featherwing to hold the female headers in place, put the\nfemale header pins into one of the silkscreened Feather footprints of the\nDoubler. Tape the ends of the USB host featherwing to the Doubler, being\ncareful not to cover any of the pins.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eClamp the Doubler in a vise and solder the female headers in place.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eLocate the set of female stacking headers from your Doubler kit. Remove the\nUSB host featherwing from the Doubler, then put the stacking headers onto\nthe pins of the USB host board.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003ePut the stacking header pins into the other silkscreen footprint of your\nDoubler board, then prepare the assembly as before with tape and a vise.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eSolder the stacking header pins in place, then trim the excess length off\nthe pins with your flush cutters.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eCarefully assemble your ESP32-S3 TFT Feather with header pins on a\nbreadboard. Leave the protective film in place to protect the display from\nflux splatter. Solder the header pins in place. You can use the solder wire\nto bend the pull tab of the protective film out of the way so it does not\ntouch your soldering iron.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch3\u003eSmoke Test and Final Assembly\u003c/h3\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003e(optional) Mount your Doubler board on a backplate (such as a Tamiya\nUniversal Plate) so the board is easier to handle without shorts or static\ndischarges.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eAssemble the TFT feather and USB host featherwing onto the Doubler.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eTry plugging your board into a USB charger to make sure the LEDs light up.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eIf the LEDs light up, unplug the USB power cable, then plug the USB gamepad\ninto the Host featherwing's USB A port.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n","metadata":{"markdown":"### Pinouts\n\n| TFT feather | USB Host | ST7789 TFT |\n| ----------- | -------- | ---------- |\n|  SCK        |  SCK     |            |\n|  MOSI       |  MOSI    |            |\n|  MISO       |  MISO    |            |\n|  D9         |  IRQ     |            |\n|  D10        |  CS      |            |\n|  TFT_CS     |          |  CS        |\n|  TFT_DC     |          |  DC        |\n\n\n\n## Assemble the Hardware\n\nIf you are unfamiliar with soldering headers, you might want to read:\n\n- [Adafruit Guide To Excellent Soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools)\n\n- [How To Solder Headers](https://learn.adafruit.com/how-to-solder-headers)\n\n\n### Order of Soldering\n\n1. The TFT Feather and USB Host Featherwing each come with two strips of\n   16-position male header. Since feather boards have 16 holes on one side and\n   12 holes on the other, use your flush cutters to trim 4 pins off the header\n   strips for the 12-hole sides.\n\n2. Assemble the USB Host Featherwing with pin headers on a breadboard, then\n   solder the headers in place.\n\n3. Locate the set of regular (non-stacking) female headers from your\n   Featherwing Doubler kit. Remove the USB host featherwing from\n   the breadboard, then put female headers onto the pins of the USB\n   host featherwing.\n\n4. Using the USB host featherwing to hold the female headers in place, put the\n   female header pins into one of the silkscreened Feather footprints of the\n   Doubler. Tape the ends of the USB host featherwing to the Doubler, being\n   careful not to cover any of the pins.\n\n5. Clamp the Doubler in a vise and solder the female headers in place.\n\n6. Locate the set of female stacking headers from your Doubler kit. Remove the\n   USB host featherwing from the Doubler, then put the stacking headers onto\n   the pins of the USB host board.\n\n7. Put the stacking header pins into the other silkscreen footprint of your\n   Doubler board, then prepare the assembly as before with tape and a vise.\n\n8. Solder the stacking header pins in place, then trim the excess length off\n   the pins with your flush cutters.\n\n9. Carefully assemble your ESP32-S3 TFT Feather with header pins on a\n   breadboard. Leave the protective film in place to protect the display from\n   flux splatter. Solder the header pins in place. You can use the solder wire\n   to bend the pull tab of the protective film out of the way so it does not\n   touch your soldering iron.\n\n\n### Smoke Test and Final Assembly\n\n1. (optional) Mount your Doubler board on a backplate (such as a Tamiya\n   Universal Plate) so the board is easier to handle without shorts or static\n   discharges.\n\n2. Assemble the TFT feather and USB host featherwing onto the Doubler.\n\n3. Try plugging your board into a USB charger to make sure the LEDs light up.\n\n4. If the LEDs light up, unplug the USB power cable, then plug the USB gamepad\n   into the Host featherwing's USB A port.\n\n"}},{"element_type":"markdown","content":"\u003ch2\u003eUpdating CircuitPython\u003c/h2\u003e\n\n\u003cp\u003e\u003cstrong\u003eNOTE: To update CircuitPython on the ESP32-S3 TFT Feather with 2MB PSRAM and\n4MB Flash, you need to use the .BIN file (combination bootloader and\nCircuitPython core)\u003c/strong\u003e\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eDownload the CircuitPython 9.1.1 \u003cstrong\u003e.BIN\u003c/strong\u003e file from the\n\u003ca href=\"https://circuitpython.org/board/adafruit_feather_esp32s3_tft/\"\u003eFeather ESP32-S3 TFT PSRAM\u003c/a\u003e\npage on circuitpython.org\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eFollow the instructions in the\n\u003ca href=\"https://learn.adafruit.com/circuitpython-with-esp32-quick-start/web-serial-esptool\"\u003eWeb Serial ESPTool\u003c/a\u003e\nsection of the \"CircuitPython on ESP32 Quick Start\" learn guide to update\nyour board with CircuitPython 9.1.1. First erasing the board's contents,\nthen programming it with the .BIN file. \u003cstrong\u003e(CAUTION: the normal UF2 file\nmethod does not work on this board because it does not have a large enough\nflash drive to hold the CircuitPython UF2 file)\u003c/strong\u003e\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n","metadata":{"markdown":"## Updating CircuitPython\n\n**NOTE: To update CircuitPython on the ESP32-S3 TFT Feather with 2MB PSRAM and\n4MB Flash, you need to use the .BIN file (combination bootloader and\nCircuitPython core)**\n\n1. Download the CircuitPython 9.1.1 **.BIN** file from the\n   [Feather ESP32-S3 TFT PSRAM](https://circuitpython.org/board/adafruit_feather_esp32s3_tft/)\n   page on circuitpython.org\n\n2. Follow the instructions in the\n   [Web Serial ESPTool](https://learn.adafruit.com/circuitpython-with-esp32-quick-start/web-serial-esptool)\n   section of the \"CircuitPython on ESP32 Quick Start\" learn guide to update\n   your board with CircuitPython 9.1.1. First erasing the board's contents,\n   then programming it with the .BIN file. **(CAUTION: the normal UF2 file\n   method does not work on this board because it does not have a large enough\n   flash drive to hold the CircuitPython UF2 file)**\n"}},{"element_type":"markdown","content":"\u003ch2\u003eInstalling CircuitPython Code\u003c/h2\u003e\n\n\u003cp\u003eTo copy the project bundle files to your CIRCUITPY drive:\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eDownload the project bundle .zip file using the Download Project Bundle button\nbelow.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eExpand the zip file by opening it, or use \u003ccode class=\"inline\"\u003eunzip\u003c/code\u003e in a Terminal. You should\nend up with a folder named prox-sensor-encoder-menu, which should contain a\n\u003ccode class=\"inline\"\u003eREADME.txt\u003c/code\u003e file and a \u003ccode class=\"inline\"\u003eCircuitPython 9.x\u003c/code\u003e folder.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eOpen the CircuitPython 9.x folder and copy all of its contents to your\nCIRCUITPY drive.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp\u003eTo learn more about copying libraries to your CIRCUITPY drive, check out the\n\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries\"\u003eCircuitPython Libraries\u003c/a\u003e\nsection of the\n\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython\"\u003eWelcome to CircuitPython!\u003c/a\u003e\nlearn guide.\u003c/p\u003e\n","metadata":{"markdown":"## Installing CircuitPython Code\n\nTo copy the project bundle files to your CIRCUITPY drive:\n\n1. Download the project bundle .zip file using the Download Project Bundle button\n   below.\n\n2. Expand the zip file by opening it, or use `unzip` in a Terminal. You should\n   end up with a folder named prox-sensor-encoder-menu, which should contain a\n   `README.txt` file and a `CircuitPython 9.x` folder.\n\n3. Open the CircuitPython 9.x folder and copy all of its contents to your\n   CIRCUITPY drive.\n\nTo learn more about copying libraries to your CIRCUITPY drive, check out the\n[CircuitPython Libraries](https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries)\nsection of the\n[Welcome to CircuitPython!](https://learn.adafruit.com/welcome-to-circuitpython)\nlearn guide."}},{"element_type":"button","content":"https://github.com/samblenny/feather-tft-gamepad/releases/download/v0.1.0/feather-tft-gamepad-b6a5eb6.zip","metadata":{"class":"btn btn-large btn-block btn-primary","button_type":"primary","target":"_blank","zip_folder":"false"}},{"element_type":"markdown","content":"\u003ch2\u003eRunning the Code\u003c/h2\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eConnect the USB gamepad to the MAX3421E USB Host Featherwing before applying\npower to the Feather TFT. (USB hot plugging may be unreliable)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003ePlug a computer or charger into the Feather TFT ESP32-S3 USB C port. You\nshould see some boot messages followed by a pixel art gamepad with gray\nbuttons.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eWhen you press buttons on the USB gamepad, you should see the corresponding\nbutton sprites on the Feather TFT change from gray to white.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp\u003e\u003cem\u003eCAUTION: This code was tested with an 8BitDo SN30 Pro USB wired gamepad, which\nuses the XInput protocol and identifies itself with the vendor and product IDs\nof an Xbox 360 gamepad (045e:028e). This code may not work properly with other\ngamepads.\u003c/em\u003e\u003c/p\u003e\n\n\u003cp\u003eIf you want to see debug messages about gamepad connection status, you can\n\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython/kattni-connecting-to-the-serial-console\"\u003econnect to the serial console\u003c/a\u003e\nthe Feather TFT's serial port.\u003c/p\u003e\n","metadata":{"markdown":"## Running the Code\n\n1. Connect the USB gamepad to the MAX3421E USB Host Featherwing before applying\n   power to the Feather TFT. (USB hot plugging may be unreliable)\n\n2. Plug a computer or charger into the Feather TFT ESP32-S3 USB C port. You\n   should see some boot messages followed by a pixel art gamepad with gray\n   buttons.\n\n3. When you press buttons on the USB gamepad, you should see the corresponding\n   button sprites on the Feather TFT change from gray to white.\n\n*CAUTION: This code was tested with an 8BitDo SN30 Pro USB wired gamepad, which\nuses the XInput protocol and identifies itself with the vendor and product IDs\nof an Xbox 360 gamepad (045e:028e). This code may not work properly with other\ngamepads.*\n\nIf you want to see debug messages about gamepad connection status, you can\n[connect to the serial console](https://learn.adafruit.com/welcome-to-circuitpython/kattni-connecting-to-the-serial-console)\nthe Feather TFT's serial port.\n\n"}},{"element_type":"markdown","content":"\u003ch2\u003eUnderstanding How the Code Works\u003c/h2\u003e\n\n\u003cp\u003eThe \u003ccode class=\"inline\"\u003emain()\u003c/code\u003e function of \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e is divided into three sections:\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eInitialize the TFT display's ST7789 controller and set up a\n\u003ccode class=\"inline\"\u003edisplayio.TileGrid\u003c/code\u003e object to draw the gamepad image using sprites.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eInitialize the MAX3421E USB host controller.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eStart the \u003ccode class=\"inline\"\u003ewhile True: ...\u003c/code\u003e event loop that connects to a USB gamepad,\nreads input events from the gamepad, and updates the display as needed.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp\u003eFor details on how the gamepad driver works, take a look at \u003ccode class=\"inline\"\u003egamepad.py\u003c/code\u003e.\u003c/p\u003e\n\n\u003ch3\u003eImportant Concept #1: Bitfields and Digital Logic\u003c/h3\u003e\n\n\u003cp\u003eThe method used by the \u003ccode class=\"inline\"\u003eupdate_GUI(scene, prev, buttons)\u003c/code\u003e function of \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e\nto decode button presses might be a bit mysterious if you are unfamiliar with\nthe concepts of \u003ca href=\"https://en.wikipedia.org/wiki/Bit_field\"\u003ebitfields\u003c/a\u003e and\n\u003ca href=\"https://learn.adafruit.com/binary-boolean-and-logic?view=all\"\u003edigital logic\u003c/a\u003e\u003c/p\u003e\n\n\u003cp\u003eThere's a whole lot of background material you could read about if you\nwanted to. But, short version, understanding a few simple techniques makes it\npossible to work with bitfields efficiently:\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eWhat is a bitfield? Suppose you have 8 buttons that can each have a value\nof pressed (1) or not pressed (0). You could store each value as its own\ninteger, but that would use many more bytes than necessary. To save\nspace, you could instead treat one 8-bit integer as a bitfield, assigning\neach of its eight bits to represent a different button.\u003c/p\u003e\n\n\u003cp\u003eFor example, maybe button A is the least significant bit. When only\nbutton A is pressed, the integer's value would be 1 (decimal), 0x01\n(hex), or 0b00000001 (binary). Maybe you assign button B to the second\nleast significant bit. In that case, if only button B is pressed, the\ninteger's value would be 2 (decimal), 0x02 (hex), or 0b00000010 (binary).\nIf you pressed buttons A and B at the same time, the value would be 3,\n0x03, or 0b00000011.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eTo detect when one or more of the bits in a bitfield have changed, you can\nwrite a loop to track the previous and current values of a bitfield, then\ncompare the previous and current values in an if statement, like this:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eprev = thing.get_bitfield()\nwhile True:\n    bits = thing.get_bitfield()\n    if prev != bits:    # check if any bits changed\n        do_stuff(bits)  #   do what needs doing when a bit has changed\n        prev = bits     #   update the previous value\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eA variation on this technique is used by \u003ccode class=\"inline\"\u003egamepad.XInputGamepad.poll()\u003c/code\u003e to\ncalculate the value that it returns for \u003ccode class=\"inline\"\u003echanged\u003c/code\u003e.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eOnce you know that one or more bits have changed, you can efficiently\ndecode the bitfield using the XOR (\u003ccode class=\"inline\"\u003e^\u003c/code\u003e in python) and AND (\u003ccode class=\"inline\"\u003e\u0026amp;\u003c/code\u003e in Python)\nbitwise digital logic operators.\u003c/p\u003e\n\n\u003cp\u003eIf you want to understand why this works, you could try reading about\ndigital logic and experimenting in a Python REPL. For the how part,\nyou can use code like this to identify which bits changed and then do\nsome appropriate action based on the current value:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e def update_GUI(scene, prev, buttons):\n     # These are bitmask constants mapping buttons to their\n     # corresponding bit (each of these is a 16-bit integer where\n     # only 1 bit is set)\n     B = 0x1000\n     A = 0x2000\n     Y = 0x4000\n     X = 0x8000\n\n     # Use XOR (^) to identify the specific bits that changed\n     #   (try `\"0b{:03b}\".format(0b111 ^ 0b101)` in a Python REPL)\n     diff = prev ^ buttons\n\n     # Use AND (\u0026amp;) to calculate a boolean value that is True when a\n     # specific button has changed (`diff \u0026amp; A`, `diff \u0026amp; B`, etc), or\n     # when a button is currently pressed (`buttons \u0026amp; A`,\n     # `buttons \u0026amp; B`, etc)\n     if diff \u0026amp; A:\n         scene[8, 2] = 15 if (buttons \u0026amp; A) else 17\n     if diff \u0026amp; B:\n         scene[7, 3] = 15 if (buttons \u0026amp; B) else 17\n     if diff \u0026amp; X:\n         scene[7, 1] = 15 if (buttons \u0026amp; X) else 17\n     if diff \u0026amp; Y:\n         scene[6, 2] = 15 if (buttons \u0026amp; Y) else 17\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n","metadata":{"markdown":"## Understanding How the Code Works\n\nThe `main()` function of `code.py` is divided into three sections:\n\n1. Initialize the TFT display's ST7789 controller and set up a\n   `displayio.TileGrid` object to draw the gamepad image using sprites.\n\n2. Initialize the MAX3421E USB host controller.\n\n3. Start the `while True: ...` event loop that connects to a USB gamepad,\n   reads input events from the gamepad, and updates the display as needed.\n\nFor details on how the gamepad driver works, take a look at `gamepad.py`.\n\n\n### Important Concept #1: Bitfields and Digital Logic\n\nThe method used by the `update_GUI(scene, prev, buttons)` function of `code.py`\nto decode button presses might be a bit mysterious if you are unfamiliar with\nthe concepts of [bitfields](https://en.wikipedia.org/wiki/Bit_field) and\n[digital logic](https://learn.adafruit.com/binary-boolean-and-logic?view=all)\n\nThere's a whole lot of background material you could read about if you\nwanted to. But, short version, understanding a few simple techniques makes it\npossible to work with bitfields efficiently:\n\n1. What is a bitfield? Suppose you have 8 buttons that can each have a value\n   of pressed (1) or not pressed (0). You could store each value as its own\n   integer, but that would use many more bytes than necessary. To save\n   space, you could instead treat one 8-bit integer as a bitfield, assigning\n   each of its eight bits to represent a different button.\n   \n    For example, maybe button A is the least significant bit. When only\n    button A is pressed, the integer's value would be 1 (decimal), 0x01\n    (hex), or 0b00000001 (binary). Maybe you assign button B to the second\n    least significant bit. In that case, if only button B is pressed, the\n    integer's value would be 2 (decimal), 0x02 (hex), or 0b00000010 (binary).\n    If you pressed buttons A and B at the same time, the value would be 3,\n    0x03, or 0b00000011.\n\n2. To detect when one or more of the bits in a bitfield have changed, you can\n   write a loop to track the previous and current values of a bitfield, then\n   compare the previous and current values in an if statement, like this:\n\n    ```\n    prev = thing.get_bitfield()\n    while True:\n        bits = thing.get_bitfield()\n        if prev != bits:    # check if any bits changed\n            do_stuff(bits)  #   do what needs doing when a bit has changed\n            prev = bits     #   update the previous value\n    ```\n\n    A variation on this technique is used by `gamepad.XInputGamepad.poll()` to\n    calculate the value that it returns for `changed`.\n\n3. Once you know that one or more bits have changed, you can efficiently\n   decode the bitfield using the XOR (`^` in python) and AND (`\u0026amp;` in Python)\n   bitwise digital logic operators.\n\n    If you want to understand why this works, you could try reading about\n    digital logic and experimenting in a Python REPL. For the how part,\n    you can use code like this to identify which bits changed and then do\n    some appropriate action based on the current value:\n\n     ```\n     def update_GUI(scene, prev, buttons):\n         # These are bitmask constants mapping buttons to their\n         # corresponding bit (each of these is a 16-bit integer where\n         # only 1 bit is set)\n         B = 0x1000\n         A = 0x2000\n         Y = 0x4000\n         X = 0x8000\n\n         # Use XOR (^) to identify the specific bits that changed\n         #   (try `\"0b{:03b}\".format(0b111 ^ 0b101)` in a Python REPL)\n         diff = prev ^ buttons\n\n         # Use AND (\u0026amp;) to calculate a boolean value that is True when a\n         # specific button has changed (`diff \u0026amp; A`, `diff \u0026amp; B`, etc), or\n         # when a button is currently pressed (`buttons \u0026amp; A`,\n         # `buttons \u0026amp; B`, etc)\n         if diff \u0026amp; A:\n             scene[8, 2] = 15 if (buttons \u0026amp; A) else 17\n         if diff \u0026amp; B:\n             scene[7, 3] = 15 if (buttons \u0026amp; B) else 17\n         if diff \u0026amp; X:\n             scene[7, 1] = 15 if (buttons \u0026amp; X) else 17\n         if diff \u0026amp; Y:\n             scene[6, 2] = 15 if (buttons \u0026amp; Y) else 17\n     ```\n\n"}},{"element_type":"markdown","content":"\u003ch3\u003eImportant Concept #2: Sprite Sheets and TileGrid\u003c/h3\u003e\n\n\u003cp\u003eMany people have written a whole lot of stuff about using sprites to make\ngraphics for old video game consoles, emulators, fantasy consoles, and so\non. That stuff is\n\u003ca href=\"https://en.wikipedia.org/wiki/Sprite_(computer_graphics)\"\u003einteresting\u003c/a\u003e and\n\u003ca href=\"https://learn.adafruit.com/circuitpython-display-support-using-displayio/sprite-sheet\"\u003eworth\u003c/a\u003e\n\u003ca href=\"https://learn.adafruit.com/iot-twitter-listener-party-parrot/animating-sprites-with-circuitpython\"\u003ereading\u003c/a\u003e\nif you have the time. The short, drastically simplified version is there are\ntwo main approaches to drawing computer graphics:\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eMake images where you set colors for each display pixel individually\n(good for realistic looking images, but uses lots of RAM and clock cycles)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eMake images in the style of a mosaic, where you set groups of pixels at the\nsame time using patterns (sprites or tiles) from a spritesheet (works on\nhardware with limited RAM and clock speed)\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp\u003eThis image shows two different magnified views of the spritesheet I made in\n\u003ca href=\"https://krita.org/en/\"\u003eKrita\u003c/a\u003e for my gamepad tester:\u003c/p\u003e\n","metadata":{"markdown":"### Important Concept #2: Sprite Sheets and TileGrid\n\nMany people have written a whole lot of stuff about using sprites to make\ngraphics for old video game consoles, emulators, fantasy consoles, and so\non. That stuff is\n[interesting](https://en.wikipedia.org/wiki/Sprite_(computer_graphics)) and\n[worth](https://learn.adafruit.com/circuitpython-display-support-using-displayio/sprite-sheet)\n[reading](https://learn.adafruit.com/iot-twitter-listener-party-parrot/animating-sprites-with-circuitpython)\nif you have the time. The short, drastically simplified version is there are\ntwo main approaches to drawing computer graphics:\n\n1. Make images where you set colors for each display pixel individually\n   (good for realistic looking images, but uses lots of RAM and clock cycles)\n\n2. Make images in the style of a mosaic, where you set groups of pixels at the\n   same time using patterns (sprites or tiles) from a spritesheet (works on\n   hardware with limited RAM and clock speed)\n\nThis image shows two different magnified views of the spritesheet I made in\n[Krita](https://krita.org/en/) for my gamepad tester:\n"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/105/original/8x-combo-spritesheet.png?1724029135","metadata":{"caption":""}},{"element_type":"markdown","content":"\u003cp\u003eThe top left portion came from a Krita screenshot with 800% zoom and 8px grid\ndivisions. The spritesheet has 28 sprites arranged into 4 rows of 7 columns\neach. The sprites are 8 pixels wide by 8 pixels high. The bottom right\nportion shows the same spritesheet overlaid with numbers that match the\nnumbering system used by \u003ccode class=\"inline\"\u003edisplayio.TileGrid\u003c/code\u003e.\u003c/p\u003e\n\n\u003cp\u003eIn Krita, I didn't do anything special to manage the color palette other than just making a swatch in the\n\"Palette\" Docker (Krita jargon, not to be confused with the container management software). For the swatch, I used white, black, gray, and five shades of blue (same hue with gradient of values). For the brush, I used the \u003ca href=\"https://docs.krita.org/en/reference_manual/krita_4_preset_bundle.html\"\u003ebrush preset\u003c/a\u003e called \"\u003ca href=\"https://docs.krita.org/en/reference_manual/krita_4_preset_bundle.html#pixel\"\u003ePixel Art\u003c/a\u003e\" set to a size of 1px. This is what my Palette and Brush Preset dockers looked like:\u003c/p\u003e\n","metadata":{"markdown":"The top left portion came from a Krita screenshot with 800% zoom and 8px grid\ndivisions. The spritesheet has 28 sprites arranged into 4 rows of 7 columns\neach. The sprites are 8 pixels wide by 8 pixels high. The bottom right\nportion shows the same spritesheet overlaid with numbers that match the\nnumbering system used by `displayio.TileGrid`.\n\nIn Krita, I didn't do anything special to manage the color palette other than just making a swatch in the\n\"Palette\" Docker (Krita jargon, not to be confused with the container management software). For the swatch, I used white, black, gray, and five shades of blue (same hue with gradient of values). For the brush, I used the [brush preset](https://docs.krita.org/en/reference_manual/krita_4_preset_bundle.html) called \"[Pixel Art](https://docs.krita.org/en/reference_manual/krita_4_preset_bundle.html#pixel)\" set to a size of 1px. This is what my Palette and Brush Preset dockers looked like:\n"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/107/original/Krita-Pixel-Art-Brush.png?1724089249","metadata":{"caption":""}},{"element_type":"markdown","content":"\u003cp\u003eThese are the settings from my \"Grid and Guides\" docker that I used to make the 8px grid overlay:\u003c/p\u003e\n","metadata":{"markdown":"These are the settings from my \"Grid and Guides\" docker that I used to make the 8px grid overlay:"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/109/original/Krita-Grid-Settings.png?1724090075","metadata":{"caption":""}},{"element_type":"markdown","content":"\u003cp\u003eAfter exporting to PNG from Krita, I converted my spritesheet to BMP using the GraphicsMagick gm convert terminal shell command, like this:\u003c/p\u003e\n","metadata":{"markdown":"After exporting to PNG from Krita, I converted my spritesheet to BMP using the GraphicsMagick gm convert terminal shell command, like this:\n"}},{"element_type":"code","content":"gm convert gamepad_7x4_8px_sprites.png BMP3:sprites.bmp","metadata":{"language":"auto","linenums":false}},{"element_type":"markdown","content":"\u003cp\u003eThis is how I taught TileGrid to make a full gamepad image (5 rows tall by 10 columns wide) by assembling a grid of sprites:\u003c/p\u003e\n","metadata":{"markdown":"This is how I taught TileGrid to make a full gamepad image (5 rows tall by 10 columns wide) by assembling a grid of sprites:"}},{"element_type":"code","content":"# load spritesheet and palette\n(bitmap, palette) = adafruit_imageload.load(\"sprites.bmp\", bitmap=Bitmap,\n    palette=Palette)\n# assemble TileGrid with gamepad using sprites from the spritesheet\nscene = TileGrid(bitmap, pixel_shader=palette, width=10, height=5,\n    tile_width=8, tile_height=8, default_tile=9)\ntilemap = (\n    (0, 5, 2, 3, 3, 3, 3, 4, 5, 6),            # . L . . . . . . R .\n    (7, 9, 12, 9, 9, 9, 9, 17, 9, 13),         # . . dU. . . . X . .\n    (7, 18, 19, 20, 9, 9, 17, 9, 17, 13),      # . dL. dR. . Y . A .\n    (7, 9, 26, 9, 24, 25, 9, 17, 9, 13),       # . . dD. SeSt. B . .\n    (21, 23, 23, 23, 23, 23, 23, 23, 23, 27),  # . . . . . . . . . .\n)\nfor (y, row) in enumerate(tilemap):\n    for (x, sprite) in enumerate(row):\n        scene[x, y] = sprite\ngrp = Group(scale=3)  # 3x zoom\ngrp.append(scene)\ndisplay.root_group = grp","metadata":{"language":"python","linenums":false}},{"element_type":"markdown","content":"\u003cp\u003eFor animating buttons to change from gray to white, \u003ccode class=\"inline\"\u003eupdate_GUI()\u003c/code\u003e changes the\nTileGrid object called \u003ccode class=\"inline\"\u003escene\u003c/code\u003e by setting new sprite numbers at the x,y\ncoordinates for the various buttons.\u003c/p\u003e\n\n\u003cp\u003eFor example, this code excerpt from \u003ccode class=\"inline\"\u003eupdate_GUI()\u003c/code\u003e sets the tile at coordinate\n(8,2) to sprite 15 or sprite 17 depending on whether button A is pressed:\u003c/p\u003e\n","metadata":{"markdown":"For animating buttons to change from gray to white, `update_GUI()` changes the\nTileGrid object called `scene` by setting new sprite numbers at the x,y\ncoordinates for the various buttons.\n\nFor example, this code excerpt from `update_GUI()` sets the tile at coordinate\n(8,2) to sprite 15 or sprite 17 depending on whether button A is pressed:\n"}},{"element_type":"code","content":"diff = prev ^  buttons\nif diff \u0026 A:\n    scene[8, 2] = 15 if (buttons \u0026 A) else 17","metadata":{"language":"python","linenums":false}},{"element_type":"markdown","content":"\u003ch2\u003eCircuitPython Code\u003c/h2\u003e\n\n\u003ch3\u003eCIRCUITPY/code.py\u003c/h3\u003e\n","metadata":{"markdown":"## CircuitPython Code\n\n### CIRCUITPY/code.py"}},{"element_type":"embed","content":"https://github.com/samblenny/feather-tft-gamepad/blob/v0.1.0/code.py"},{"element_type":"markdown","content":"\u003ch3\u003eCIRCUITPY/gamepad.py\u003c/h3\u003e\n","metadata":{"markdown":"### CIRCUITPY/gamepad.py"}},{"element_type":"embed","content":"https://github.com/samblenny/feather-tft-gamepad/blob/v0.1.0/gamepad.py"}]