This is a work in progress. Trying to make a working arcade button to USB interface with the Nucleo F411RE.

Dependencies:   USBDevice mbed

This project was inspired by USB Joystick Device

The goal of this project is to build an simple interface that I will connect numerous Arcade buttons and joysticks to the Nucleo board, and the Nucleo will present itself as a USB Gamepad to Windows or Linux. The joysticks are Arcade joysticks that have four switches, up/down/left/right. It is not an analog joystick, just a set of switches. These will be connected to the Nucleo's pins configured as inputs.

The code will then continuously loop and test which buttons are closed, and transfer this data to the host via USBGamepad protocol.

Much thanks to Wim Huiskamp for his original project, documentation, and later reaching out to me to guide me to solving a last minute problem. Wim clued me into the fact that I needed a 1k5 pullup resistor between the 3v3 pin and the PA_12/D+ pin. Once I did this Windows detected the device as a Gamepad and registered it correctly! Yay!

Connecting USB cable to the board is as follows:

You will need a USB data cable (the one I used had a micro usb on one end and regular usb on the other). I cut off the micro USB end, and cut the insulation back about 30mm. This exposed four wires, Red, Black, White and Green. You will then either crimp some header connectors or solder directly to the Nucleo header pins as follows:

  • Green USB D+ to PA_12
  • White USB D- to PA_11
  • Red USB 5V to E5V (with jumper JP5 set to E5V)
  • Black USB GND to GND

As an extra debugging measure, you can connect both the ST/Link USB and the PA_12/11 USB to the Windows machine to run both at the same time, and you can see printf messages from within ST/Link.

We can verify the HID Vendor and Product IDs by looking at the Device Manager, and look for the HID Game Controller:

/media/uploads/thetazzbot/arcade_controller_hid.jpg

I used pid.codes to register my own Vendor_ID and Product_ID

If you go to the USB Game Controller control panel widget, you will see the new entry for Arcade Gamepad:

/media/uploads/thetazzbot/arcade_controller_controlpanel.jpg

And here we can see all 32 buttons:

/media/uploads/thetazzbot/arcade_controller_buttons.jpg

On the Nucleo board you may have difficulties depending on the revision. The board I am using is an STM32F411RE Revision C03, which has resistors and solder joints (bottom) to allow the use of the Crystal on the STLink board for USB purposes. After programming via STLink, remove the USB cable from the STLink, the jumper must be set to E5V to power the board from the PC's usb port. Plug the new cable into the PC.

When you're ready to install it in the arcade cabinet, or project, just remember to setup the jumper JP5 to E5V and you only need the single USB connection to the host.

Here are some useful links that I used to grasp all the little things involved in this project:

