mark w. / Mbed 2 deprecated Nucleo_Gamepad

Dependencies:   USBDevice mbed

main.cpp

Committer:
thetazzbot
Date:
2016-12-14
Revision:
6:29a04fe27b5e
Parent:
4:05f4ace9508a

File content as of revision 6:29a04fe27b5e:

/* Modified for RetroPie MW 2016 */
/* Copyright 2014 M J Roberts, MIT License
*
* 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.
*/

//
//  - Button input wiring.  24 of the KL25Z's GPIO ports are mapped as digital inputs
//    for buttons and switches.  The software reports these as joystick buttons when
//    it sends reports to the PC.  These can be used to wire physical pinball-style
//    buttons in the cabinet (e.g., flipper buttons, the Start button) and miscellaneous 
//    switches (such as a tilt bob) to the PC.  Visual Pinball can use joystick buttons
//    for input - you just have to assign a VP function to each button using VP's
//    keyboard options dialog.  To wire a button physically, connect one terminal of
//    the button switch to the KL25Z ground, and connect the other terminal to the
//    the GPIO port you wish to assign to the button.  See the buttonMap[] array
//    below for the available GPIO ports and their assigned joystick button numbers.
//    If you're not using a GPIO port, you can just leave it unconnected - the digital
//    inputs have built-in pull-up resistors, so an unconnected port is the same as
//    an open switch (an "off" state for the button).
//
//
// STATUS LIGHTS:  The on-board LED on the KL25Z flashes to indicate the current 
// device status.  The flash patterns are:
//
//    two short red flashes = the device is powered but hasn't successfully
//        connected to the host via USB (either it's not physically connected
//        to the USB port, or there was a problem with the software handshake
//        with the USB device driver on the computer)
//
//    short red flash = the host computer is in sleep/suspend mode
//
//    long red/green = the LedWiz unti number has been changed, so a reset
//        is needed.  You can simply unplug the device and plug it back in,
//        or presss and hold the reset button on the device for a few seconds.
//
//    long yellow/green = everything's working, but the plunger hasn't
//        been calibrated; follow the calibration procedure described above.
//        This flash mode won't appear if the CCD has been disabled.  Note
//        that the device can't tell whether a CCD is physically attached;
//        if you don't have a CCD attached, you can set the appropriate option 
//        in config.h or use the  Windows config tool to disable the CCD 
//        software features.
//
//    alternating blue/green = everything's working
//
#include "mbed.h"
#include "math.h"
#include "USBGamepad.h"


#define DECL_EXTERNS
#include "config.h"


// ---------------------------------------------------------------------------
// utilities

// number of elements in an array
#define countof(x) (sizeof(x)/sizeof((x)[0]))

// --------------------------------------------------------------------------
// 
//
// Build the full USB product ID.  If we're using the LedWiz compatible
// vendor ID, the full product ID is the combination of the LedWiz base
// product ID (0x00F0) and the 0-based unit number (0-15).  If we're not
// trying to be LedWiz compatible, we just use the exact product ID
// specified in config.h.
#define MAKE_USB_PRODUCT_ID(vid, pidbase, unit) \
    ((vid) == 0xFAFA && (pidbase) == 0x00F0 ? (pidbase) | (unit) : (pidbase))


// --------------------------------------------------------------------------
//
// Joystick axis report range - we report from -JOYMAX to +JOYMAX
//
#define JOYMAX 4096


// ---------------------------------------------------------------------------
//
// On-board RGB LED elements - we use these for diagnostic displays.
//
//TODO: FIXME for Nucleo
DigitalOut ledR(LED1), ledG(LED2), ledB(LED3);

// ---------------------------------------------------------------------------
//
// Button input
//

// button input map array

DigitalIn *buttonDigIn[NUM_OF_BUTTONS];  // config.h

// button state
struct ButtonState
{
    // current on/off state
    int pressed;
    
    // Sticky time remaining for current state.  When a
    // state transition occurs, we set this to a debounce
    // period.  Future state transitions will be ignored
    // until the debounce time elapses.
    int t;
} buttonState[NUM_OF_BUTTONS];

// timer for button reports
static Timer buttonTimer;

// initialize the button inputs
void initButtons()
{
    // create the digital inputs
    for (int i = 0 ; i < countof(buttonDigIn) ; ++i)
    {
        if (i < countof(buttonMap) && buttonMap[i] != NC)
            buttonDigIn[i] = new DigitalIn(buttonMap[i]);
        else
            buttonDigIn[i] = 0;
    }
    
    // start the button timer
    buttonTimer.start();
}


