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:

Revision:
4:05f4ace9508a
Child:
6:29a04fe27b5e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/USBGamepad.cpp	Wed Dec 14 00:49:38 2016 +0000
@@ -0,0 +1,117 @@
+/* Copyright (c) 2010-2011 mbed.org, MIT License
+* Modified Mouse code for Joystick - WH 2012
+* Modified for RetroPie MW 2016
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+* and associated documentation files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+* Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or
+* substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+ 
+#include "stdint.h"
+#include "USBGamepad.h"
+
+
+
+bool USBGamepad::update(uint32_t buttons) 
+{
+   _buttonsLo = (uint16_t)(buttons & 0xffff);
+   _buttonsHi = (uint16_t)((buttons >> 16) & 0xffff);
+   return update();
+}
+ 
+bool USBGamepad::update() 
+{
+   HID_REPORT report;
+
+   // Fill the report according to the Gamepad Descriptor
+#define put(idx, val) (report.data[idx] = (val) & 0xff, report.data[(idx)+1] = ((val) >> 8) & 0xff)
+   put(0, _buttonsLo); // 0..1 2 bytes
+   put(2, _buttonsHi);//  2..3 2 bytes, 32 buttons
+   
+   // important: keep reportLen in sync with the actual byte length of
+   // the reports we build here
+   report.length = REPORT_LEN;
+ 
+   // send the report
+   return sendNB(&report);
+}
+
+bool USBGamepad::buttons(uint32_t buttons) 
+{
+     _buttonsLo = (uint16_t)(buttons & 0xffff);
+     _buttonsHi = (uint16_t)((buttons >> 16) & 0xffff);
+     return update();
+}
+
+ 
+void USBGamepad::_init() {
+ 
+   _buttonsLo = 0x0000; // 16 buttons
+   _buttonsHi = 0x0000; // 16 buttons
+   
+}
+ 
+ 
+uint8_t * USBGamepad::reportDesc() 
+{    
+    // Descriptor generated by USBHID Descriptor tool
+    static uint8_t reportDescriptor[] = {
+        0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
+        0x15, 0x00,                    // LOGICAL_MINIMUM (0)
+        0x09, 0x05,                    // USAGE (Game Pad)
+        0xa1, 0x01,                    // COLLECTION (Application)
+        0x05, 0x09,                    //   USAGE_PAGE (Button)
+        0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
+        0x29, 0x20,                    //   USAGE_MAXIMUM (Button 32)
+        0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
+        0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
+        0x75, 0x01,                    //   REPORT_SIZE (1)
+        0x95, 0x20,                    //   REPORT_COUNT (32)
+        0x55, 0x00,                    //   UNIT_EXPONENT (0)
+        0x65, 0x00,                    //   UNIT (None)
+        0x81, 0x02,                    //   INPUT (Data,Var,Abs)
+        0xc0                           // END_COLLECTION
+    };
+    
+      reportLength = sizeof(reportDescriptor);
+      return reportDescriptor;
+}
+ 
+ uint8_t * USBGamepad::stringImanufacturerDesc() {
+    static uint8_t stringImanufacturerDescriptor[] = {
+        0x14,                                            /*bLength*/
+        STRING_DESCRIPTOR,                               /*bDescriptorType 0x03*/
+        't',0,'h',0,'e',0,'t',0,'a',0,'z',0,'z',0,'b',0,'t',0        /*bString iManufacturer - mjrcorp*/
+    };
+    return stringImanufacturerDescriptor;
+}
+
+uint8_t * USBGamepad::stringIserialDesc() {
+    static uint8_t stringIserialDescriptor[] = {
+        0x16,                                                           /*bLength*/
+        STRING_DESCRIPTOR,                                              /*bDescriptorType 0x03*/
+        '0',0,'1',0,'2',0,'3',0,'4',0,'5',0,'6',0,'7',0,'8',0,'9',0,    /*bString iSerial - 0123456789*/
+    };
+    return stringIserialDescriptor;
+}
+
+uint8_t * USBGamepad::stringIproductDesc() {
+    static uint8_t stringIproductDescriptor[] = {
+        0x1E,                                                       /*bLength*/
+        STRING_DESCRIPTOR,                                          /*bDescriptorType 0x03*/
+        'A',0,'r',0,'c',0,'a',0,'d',0,'e',0,' ',0,'G',0,
+        'a',0,'m',0,'e',0,'p',0,'a',0,'d',0                         /*String iProduct */
+    };
+    return stringIproductDescriptor;
+}