Committer:
thetazzbot
Date:
Wed Dec 14 15:30:55 2016 +0000
Revision:
6:29a04fe27b5e
Parent:
4:05f4ace9508a
Changed Report descriptor to implement a 2 player, 16 buttons each controller.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
thetazzbot 4:05f4ace9508a 1 /* Copyright (c) 2010-2011 mbed.org, MIT License
thetazzbot 4:05f4ace9508a 2 * Modified Mouse code for Joystick - WH 2012
thetazzbot 4:05f4ace9508a 3 * Modified for RetroPie MW 2016
thetazzbot 4:05f4ace9508a 4 *
thetazzbot 4:05f4ace9508a 5 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
thetazzbot 4:05f4ace9508a 6 * and associated documentation files (the "Software"), to deal in the Software without
thetazzbot 4:05f4ace9508a 7 * restriction, including without limitation the rights to use, copy, modify, merge, publish,
thetazzbot 4:05f4ace9508a 8 * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
thetazzbot 4:05f4ace9508a 9 * Software is furnished to do so, subject to the following conditions:
thetazzbot 4:05f4ace9508a 10 *
thetazzbot 4:05f4ace9508a 11 * The above copyright notice and this permission notice shall be included in all copies or
thetazzbot 4:05f4ace9508a 12 * substantial portions of the Software.
thetazzbot 4:05f4ace9508a 13 *
thetazzbot 4:05f4ace9508a 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
thetazzbot 4:05f4ace9508a 15 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
thetazzbot 4:05f4ace9508a 16 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
thetazzbot 4:05f4ace9508a 17 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
thetazzbot 4:05f4ace9508a 18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
thetazzbot 4:05f4ace9508a 19 */
thetazzbot 4:05f4ace9508a 20
thetazzbot 4:05f4ace9508a 21 #include "stdint.h"
thetazzbot 4:05f4ace9508a 22 #include "USBGamepad.h"
thetazzbot 4:05f4ace9508a 23
thetazzbot 4:05f4ace9508a 24
thetazzbot 4:05f4ace9508a 25
thetazzbot 4:05f4ace9508a 26 bool USBGamepad::update(uint32_t buttons)
thetazzbot 4:05f4ace9508a 27 {
thetazzbot 6:29a04fe27b5e 28 _buttonsP1 = (uint16_t)(buttons & 0xffff);
thetazzbot 6:29a04fe27b5e 29 _buttonsP2 = (uint16_t)((buttons >> 16) & 0xffff);
thetazzbot 4:05f4ace9508a 30 return update();
thetazzbot 4:05f4ace9508a 31 }
thetazzbot 4:05f4ace9508a 32
thetazzbot 4:05f4ace9508a 33 bool USBGamepad::update()
thetazzbot 4:05f4ace9508a 34 {
thetazzbot 4:05f4ace9508a 35 HID_REPORT report;
thetazzbot 4:05f4ace9508a 36
thetazzbot 4:05f4ace9508a 37 // Fill the report according to the Gamepad Descriptor
thetazzbot 6:29a04fe27b5e 38
thetazzbot 6:29a04fe27b5e 39 #define put(idx, val) (report.data[idx] = (val) & 0xff, report.data[(idx)+1] = ((val) >> 8) & 0xff)
thetazzbot 6:29a04fe27b5e 40
thetazzbot 6:29a04fe27b5e 41 report.data[0] = 0x01; //Report id 1
thetazzbot 6:29a04fe27b5e 42 put(1, _buttonsP1); // 0..1 2 bytes
thetazzbot 6:29a04fe27b5e 43
thetazzbot 6:29a04fe27b5e 44 send(&report);
thetazzbot 6:29a04fe27b5e 45
thetazzbot 6:29a04fe27b5e 46 report.data[0] = 0x02; //Report id 2
thetazzbot 6:29a04fe27b5e 47 put(1, _buttonsP1);
thetazzbot 6:29a04fe27b5e 48
thetazzbot 6:29a04fe27b5e 49 send(&report);
thetazzbot 6:29a04fe27b5e 50
thetazzbot 6:29a04fe27b5e 51 put(2, _buttonsP2);// 2..3 2 bytes, 32 buttons
thetazzbot 4:05f4ace9508a 52
thetazzbot 4:05f4ace9508a 53 // important: keep reportLen in sync with the actual byte length of
thetazzbot 4:05f4ace9508a 54 // the reports we build here
thetazzbot 4:05f4ace9508a 55 report.length = REPORT_LEN;
thetazzbot 4:05f4ace9508a 56
thetazzbot 4:05f4ace9508a 57 // send the report
thetazzbot 4:05f4ace9508a 58 return sendNB(&report);
thetazzbot 4:05f4ace9508a 59 }
thetazzbot 4:05f4ace9508a 60
thetazzbot 4:05f4ace9508a 61 bool USBGamepad::buttons(uint32_t buttons)
thetazzbot 4:05f4ace9508a 62 {
thetazzbot 6:29a04fe27b5e 63 _buttonsP1 = (uint16_t)(buttons & 0xffff);
thetazzbot 6:29a04fe27b5e 64 _buttonsP2 = (uint16_t)((buttons >> 16) & 0xffff);
thetazzbot 4:05f4ace9508a 65 return update();
thetazzbot 4:05f4ace9508a 66 }
thetazzbot 4:05f4ace9508a 67
thetazzbot 4:05f4ace9508a 68
thetazzbot 4:05f4ace9508a 69 void USBGamepad::_init() {
thetazzbot 4:05f4ace9508a 70
thetazzbot 6:29a04fe27b5e 71 _buttonsP1 = 0x0000; // 16 buttons
thetazzbot 6:29a04fe27b5e 72 _buttonsP2 = 0x0000; // 16 buttons
thetazzbot 4:05f4ace9508a 73
thetazzbot 4:05f4ace9508a 74 }
thetazzbot 4:05f4ace9508a 75
thetazzbot 4:05f4ace9508a 76
thetazzbot 4:05f4ace9508a 77 uint8_t * USBGamepad::reportDesc()
thetazzbot 4:05f4ace9508a 78 {
thetazzbot 6:29a04fe27b5e 79
thetazzbot 6:29a04fe27b5e 80 static const uint8_t reportDescriptor[27] = {
thetazzbot 6:29a04fe27b5e 81 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
thetazzbot 6:29a04fe27b5e 82 0x09, 0x05, // USAGE (Game Pad)
thetazzbot 6:29a04fe27b5e 83 0xa1, 0x01, // COLLECTION (Application)
thetazzbot 6:29a04fe27b5e 84 0xa1, 0x00, // COLLECTION (Physical)
thetazzbot 6:29a04fe27b5e 85 0x85, 0x01, // REPORT_ID (1)
thetazzbot 6:29a04fe27b5e 86 0x05, 0x09, // USAGE_PAGE (Button)
thetazzbot 6:29a04fe27b5e 87 0x19, 0x01, // USAGE_MINIMUM (Button 1)
thetazzbot 6:29a04fe27b5e 88 0x29, 0x10, // USAGE_MAXIMUM (Button 16)
thetazzbot 6:29a04fe27b5e 89 0x15, 0x00, // LOGICAL_MINIMUM (0)
thetazzbot 6:29a04fe27b5e 90 0x25, 0x01, // LOGICAL_MAXIMUM (1)
thetazzbot 6:29a04fe27b5e 91 0x75, 0x01, // REPORT_SIZE (1)
thetazzbot 6:29a04fe27b5e 92 0x95, 0x10, // REPORT_COUNT (16)
thetazzbot 6:29a04fe27b5e 93 0x81, 0x02, // INPUT (Data,Var,Abs)
thetazzbot 6:29a04fe27b5e 94 0xc0 // END_COLLECTION
thetazzbot 6:29a04fe27b5e 95 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
thetazzbot 6:29a04fe27b5e 96 0x09, 0x05, // USAGE (Game Pad)
thetazzbot 6:29a04fe27b5e 97 0xa1, 0x01, // COLLECTION (Application)
thetazzbot 6:29a04fe27b5e 98 0xa1, 0x00, // COLLECTION (Physical)
thetazzbot 6:29a04fe27b5e 99 0x85, 0x02, // REPORT_ID (2)
thetazzbot 6:29a04fe27b5e 100 0x05, 0x09, // USAGE_PAGE (Button)
thetazzbot 6:29a04fe27b5e 101 0x19, 0x01, // USAGE_MINIMUM (Button 1)
thetazzbot 6:29a04fe27b5e 102 0x29, 0x10, // USAGE_MAXIMUM (Button 16)
thetazzbot 6:29a04fe27b5e 103 0x15, 0x00, // LOGICAL_MINIMUM (0)
thetazzbot 6:29a04fe27b5e 104 0x25, 0x01, // LOGICAL_MAXIMUM (1)
thetazzbot 6:29a04fe27b5e 105 0x75, 0x01, // REPORT_SIZE (1)
thetazzbot 6:29a04fe27b5e 106 0x95, 0x10, // REPORT_COUNT (16)
thetazzbot 6:29a04fe27b5e 107 0x81, 0x02, // INPUT (Data,Var,Abs)
thetazzbot 6:29a04fe27b5e 108 0xc0 // END_COLLECTION
thetazzbot 6:29a04fe27b5e 109 };
thetazzbot 6:29a04fe27b5e 110
thetazzbot 4:05f4ace9508a 111 reportLength = sizeof(reportDescriptor);
thetazzbot 4:05f4ace9508a 112 return reportDescriptor;
thetazzbot 4:05f4ace9508a 113 }
thetazzbot 4:05f4ace9508a 114
thetazzbot 4:05f4ace9508a 115 uint8_t * USBGamepad::stringImanufacturerDesc() {
thetazzbot 4:05f4ace9508a 116 static uint8_t stringImanufacturerDescriptor[] = {
thetazzbot 6:29a04fe27b5e 117 0x15, /*bLength*/
thetazzbot 4:05f4ace9508a 118 STRING_DESCRIPTOR, /*bDescriptorType 0x03*/
thetazzbot 6:29a04fe27b5e 119 't',0,'h',0,'e',0,'t',0,'a',0,'z',0,'z',0,'b',0,'o',0,'t',0 /*bString iManufacturer - mjrcorp*/
thetazzbot 4:05f4ace9508a 120 };
thetazzbot 4:05f4ace9508a 121 return stringImanufacturerDescriptor;
thetazzbot 4:05f4ace9508a 122 }
thetazzbot 4:05f4ace9508a 123
thetazzbot 4:05f4ace9508a 124 uint8_t * USBGamepad::stringIserialDesc() {
thetazzbot 4:05f4ace9508a 125 static uint8_t stringIserialDescriptor[] = {
thetazzbot 4:05f4ace9508a 126 0x16, /*bLength*/
thetazzbot 4:05f4ace9508a 127 STRING_DESCRIPTOR, /*bDescriptorType 0x03*/
thetazzbot 4:05f4ace9508a 128 '0',0,'1',0,'2',0,'3',0,'4',0,'5',0,'6',0,'7',0,'8',0,'9',0, /*bString iSerial - 0123456789*/
thetazzbot 4:05f4ace9508a 129 };
thetazzbot 4:05f4ace9508a 130 return stringIserialDescriptor;
thetazzbot 4:05f4ace9508a 131 }
thetazzbot 4:05f4ace9508a 132
thetazzbot 4:05f4ace9508a 133 uint8_t * USBGamepad::stringIproductDesc() {
thetazzbot 4:05f4ace9508a 134 static uint8_t stringIproductDescriptor[] = {
thetazzbot 4:05f4ace9508a 135 0x1E, /*bLength*/
thetazzbot 4:05f4ace9508a 136 STRING_DESCRIPTOR, /*bDescriptorType 0x03*/
thetazzbot 4:05f4ace9508a 137 'A',0,'r',0,'c',0,'a',0,'d',0,'e',0,' ',0,'G',0,
thetazzbot 4:05f4ace9508a 138 'a',0,'m',0,'e',0,'p',0,'a',0,'d',0 /*String iProduct */
thetazzbot 4:05f4ace9508a 139 };
thetazzbot 4:05f4ace9508a 140 return stringIproductDescriptor;
thetazzbot 4:05f4ace9508a 141 }