// read the button input state
uint32_t readButtons()
{
    // start with all buttons off
    uint32_t buttons = 0;
    
    // figure the time elapsed since the last scan
    int dt = buttonTimer.read_ms();
    
    // reset the timef for the next scan
    buttonTimer.reset();
    
    // scan the button list
    uint32_t bit = 1;
    DigitalIn **di = buttonDigIn;
    ButtonState *bs = buttonState;
    for (int i = 0 ; i < countof(buttonDigIn) ; ++i, ++di, ++bs, bit <<= 1)
    {
        // read this button
        if (*di != 0)
        {
            // deduct the elapsed time since the last update
            // from the button's remaining sticky time
            bs->t -= dt;
            if (bs->t < 0)
                bs->t = 0;
            
            // If the sticky time has elapsed, note the new physical
            // state of the button.  If we still have sticky time
            // remaining, ignore the physical state; the last state
            // change persists until the sticky time elapses so that
            // we smooth out any "bounce" (electrical transients that
            // occur when the switch contact is opened or closed).
            if (bs->t == 0)
            {
                // get the new physical state
                int pressed = !(*di)->read();
                
                // update the button's logical state if this is a change
                if (pressed != bs->pressed)
                {
                    // store the new state
                    bs->pressed = pressed;
                    
                    // start a new sticky period for debouncing this
                    // state change
                    bs->t = 25;
                }
            }
            
            // if it's pressed, OR its bit into the state
            if (bs->pressed)
                buttons |= bit;
        }
    }
    
    // return the new button list
    return buttons;
}

// ---------------------------------------------------------------------------
//
// Customization joystick subbclass
//

class MyUSBGamepad: public USBGamepad
{
public:
    MyUSBGamepad(uint16_t vendor_id, uint16_t product_id, uint16_t product_release) 
        : USBGamepad(vendor_id, product_id, product_release, true)
    {
        suspended_ = false;
    }
    
    // are we connected?
    int isConnected()  { return configured(); }
    
    // Are we in suspend mode?
    int isSuspended() const { return suspended_; }
    
protected:
    virtual void suspendStateChanged(unsigned int suspended)
        { suspended_ = suspended; }

    // are we suspended?
    int suspended_; 
};



// ---------------------------------------------------------------------------
//
// Main program loop.  This is invoked on startup and runs forever.  Our
// main work is to read our devices, process
// the readings into nudge and plunger position data, and send the results
// to the host computer via the USB joystick interface.  
//
int main(void)
{
    // turn off our on-board indicator LED
    ledR = 1;
    ledG = 1;
    ledB = 1;
    
    // we're not connected/awake yet
    bool connected = false;
    time_t connectChangeTime = time(0);

    // initialize the button input ports
    initButtons();

    // we don't need a reset yet
    bool needReset = false;
    // Create the joystick USB client.  
    MyUSBGamepad js(USB_VENDOR_ID,USB_PRODUCT_ID,USB_PRODUCT_VER); // vendor, product, product release
        
    // last report timer - we use this to throttle reports, since VP
    // doesn't want to hear from us more than about every 10ms
    Timer reportTimer;
    reportTimer.start();

    // set up a timer for our heartbeat indicator
    Timer hbTimer;
    hbTimer.start();
    int hb = 0;
    uint16_t hbcnt = 0;

    // we're all set up - now just loop, processing sensor reports and 
    // host requests
    for (;;)
    {
        
        // update the buttons
        uint32_t buttons = readButtons();

        
        // don't poll too fast, this can outrun the host
        if (reportTimer.read_ms() > 15)
        {
            js.update(buttons);
            // we've just started a new report interval, so reset the timer
            reportTimer.reset();
        }


        
#ifdef DEBUG_PRINTF
        if (x != 0 || y != 0)
            printf("%d,%d\r\n", x, y);
#endif

        // check for connection status changes
        int newConnected = js.isConnected() && !js.isSuspended();
        if (newConnected != connected)
        {
            // give it a few seconds to stabilize
            time_t tc = time(0);
            if (tc - connectChangeTime > 3)
            {
                // note the new status
                connected = newConnected;
                connectChangeTime = tc;
            }
        }

        // provide a visual status indication on the on-board LED
        if (hbTimer.read_ms() > 1000) 
        {
            if (!newConnected)
            {
                // suspended - turn off the LED
                ledR = 1;
                ledG = 1;
                ledB = 1;

                // show a status flash every so often                
                if (hbcnt % 3 == 0)
                {
                    // disconnected = red/red flash; suspended = red
                    for (int n = js.isConnected() ? 1 : 2 ; n > 0 ; --n)
                    {
                        ledR = 0;
                        wait(0.05);
                        ledR = 1;
                        wait(0.25);
                    }
                }
            }
            else if (needReset)
            {
                // connected, need to reset due to changes in config parameters -
                // flash red/green
                hb = !hb;
                ledR = (hb ? 0 : 1);
                ledG = (hb ? 1 : 0);
                ledB = 0;
            }
            else
            {
                // connected - flash blue/green
                hb = !hb;
                ledR = 1;
                ledG = (hb ? 0 : 1);
                ledB = (hb ? 1 : 0);
            }
            
            // reset the heartbeat timer
            hbTimer.reset();
            ++hbcnt;
        }
      
    }
}