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

Dependencies:   USBDevice mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 /* Modified for RetroPie MW 2016 */
00002 /* Copyright 2014 M J Roberts, MIT License
00003 *
00004 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
00005 * and associated documentation files (the "Software"), to deal in the Software without
00006 * restriction, including without limitation the rights to use, copy, modify, merge, publish,
00007 * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
00008 * Software is furnished to do so, subject to the following conditions:
00009 *
00010 * The above copyright notice and this permission notice shall be included in all copies or
00011 * substantial portions of the Software.
00012 *
00013 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
00014 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00015 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
00016 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00017 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00018 */
00019 
00020 //
00021 //  - Button input wiring.  24 of the KL25Z's GPIO ports are mapped as digital inputs
00022 //    for buttons and switches.  The software reports these as joystick buttons when
00023 //    it sends reports to the PC.  These can be used to wire physical pinball-style
00024 //    buttons in the cabinet (e.g., flipper buttons, the Start button) and miscellaneous 
00025 //    switches (such as a tilt bob) to the PC.  Visual Pinball can use joystick buttons
00026 //    for input - you just have to assign a VP function to each button using VP's
00027 //    keyboard options dialog.  To wire a button physically, connect one terminal of
00028 //    the button switch to the KL25Z ground, and connect the other terminal to the
00029 //    the GPIO port you wish to assign to the button.  See the buttonMap[] array
00030 //    below for the available GPIO ports and their assigned joystick button numbers.
00031 //    If you're not using a GPIO port, you can just leave it unconnected - the digital
00032 //    inputs have built-in pull-up resistors, so an unconnected port is the same as
00033 //    an open switch (an "off" state for the button).
00034 //
00035 //
00036 // STATUS LIGHTS:  The on-board LED on the KL25Z flashes to indicate the current 
00037 // device status.  The flash patterns are:
00038 //
00039 //    two short red flashes = the device is powered but hasn't successfully
00040 //        connected to the host via USB (either it's not physically connected
00041 //        to the USB port, or there was a problem with the software handshake
00042 //        with the USB device driver on the computer)
00043 //
00044 //    short red flash = the host computer is in sleep/suspend mode
00045 //
00046 //    long red/green = the LedWiz unti number has been changed, so a reset
00047 //        is needed.  You can simply unplug the device and plug it back in,
00048 //        or presss and hold the reset button on the device for a few seconds.
00049 //
00050 //    long yellow/green = everything's working, but the plunger hasn't
00051 //        been calibrated; follow the calibration procedure described above.
00052 //        This flash mode won't appear if the CCD has been disabled.  Note
00053 //        that the device can't tell whether a CCD is physically attached;
00054 //        if you don't have a CCD attached, you can set the appropriate option 
00055 //        in config.h or use the  Windows config tool to disable the CCD 
00056 //        software features.
00057 //
00058 //    alternating blue/green = everything's working
00059 //
00060 #include "mbed.h"
00061 #include "math.h"
00062 #include "USBGamepad.h"
00063 
00064 
00065 #define DECL_EXTERNS
00066 #include "config.h"
00067 
00068 
00069 // ---------------------------------------------------------------------------
00070 // utilities
00071 
00072 // number of elements in an array
00073 #define countof(x) (sizeof(x)/sizeof((x)[0]))
00074 
00075 // --------------------------------------------------------------------------
00076 // 
00077 //
00078 // Build the full USB product ID.  If we're using the LedWiz compatible
00079 // vendor ID, the full product ID is the combination of the LedWiz base
00080 // product ID (0x00F0) and the 0-based unit number (0-15).  If we're not
00081 // trying to be LedWiz compatible, we just use the exact product ID
00082 // specified in config.h.
00083 #define MAKE_USB_PRODUCT_ID(vid, pidbase, unit) \
00084     ((vid) == 0xFAFA && (pidbase) == 0x00F0 ? (pidbase) | (unit) : (pidbase))
00085 
00086 
00087 // --------------------------------------------------------------------------
00088 //
00089 // Joystick axis report range - we report from -JOYMAX to +JOYMAX
00090 //
00091 #define JOYMAX 4096
00092 
00093 
00094 // ---------------------------------------------------------------------------
00095 //
00096 // On-board RGB LED elements - we use these for diagnostic displays.
00097 //
00098 //TODO: FIXME for Nucleo
00099 DigitalOut ledR(LED1), ledG(LED2), ledB(LED3);
00100 
00101 // ---------------------------------------------------------------------------
00102 //
00103 // Button input
00104 //
00105 
00106 // button input map array
00107 
00108 DigitalIn *buttonDigIn[NUM_OF_BUTTONS];  // config.h
00109 
00110 // button state
00111 struct ButtonState
00112 {
00113     // current on/off state
00114     int pressed;
00115     
00116     // Sticky time remaining for current state.  When a
00117     // state transition occurs, we set this to a debounce
00118     // period.  Future state transitions will be ignored
00119     // until the debounce time elapses.
00120     int t;
00121 } buttonState[NUM_OF_BUTTONS];
00122 
00123 // timer for button reports
00124 static Timer buttonTimer;
00125 
00126 // initialize the button inputs
00127 void initButtons()
00128 {
00129     // create the digital inputs
00130     for (int i = 0 ; i < countof(buttonDigIn) ; ++i)
00131     {
00132         if (i < countof(buttonMap) && buttonMap[i] != NC)
00133             buttonDigIn[i] = new DigitalIn(buttonMap[i]);
00134         else
00135             buttonDigIn[i] = 0;
00136     }
00137     
00138     // start the button timer
00139     buttonTimer.start();
00140 }
00141 
00142 
00143 // read the button input state
00144 uint32_t readButtons()
00145 {
00146     // start with all buttons off
00147     uint32_t buttons = 0;
00148     
00149     // figure the time elapsed since the last scan
00150     int dt = buttonTimer.read_ms();
00151     
00152     // reset the timef for the next scan
00153     buttonTimer.reset();
00154     
00155     // scan the button list
00156     uint32_t bit = 1;
00157     DigitalIn **di = buttonDigIn;
00158     ButtonState *bs = buttonState;
00159     for (int i = 0 ; i < countof(buttonDigIn) ; ++i, ++di, ++bs, bit <<= 1)
00160     {
00161         // read this button
00162         if (*di != 0)
00163         {
00164             // deduct the elapsed time since the last update
00165             // from the button's remaining sticky time
00166             bs->t -= dt;
00167             if (bs->t < 0)
00168                 bs->t = 0;
00169             
00170             // If the sticky time has elapsed, note the new physical
00171             // state of the button.  If we still have sticky time
00172             // remaining, ignore the physical state; the last state
00173             // change persists until the sticky time elapses so that
00174             // we smooth out any "bounce" (electrical transients that
00175             // occur when the switch contact is opened or closed).
00176             if (bs->t == 0)
00177             {
00178                 // get the new physical state
00179                 int pressed = !(*di)->read();
00180                 
00181                 // update the button's logical state if this is a change
00182                 if (pressed != bs->pressed)
00183                 {
00184                     // store the new state
00185                     bs->pressed = pressed;
00186                     
00187                     // start a new sticky period for debouncing this
00188                     // state change
00189                     bs->t = 25;
00190                 }
00191             }
00192             
00193             // if it's pressed, OR its bit into the state
00194             if (bs->pressed)
00195                 buttons |= bit;
00196         }
00197     }
00198     
00199     // return the new button list
00200     return buttons;
00201 }
00202 
00203 // ---------------------------------------------------------------------------
00204 //
00205 // Customization joystick subbclass
00206 //
00207 
00208 class MyUSBGamepad: public USBGamepad
00209 {
00210 public:
00211     MyUSBGamepad(uint16_t vendor_id, uint16_t product_id, uint16_t product_release) 
00212         : USBGamepad(vendor_id, product_id, product_release, true)
00213     {
00214         suspended_ = false;
00215     }
00216     
00217     // are we connected?
00218     int isConnected()  { return configured(); }
00219     
00220     // Are we in suspend mode?
00221     int isSuspended() const { return suspended_; }
00222     
00223 protected:
00224     virtual void suspendStateChanged(unsigned int suspended)
00225         { suspended_ = suspended; }
00226 
00227     // are we suspended?
00228     int suspended_; 
00229 };
00230 
00231 
00232 
00233 // ---------------------------------------------------------------------------
00234 //
00235 // Main program loop.  This is invoked on startup and runs forever.  Our
00236 // main work is to read our devices, process
00237 // the readings into nudge and plunger position data, and send the results
00238 // to the host computer via the USB joystick interface.  
00239 //
00240 int main(void)
00241 {
00242     // turn off our on-board indicator LED
00243     ledR = 1;
00244     ledG = 1;
00245     ledB = 1;
00246     
00247     // we're not connected/awake yet
00248     bool connected = false;
00249     time_t connectChangeTime = time(0);
00250 
00251     // initialize the button input ports
00252     initButtons();
00253 
00254     // we don't need a reset yet
00255     bool needReset = false;
00256     // Create the joystick USB client.  
00257     MyUSBGamepad js(USB_VENDOR_ID,USB_PRODUCT_ID,USB_PRODUCT_VER); // vendor, product, product release
00258         
00259     // last report timer - we use this to throttle reports, since VP
00260     // doesn't want to hear from us more than about every 10ms
00261     Timer reportTimer;
00262     reportTimer.start();
00263 
00264     // set up a timer for our heartbeat indicator
00265     Timer hbTimer;
00266     hbTimer.start();
00267     int hb = 0;
00268     uint16_t hbcnt = 0;
00269 
00270     // we're all set up - now just loop, processing sensor reports and 
00271     // host requests
00272     for (;;)
00273     {
00274         
00275         // update the buttons
00276         uint32_t buttons = readButtons();
00277 
00278         
00279         // don't poll too fast, this can outrun the host
00280         if (reportTimer.read_ms() > 15)
00281         {
00282             js.update(buttons);
00283             // we've just started a new report interval, so reset the timer
00284             reportTimer.reset();
00285         }
00286 
00287 
00288         
00289 #ifdef DEBUG_PRINTF
00290         if (x != 0 || y != 0)
00291             printf("%d,%d\r\n", x, y);
00292 #endif
00293 
00294         // check for connection status changes
00295         int newConnected = js.isConnected() && !js.isSuspended();
00296         if (newConnected != connected)
00297         {
00298             // give it a few seconds to stabilize
00299             time_t tc = time(0);
00300             if (tc - connectChangeTime > 3)
00301             {
00302                 // note the new status
00303                 connected = newConnected;
00304                 connectChangeTime = tc;
00305             }
00306         }
00307 
00308         // provide a visual status indication on the on-board LED
00309         if (hbTimer.read_ms() > 1000) 
00310         {
00311             if (!newConnected)
00312             {
00313                 // suspended - turn off the LED
00314                 ledR = 1;
00315                 ledG = 1;
00316                 ledB = 1;
00317 
00318                 // show a status flash every so often                
00319                 if (hbcnt % 3 == 0)
00320                 {
00321                     // disconnected = red/red flash; suspended = red
00322                     for (int n = js.isConnected() ? 1 : 2 ; n > 0 ; --n)
00323                     {
00324                         ledR = 0;
00325                         wait(0.05);
00326                         ledR = 1;
00327                         wait(0.25);
00328                     }
00329                 }
00330             }
00331             else if (needReset)
00332             {
00333                 // connected, need to reset due to changes in config parameters -
00334                 // flash red/green
00335                 hb = !hb;
00336                 ledR = (hb ? 0 : 1);
00337                 ledG = (hb ? 1 : 0);
00338                 ledB = 0;
00339             }
00340             else
00341             {
00342                 // connected - flash blue/green
00343                 hb = !hb;
00344                 ledR = 1;
00345                 ledG = (hb ? 0 : 1);
00346                 ledB = (hb ? 1 : 0);
00347             }
00348             
00349             // reset the heartbeat timer
00350             hbTimer.reset();
00351             ++hbcnt;
00352         }
00353       
00354     }
00355 }