An input/output controller for virtual pinball machines, with plunger position tracking, accelerometer-based nudge sensing, button input encoding, and feedback device control.

Dependencies:   USBDevice mbed FastAnalogIn FastIO FastPWM SimpleDMA

/media/uploads/mjr/pinscape_no_background_small_L7Miwr6.jpg

The Pinscape Controller is a special-purpose software project that I wrote for my virtual pinball machine.

New version: V2 is now available! The information below is for version 1, which will continue to be available for people who prefer the original setup.

What exactly is a virtual pinball machine? It's basically a video-game pinball emulator built to look like a real pinball machine. (The picture at right is the one I built.) You start with a standard pinball cabinet, either built from scratch or salvaged from a real machine. Inside, you install a PC motherboard to run the software, and install TVs in place of the playfield and backglass. Several Windows pinball programs can take advantage of this setup, including the open-source project Visual Pinball, which has hundreds of tables available. Building one of these makes a great DIY project, and it's a good way to add to your skills at woodworking, computers, and electronics. Check out the Cabinet Builders' Forum on vpforums.org for lots of examples and advice.

This controller project is a key piece in my setup that helps integrate the video game into the pinball cabinet. It handles several input/output tasks that are unique to virtual pinball machines. First, it lets you connect a mechanical plunger to the software, so you can launch the ball like on a real machine. Second, it sends "nudge" data to the software, based on readings from an accelerometer. This lets you interact with the game physically, which makes the playing experience more realistic and immersive. Third, the software can handle button input (for wiring flipper buttons and other cabinet buttons), and fourth, it can control output devices (for tactile feedback, button lights, flashers, and other special effects).

Documentation

The Hardware Build Guide (PDF) has detailed instructions on how to set up a Pinscape Controller for your own virtual pinball cabinet.

Update notes

December 2015 version: This version fully supports the new Expansion Board project, but it'll also run without it. The default configuration settings haven't changed, so existing setups should continue to work as before.

August 2015 version: Be sure to get the latest version of the Config Tool for windows if you're upgrading from an older version of the firmware. This update adds support for TSL1412R sensors (a version of the 1410 sensor with a slightly larger pixel array), and a config option to set the mounting orientation of the board in the firmware rather than in VP (for better support for FP and other pinball programs that don't have VP's flexibility for setting the rotation).

Feb/March 2015 software versions: If you have a CCD plunger that you've been using with the older versions, and the plunger stops working (or doesn't work as well) after you update to the latest version, you might need to increase the brightness of your light source slightly. Check the CCD exposure with the Windows config tool to see if it looks too dark. The new software reads the CCD much more quickly than the old versions did. This makes the "shutter speed" faster, which might require a little more light to get the same readings. The CCD is actually really tolerant of varying light levels, so you probably won't have to change anything for the update - I didn't. But if you do have any trouble, have a look at the exposure meter and try a slightly brighter light source if the exposure looks too dark.

Downloads

  • Config tool for Windows (.exe and C# source): this is a Windows program that lets you view the raw pixel data from the CCD sensor, trigger plunger calibration mode, and configure some of the software options on the controller.
  • Custom VP builds: I created modified versions of Visual Pinball 9.9 and Physmod5 that you might want to use in combination with this controller. The modified versions have special handling for plunger calibration specific to the Pinscape Controller, as well as some enhancements to the nudge physics. If you're not using the plunger, you might still want it for the nudge improvements. The modified version also works with any other input controller, so you can get the enhanced nudging effects even if you're using a different plunger/nudge kit. The big change in the modified versions is a "filter" for accelerometer input that's designed to make the response to cabinet nudges more realistic. It also makes the response more subdued than in the standard VP, so it's not to everyone's taste. The downloads include both the updated executables and the source code changes, in case you want to merge the changes into your own custom version(s).

    Note! These features are now standard in the official VP 9.9.1 and VP 10 releases, so you don't need my custom builds if you're using 9.9.1 or 10 or later. I don't think there's any reason to use my 9.9 instead of the official 9.9.1, but I'm leaving it here just in case. In the official VP releases, look for the checkbox "Enable Nudge Filter" in the Keys preferences dialog. (There's no checkbox in my custom builds, though; the filter is simply always on in those.)
  • Output circuit shopping list: This is a saved shopping cart at mouser.com with the parts needed for each output driver, if you want to use the LedWiz emulator feature. Note that quantities in the cart are for one output channel, so multiply everything by the number of channels you plan to use, except that you only need one of the ULN2803 transistor array chips for each eight output circuits.
  • Lemming77's potentiometer mounting bracket and shooter rod connecter: Sketchup designs for 3D-printable parts for mounting a slide potentiometer as the plunger sensor. These were designed for a particular slide potentiometer that used to be available from an Aliexpress.com seller but is no longer listed. You can probably use this design as a starting point for other similar devices; just check the dimensions before committing the design to plastic.

Features

  • Plunger position sensing, using a TAOS TSL 1410R CCD linear array sensor. This sensor is a 1280 x 1 pixel array at 400 dpi, which makes it about 3" long - almost exactly the travel distance of a standard pinball plunger. The idea is that you install the sensor just above (within a few mm of) the shooter rod on the inside of the cabinet, with the CCD window facing down, aligned with and centered on the long axis of the shooter rod, and positioned so that the rest position of the tip is about 1/2" from one end of the window. As you pull back the plunger, the tip will travel down the length of the window, and the maximum retraction point will put the tip just about at the far end of the window. Put a light source below, facing the sensor - I'm using two typical 20 mA blue LEDs about 8" away (near the floor of the cabinet) with good results. The principle of operation is that the shooter rod casts a shadow on the CCD, so pixels behind the rod will register lower brightness than pixels that aren't in the shadow. We scan down the length of the sensor for the edge between darker and brighter, and this tells us how far back the rod has been pulled. We can read the CCD at about 25-30 ms intervals, so we can get rapid updates. We pass the readings reports to VP via our USB joystick reports.

    The hardware build guide includes schematics showing how to wire the CCD to the KL25Z. It's pretty straightforward - five wires between the two devices, no external components needed. Two GPIO ports are used as outputs to send signals to the device and one is used as an ADC in to read the pixel brightness inputs. The config tool has a feature that lets you display the raw pixel readings across the array, so you can test that the CCD is working and adjust the light source to get the right exposure level.

    Alternatively, you can use a slide potentiometer as the plunger sensor. This is a cheaper and somewhat simpler option that seems to work quite nicely, as you can see in Lemming77's video of this setup in action. This option is also explained more fully in the build guide.
  • Nudge sensing via the KL25Z's on-board accelerometer. Mounting the board in your cabinet makes it feel the same accelerations the cabinet experiences when you nudge it. Visual Pinball already knows how to interpret accelerometer input as nudging, so we simply feed the acceleration readings to VP via the joystick interface.
  • Cabinet button wiring. Up to 24 pushbuttons and switches can be wired to the controller for input controls (for example, flipper buttons, the Start button, the tilt bob, coin slot switches, and service door buttons). These appear to Windows as joystick buttons. VP can map joystick buttons to pinball inputs via its keyboard preferences dialog. (You can raise the 24-button limit by editing the source code, but since all of the GPIO pins are allocated, you'll have to reassign pins currently used for other functions.)
  • LedWiz emulation (limited). In addition to emulating a joystick, the device emulates the LedWiz USB interface, so controllers on the PC side such as DirectOutput Framework can recognize it and send it commands to control lights, solenoids, and other feedback devices. 22 GPIO ports are assigned by default as feedback device outputs. This feature has some limitations. The big one is that the KL25Z hardware only has 10 PWM channels, which isn't enough for a fully decked-out cabinet. You also need to build some external power driver circuitry to use this feature, because of the paltry 4mA output capacity of the KL25Z GPIO ports. The build guide includes instructions for a simple and robust output circuit, including part numbers for the exact components you need. It's not hard if you know your way around a soldering iron, but just be aware that it'll take a little work.

Warning: This is not replacement software for the VirtuaPin plunger kit. If you bought the VirtuaPin kit, please don't try to install this software. The VP kit happens to use the same microcontroller board, but the rest of its hardware is incompatible. The VP kit uses a different type of sensor for its plunger and has completely different button wiring, so the Pinscape software won't work properly with it.

Committer:
mjr
Date:
Sat Dec 19 06:37:19 2015 +0000
Revision:
35:e959ffba78fd
Parent:
34:6b981a2afab7
Child:
36:b9747461331e
Keyboard/Media Control interface working, but the extra interface confuses the DOF connector.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mjr 35:e959ffba78fd 1 /* Copyright 2014, 2015 M J Roberts, MIT License
mjr 5:a70c0bce770d 2 *
mjr 5:a70c0bce770d 3 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
mjr 5:a70c0bce770d 4 * and associated documentation files (the "Software"), to deal in the Software without
mjr 5:a70c0bce770d 5 * restriction, including without limitation the rights to use, copy, modify, merge, publish,
mjr 5:a70c0bce770d 6 * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
mjr 5:a70c0bce770d 7 * Software is furnished to do so, subject to the following conditions:
mjr 5:a70c0bce770d 8 *
mjr 5:a70c0bce770d 9 * The above copyright notice and this permission notice shall be included in all copies or
mjr 5:a70c0bce770d 10 * substantial portions of the Software.
mjr 5:a70c0bce770d 11 *
mjr 5:a70c0bce770d 12 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
mjr 5:a70c0bce770d 13 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
mjr 5:a70c0bce770d 14 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
mjr 5:a70c0bce770d 15 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
mjr 5:a70c0bce770d 16 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
mjr 5:a70c0bce770d 17 */
mjr 5:a70c0bce770d 18
mjr 5:a70c0bce770d 19 //
mjr 35:e959ffba78fd 20 // The Pinscape Controller
mjr 35:e959ffba78fd 21 // A comprehensive input/output controller for virtual pinball machines
mjr 5:a70c0bce770d 22 //
mjr 35:e959ffba78fd 23 // This project implements an I/O controller designed for use in custom-built virtual
mjr 35:e959ffba78fd 24 // pinball cabinets. It can handle nearly all of the functions involved in connecting
mjr 35:e959ffba78fd 25 // pinball simulation software on a Windows PC with devices in the cabinet, including
mjr 35:e959ffba78fd 26 // input devices such as buttons and sensors, and output devices that generate visual
mjr 35:e959ffba78fd 27 // or mechanical feedback during play, like lights, solenoids, and shaker motors.
mjr 35:e959ffba78fd 28 // You can use one, some, or all of the functions, in any combination. You can select
mjr 35:e959ffba78fd 29 // options and configure the controller using a setup tool that runs on Windows.
mjr 5:a70c0bce770d 30 //
mjr 35:e959ffba78fd 31 // The main functions are:
mjr 5:a70c0bce770d 32 //
mjr 35:e959ffba78fd 33 // - Nudge sensing, via the KL25Z's on-board accelerometer. Nudging the cabinet
mjr 17:ab3cec0c8bf4 34 // causes small accelerations that the accelerometer can detect; these are sent to
mjr 35:e959ffba78fd 35 // Visual Pinball (or other pinball emulator software) on the PC via the joystick
mjr 35:e959ffba78fd 36 // interface, using the X and Y axes. VP and most other PC pinball emulators have
mjr 35:e959ffba78fd 37 // native handling for this type of nudge input, so all you have to do is set some
mjr 35:e959ffba78fd 38 // preferences in VP to let it know that an accelerometer is attached.
mjr 5:a70c0bce770d 39 //
mjr 35:e959ffba78fd 40 // - Plunger position sensing, via a number of sensor options. To use this feature,
mjr 35:e959ffba78fd 41 // you need to choose a sensor and set it up, connect the sensor electrically to
mjr 35:e959ffba78fd 42 // the KL25Z, and configure the Pinscape software on the KL25Z to let it know how
mjr 35:e959ffba78fd 43 // the sensor is hooked up. The Pinscape software monitors the sensor and sends
mjr 35:e959ffba78fd 44 // readings to Visual Pinball via the joystick Z axis. VP and other PC software
mjr 35:e959ffba78fd 45 // has native support for this type of input as well; as with the nudge setup,
mjr 35:e959ffba78fd 46 // you just have to set some options in VP to activate the plunger.
mjr 17:ab3cec0c8bf4 47 //
mjr 35:e959ffba78fd 48 // The Pinscape software supports optical sensors (the TAOS TSL1410R and TSL1412R
mjr 35:e959ffba78fd 49 // linear sensor arrays) as well as slide potentiometers. The specific equipment
mjr 35:e959ffba78fd 50 // that's supported, along with physical mounting and wiring details, can be found
mjr 35:e959ffba78fd 51 // in the Build Guide.
mjr 35:e959ffba78fd 52 //
mjr 35:e959ffba78fd 53 // Note that while VP has its own built-in support for plunger devices like this
mjr 35:e959ffba78fd 54 // one, many existing VP tables will ignore it, because they use custom scripting
mjr 35:e959ffba78fd 55 // that's only designed for keyboard plunger input. The Build Guide has advice on
mjr 35:e959ffba78fd 56 // adjusting tables to add plunger support when necessary.
mjr 5:a70c0bce770d 57 //
mjr 6:cc35eb643e8f 58 // For best results, the plunger sensor should be calibrated. The calibration
mjr 6:cc35eb643e8f 59 // is stored in non-volatile memory on board the KL25Z, so it's only necessary
mjr 6:cc35eb643e8f 60 // to do the calibration once, when you first install everything. (You might
mjr 6:cc35eb643e8f 61 // also want to re-calibrate if you physically remove and reinstall the CCD
mjr 17:ab3cec0c8bf4 62 // sensor or the mechanical plunger, since their alignment shift change slightly
mjr 17:ab3cec0c8bf4 63 // when you put everything back together.) You can optionally install a
mjr 17:ab3cec0c8bf4 64 // dedicated momentary switch or pushbutton to activate the calibration mode;
mjr 17:ab3cec0c8bf4 65 // this is describe in the project documentation. If you don't want to bother
mjr 17:ab3cec0c8bf4 66 // with the extra button, you can also trigger calibration using the Windows
mjr 17:ab3cec0c8bf4 67 // setup software, which you can find on the Pinscape project page.
mjr 6:cc35eb643e8f 68 //
mjr 17:ab3cec0c8bf4 69 // The calibration procedure is described in the project documentation. Briefly,
mjr 17:ab3cec0c8bf4 70 // when you trigger calibration mode, the software will scan the CCD for about
mjr 17:ab3cec0c8bf4 71 // 15 seconds, during which you should simply pull the physical plunger back
mjr 17:ab3cec0c8bf4 72 // all the way, hold it for a moment, and then slowly return it to the rest
mjr 17:ab3cec0c8bf4 73 // position. (DON'T just release it from the retracted position, since that
mjr 17:ab3cec0c8bf4 74 // let it shoot forward too far. We want to measure the range from the park
mjr 17:ab3cec0c8bf4 75 // position to the fully retracted position only.)
mjr 5:a70c0bce770d 76 //
mjr 13:72dda449c3c0 77 // - Button input wiring. 24 of the KL25Z's GPIO ports are mapped as digital inputs
mjr 13:72dda449c3c0 78 // for buttons and switches. The software reports these as joystick buttons when
mjr 13:72dda449c3c0 79 // it sends reports to the PC. These can be used to wire physical pinball-style
mjr 13:72dda449c3c0 80 // buttons in the cabinet (e.g., flipper buttons, the Start button) and miscellaneous
mjr 13:72dda449c3c0 81 // switches (such as a tilt bob) to the PC. Visual Pinball can use joystick buttons
mjr 13:72dda449c3c0 82 // for input - you just have to assign a VP function to each button using VP's
mjr 13:72dda449c3c0 83 // keyboard options dialog. To wire a button physically, connect one terminal of
mjr 13:72dda449c3c0 84 // the button switch to the KL25Z ground, and connect the other terminal to the
mjr 35:e959ffba78fd 85 // the GPIO port you wish to assign to the button.
mjr 13:72dda449c3c0 86 //
mjr 5:a70c0bce770d 87 // - LedWiz emulation. The KL25Z can appear to the PC as an LedWiz device, and will
mjr 5:a70c0bce770d 88 // accept and process LedWiz commands from the host. The software can turn digital
mjr 5:a70c0bce770d 89 // output ports on and off, and can set varying PWM intensitiy levels on a subset
mjr 5:a70c0bce770d 90 // of ports. (The KL25Z can only provide 6 PWM ports. Intensity level settings on
mjr 5:a70c0bce770d 91 // other ports is ignored, so non-PWM ports can only be used for simple on/off
mjr 5:a70c0bce770d 92 // devices such as contactors and solenoids.) The KL25Z can only supply 4mA on its
mjr 5:a70c0bce770d 93 // output ports, so external hardware is required to take advantage of the LedWiz
mjr 5:a70c0bce770d 94 // emulation. Many different hardware designs are possible, but there's a simple
mjr 5:a70c0bce770d 95 // reference design in the documentation that uses a Darlington array IC to
mjr 5:a70c0bce770d 96 // increase the output from each port to 500mA (the same level as the LedWiz),
mjr 5:a70c0bce770d 97 // plus an extended design that adds an optocoupler and MOSFET to provide very
mjr 5:a70c0bce770d 98 // high power handling, up to about 45A or 150W, with voltages up to 100V.
mjr 5:a70c0bce770d 99 // That will handle just about any DC device directly (wtihout relays or other
mjr 5:a70c0bce770d 100 // amplifiers), and switches fast enough to support PWM devices.
mjr 5:a70c0bce770d 101 //
mjr 5:a70c0bce770d 102 // The device can report any desired LedWiz unit number to the host, which makes
mjr 5:a70c0bce770d 103 // it possible to use the LedWiz emulation on a machine that also has one or more
mjr 5:a70c0bce770d 104 // actual LedWiz devices intalled. The LedWiz design allows for up to 16 units
mjr 5:a70c0bce770d 105 // to be installed in one machine - each one is invidually addressable by its
mjr 5:a70c0bce770d 106 // distinct unit number.
mjr 5:a70c0bce770d 107 //
mjr 5:a70c0bce770d 108 // The LedWiz emulation features are of course optional. There's no need to
mjr 5:a70c0bce770d 109 // build any of the external port hardware (or attach anything to the output
mjr 5:a70c0bce770d 110 // ports at all) if the LedWiz features aren't needed. Most people won't have
mjr 5:a70c0bce770d 111 // any use for the LedWiz features. I built them mostly as a learning exercise,
mjr 5:a70c0bce770d 112 // but with a slight practical need for a handful of extra ports (I'm using the
mjr 5:a70c0bce770d 113 // cutting-edge 10-contactor setup, so my real LedWiz is full!).
mjr 6:cc35eb643e8f 114 //
mjr 26:cb71c4af2912 115 // - Enhanced LedWiz emulation with TLC5940 PWM controller chips. You can attach
mjr 26:cb71c4af2912 116 // external PWM controller chips for controlling device outputs, instead of using
mjr 26:cb71c4af2912 117 // the limited LedWiz emulation through the on-board GPIO ports as described above.
mjr 26:cb71c4af2912 118 // The software can control a set of daisy-chained TLC5940 chips, which provide
mjr 26:cb71c4af2912 119 // 16 PWM outputs per chip. Two of these chips give you the full complement
mjr 26:cb71c4af2912 120 // of 32 output ports of an actual LedWiz, and four give you 64 ports, which
mjr 33:d832bcab089e 121 // should be plenty for nearly any virtual pinball project. A private, extended
mjr 33:d832bcab089e 122 // version of the LedWiz protocol lets the host control the extra outputs, up to
mjr 33:d832bcab089e 123 // 128 outputs per KL25Z (8 TLC5940s). To take advantage of the extra outputs
mjr 33:d832bcab089e 124 // on the PC side, you need software that knows about the protocol extensions,
mjr 33:d832bcab089e 125 // which means you need the latest version of DirectOutput Framework (DOF). VP
mjr 33:d832bcab089e 126 // uses DOF for its output, so VP will be able to use the added ports without any
mjr 33:d832bcab089e 127 // extra work on your part. Older software (e.g., Future Pinball) that doesn't
mjr 33:d832bcab089e 128 // use DOF will still be able to use the LedWiz-compatible protocol, so it'll be
mjr 33:d832bcab089e 129 // able to control your first 32 ports (numbered 1-32 in the LedWiz scheme), but
mjr 33:d832bcab089e 130 // older software won't be able to address higher-numbered ports. That shouldn't
mjr 33:d832bcab089e 131 // be a problem because older software wouldn't know what to do with the extra
mjr 33:d832bcab089e 132 // devices anyway - FP, for example, is limited to a pre-defined set of outputs.
mjr 33:d832bcab089e 133 // As long as you put the most common devices on the first 32 outputs, and use
mjr 33:d832bcab089e 134 // higher numbered ports for the less common devices that older software can't
mjr 33:d832bcab089e 135 // use anyway, you'll get maximum functionality out of software new and old.
mjr 26:cb71c4af2912 136 //
mjr 35:e959ffba78fd 137 //
mjr 35:e959ffba78fd 138 //
mjr 33:d832bcab089e 139 // STATUS LIGHTS: The on-board LED on the KL25Z flashes to indicate the current
mjr 33:d832bcab089e 140 // device status. The flash patterns are:
mjr 6:cc35eb643e8f 141 //
mjr 6:cc35eb643e8f 142 // two short red flashes = the device is powered but hasn't successfully
mjr 6:cc35eb643e8f 143 // connected to the host via USB (either it's not physically connected
mjr 6:cc35eb643e8f 144 // to the USB port, or there was a problem with the software handshake
mjr 6:cc35eb643e8f 145 // with the USB device driver on the computer)
mjr 6:cc35eb643e8f 146 //
mjr 6:cc35eb643e8f 147 // short red flash = the host computer is in sleep/suspend mode
mjr 6:cc35eb643e8f 148 //
mjr 6:cc35eb643e8f 149 // long yellow/green = everything's working, but the plunger hasn't
mjr 6:cc35eb643e8f 150 // been calibrated; follow the calibration procedure described above.
mjr 6:cc35eb643e8f 151 // This flash mode won't appear if the CCD has been disabled. Note
mjr 18:5e890ebd0023 152 // that the device can't tell whether a CCD is physically attached;
mjr 18:5e890ebd0023 153 // if you don't have a CCD attached, you can set the appropriate option
mjr 18:5e890ebd0023 154 // in config.h or use the Windows config tool to disable the CCD
mjr 18:5e890ebd0023 155 // software features.
mjr 6:cc35eb643e8f 156 //
mjr 35:e959ffba78fd 157 // alternating blue/green = everything's working, and the plunger has
mjr 35:e959ffba78fd 158 // been calibrated
mjr 10:976666ffa4ef 159 //
mjr 10:976666ffa4ef 160 //
mjr 35:e959ffba78fd 161 // USB PROTOCOL: please refer to USBProtocol.h for details on the USB
mjr 35:e959ffba78fd 162 // message protocol.
mjr 33:d832bcab089e 163
mjr 33:d832bcab089e 164
mjr 0:5acbbe3f4cf4 165 #include "mbed.h"
mjr 6:cc35eb643e8f 166 #include "math.h"
mjr 0:5acbbe3f4cf4 167 #include "USBJoystick.h"
mjr 0:5acbbe3f4cf4 168 #include "MMA8451Q.h"
mjr 1:d913e0afb2ac 169 #include "tsl1410r.h"
mjr 1:d913e0afb2ac 170 #include "FreescaleIAP.h"
mjr 2:c174f9ee414a 171 #include "crc32.h"
mjr 26:cb71c4af2912 172 #include "TLC5940.h"
mjr 34:6b981a2afab7 173 #include "74HC595.h"
mjr 35:e959ffba78fd 174 #include "nvm.h"
mjr 35:e959ffba78fd 175 #include "plunger.h"
mjr 35:e959ffba78fd 176 #include "ccdSensor.h"
mjr 35:e959ffba78fd 177 #include "potSensor.h"
mjr 35:e959ffba78fd 178 #include "nullSensor.h"
mjr 2:c174f9ee414a 179
mjr 21:5048e16cc9ef 180 #define DECL_EXTERNS
mjr 17:ab3cec0c8bf4 181 #include "config.h"
mjr 17:ab3cec0c8bf4 182
mjr 5:a70c0bce770d 183
mjr 5:a70c0bce770d 184 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 185 // utilities
mjr 17:ab3cec0c8bf4 186
mjr 17:ab3cec0c8bf4 187 // number of elements in an array
mjr 17:ab3cec0c8bf4 188 #define countof(x) (sizeof(x)/sizeof((x)[0]))
mjr 17:ab3cec0c8bf4 189
mjr 26:cb71c4af2912 190 // floating point square of a number
mjr 26:cb71c4af2912 191 inline float square(float x) { return x*x; }
mjr 26:cb71c4af2912 192
mjr 26:cb71c4af2912 193 // floating point rounding
mjr 26:cb71c4af2912 194 inline float round(float x) { return x > 0 ? floor(x + 0.5) : ceil(x - 0.5); }
mjr 26:cb71c4af2912 195
mjr 17:ab3cec0c8bf4 196
mjr 33:d832bcab089e 197 // --------------------------------------------------------------------------
mjr 33:d832bcab089e 198 //
mjr 33:d832bcab089e 199 // USB product version number
mjr 5:a70c0bce770d 200 //
mjr 35:e959ffba78fd 201 const uint16_t USB_VERSION_NO = 0x0008;
mjr 33:d832bcab089e 202
mjr 33:d832bcab089e 203 // --------------------------------------------------------------------------
mjr 33:d832bcab089e 204 //
mjr 6:cc35eb643e8f 205 // Joystick axis report range - we report from -JOYMAX to +JOYMAX
mjr 33:d832bcab089e 206 //
mjr 6:cc35eb643e8f 207 #define JOYMAX 4096
mjr 6:cc35eb643e8f 208
mjr 9:fd65b0a94720 209
mjr 17:ab3cec0c8bf4 210 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 211 //
mjr 17:ab3cec0c8bf4 212 // On-board RGB LED elements - we use these for diagnostic displays.
mjr 17:ab3cec0c8bf4 213 //
mjr 26:cb71c4af2912 214 // Note that LED3 (the blue segment) is hard-wired on the KL25Z to PTD1,
mjr 26:cb71c4af2912 215 // so PTD1 shouldn't be used for any other purpose (e.g., as a keyboard
mjr 26:cb71c4af2912 216 // input or a device output). (This is kind of unfortunate in that it's
mjr 26:cb71c4af2912 217 // one of only two ports exposed on the jumper pins that can be muxed to
mjr 26:cb71c4af2912 218 // SPI0 SCLK. This effectively limits us to PTC5 if we want to use the
mjr 26:cb71c4af2912 219 // SPI capability.)
mjr 26:cb71c4af2912 220 //
mjr 17:ab3cec0c8bf4 221 DigitalOut ledR(LED1), ledG(LED2), ledB(LED3);
mjr 17:ab3cec0c8bf4 222
mjr 9:fd65b0a94720 223
mjr 9:fd65b0a94720 224 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 225 //
mjr 35:e959ffba78fd 226 // Wire protocol value translations. These translate byte values from
mjr 35:e959ffba78fd 227 // the USB protocol to local native format.
mjr 35:e959ffba78fd 228 //
mjr 35:e959ffba78fd 229
mjr 35:e959ffba78fd 230 // unsigned 16-bit integer
mjr 35:e959ffba78fd 231 inline uint16_t wireUI16(const uint8_t *b)
mjr 35:e959ffba78fd 232 {
mjr 35:e959ffba78fd 233 return b[0] | ((uint16_t)b[1] << 8);
mjr 35:e959ffba78fd 234 }
mjr 35:e959ffba78fd 235
mjr 35:e959ffba78fd 236 inline int16_t wireI16(const uint8_t *b)
mjr 35:e959ffba78fd 237 {
mjr 35:e959ffba78fd 238 return (int16_t)wireUI16(b);
mjr 35:e959ffba78fd 239 }
mjr 35:e959ffba78fd 240
mjr 35:e959ffba78fd 241 inline uint32_t wireUI32(const uint8_t *b)
mjr 35:e959ffba78fd 242 {
mjr 35:e959ffba78fd 243 return b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24);
mjr 35:e959ffba78fd 244 }
mjr 35:e959ffba78fd 245
mjr 35:e959ffba78fd 246 inline int32_t wireI32(const uint8_t *b)
mjr 35:e959ffba78fd 247 {
mjr 35:e959ffba78fd 248 return (int32_t)wireUI32(b);
mjr 35:e959ffba78fd 249 }
mjr 35:e959ffba78fd 250
mjr 35:e959ffba78fd 251 inline PinName wirePinName(int c)
mjr 35:e959ffba78fd 252 {
mjr 35:e959ffba78fd 253 static const PinName p[] = {
mjr 35:e959ffba78fd 254 NC, PTA1, PTA2, PTA4, PTA5, PTA12, PTA13, PTA16, PTA17, PTB0, // 0-9
mjr 35:e959ffba78fd 255 PTB1, PTB2, PTB3, PTB8, PTB9, PTB10, PTB11, PTC0, PTC1, PTC2, // 10-19
mjr 35:e959ffba78fd 256 PTC3, PTC4, PTC5, PTC6, PTC7, PTC8, PTC9, PTC10, PTC11, PTC12, // 20-29
mjr 35:e959ffba78fd 257 PTC13, PTC16, PTC17, PTD0, PTD1, PTD2, PTD3, PTD4, PTD5, PTD6, // 30-39
mjr 35:e959ffba78fd 258 PTD7, PTE0, PTE1, PTE2, PTE3, PTE4, PTE5, PTE20, PTE21, PTE22, // 40-49
mjr 35:e959ffba78fd 259 PTE23, PTE29, PTE30, PTE31 // 50-53
mjr 35:e959ffba78fd 260 };
mjr 35:e959ffba78fd 261 return (c < countof(p) ? p[c] : NC);
mjr 35:e959ffba78fd 262 }
mjr 35:e959ffba78fd 263
mjr 35:e959ffba78fd 264
mjr 35:e959ffba78fd 265 // ---------------------------------------------------------------------------
mjr 35:e959ffba78fd 266 //
mjr 29:582472d0bc57 267 // LedWiz emulation, and enhanced TLC5940 output controller
mjr 5:a70c0bce770d 268 //
mjr 26:cb71c4af2912 269 // There are two modes for this feature. The default mode uses the on-board
mjr 26:cb71c4af2912 270 // GPIO ports to implement device outputs - each LedWiz software port is
mjr 26:cb71c4af2912 271 // connected to a physical GPIO pin on the KL25Z. The KL25Z only has 10
mjr 26:cb71c4af2912 272 // PWM channels, so in this mode only 10 LedWiz ports will be dimmable; the
mjr 26:cb71c4af2912 273 // rest are strictly on/off. The KL25Z also has a limited number of GPIO
mjr 26:cb71c4af2912 274 // ports overall - not enough for the full complement of 32 LedWiz ports
mjr 26:cb71c4af2912 275 // and 24 VP joystick inputs, so it's necessary to trade one against the
mjr 26:cb71c4af2912 276 // other if both features are to be used.
mjr 26:cb71c4af2912 277 //
mjr 26:cb71c4af2912 278 // The alternative, enhanced mode uses external TLC5940 PWM controller
mjr 26:cb71c4af2912 279 // chips to control device outputs. In this mode, each LedWiz software
mjr 26:cb71c4af2912 280 // port is mapped to an output on one of the external TLC5940 chips.
mjr 26:cb71c4af2912 281 // Two 5940s is enough for the full set of 32 LedWiz ports, and we can
mjr 26:cb71c4af2912 282 // support even more chips for even more outputs (although doing so requires
mjr 26:cb71c4af2912 283 // breaking LedWiz compatibility, since the LedWiz USB protocol is hardwired
mjr 26:cb71c4af2912 284 // for 32 outputs). Every port in this mode has full PWM support.
mjr 26:cb71c4af2912 285 //
mjr 5:a70c0bce770d 286
mjr 29:582472d0bc57 287
mjr 26:cb71c4af2912 288 // Current starting output index for "PBA" messages from the PC (using
mjr 26:cb71c4af2912 289 // the LedWiz USB protocol). Each PBA message implicitly uses the
mjr 26:cb71c4af2912 290 // current index as the starting point for the ports referenced in
mjr 26:cb71c4af2912 291 // the message, and increases it (by 8) for the next call.
mjr 0:5acbbe3f4cf4 292 static int pbaIdx = 0;
mjr 0:5acbbe3f4cf4 293
mjr 26:cb71c4af2912 294 // Generic LedWiz output port interface. We create a cover class to
mjr 26:cb71c4af2912 295 // virtualize digital vs PWM outputs, and on-board KL25Z GPIO vs external
mjr 26:cb71c4af2912 296 // TLC5940 outputs, and give them all a common interface.
mjr 6:cc35eb643e8f 297 class LwOut
mjr 6:cc35eb643e8f 298 {
mjr 6:cc35eb643e8f 299 public:
mjr 26:cb71c4af2912 300 // Set the output intensity. 'val' is 0.0 for fully off, 1.0 for
mjr 26:cb71c4af2912 301 // fully on, and fractional values for intermediate intensities.
mjr 6:cc35eb643e8f 302 virtual void set(float val) = 0;
mjr 6:cc35eb643e8f 303 };
mjr 26:cb71c4af2912 304
mjr 35:e959ffba78fd 305 // LwOut class for virtual ports. This type of port is visible to
mjr 35:e959ffba78fd 306 // the host software, but isn't connected to any physical output.
mjr 35:e959ffba78fd 307 // This can be used for special software-only ports like the ZB
mjr 35:e959ffba78fd 308 // Launch Ball output, or simply for placeholders in the LedWiz port
mjr 35:e959ffba78fd 309 // numbering.
mjr 35:e959ffba78fd 310 class LwVirtualOut: public LwOut
mjr 33:d832bcab089e 311 {
mjr 33:d832bcab089e 312 public:
mjr 35:e959ffba78fd 313 LwVirtualOut() { }
mjr 33:d832bcab089e 314 virtual void set(float val) { }
mjr 33:d832bcab089e 315 };
mjr 26:cb71c4af2912 316
mjr 34:6b981a2afab7 317 // Active Low out. For any output marked as active low, we layer this
mjr 34:6b981a2afab7 318 // on top of the physical pin interface. This simply inverts the value of
mjr 34:6b981a2afab7 319 // the output value, so that 1.0 means fully off and 0.0 means fully on.
mjr 34:6b981a2afab7 320 class LwInvertedOut: public LwOut
mjr 34:6b981a2afab7 321 {
mjr 34:6b981a2afab7 322 public:
mjr 34:6b981a2afab7 323 LwInvertedOut(LwOut *o) : out(o) { }
mjr 34:6b981a2afab7 324 virtual void set(float val) { out->set(1.0 - val); }
mjr 34:6b981a2afab7 325
mjr 34:6b981a2afab7 326 private:
mjr 34:6b981a2afab7 327 LwOut *out;
mjr 34:6b981a2afab7 328 };
mjr 34:6b981a2afab7 329
mjr 26:cb71c4af2912 330
mjr 35:e959ffba78fd 331 //
mjr 35:e959ffba78fd 332 // The TLC5940 interface object. We'll set this up with the port
mjr 35:e959ffba78fd 333 // assignments set in config.h.
mjr 33:d832bcab089e 334 //
mjr 35:e959ffba78fd 335 TLC5940 *tlc5940 = 0;
mjr 35:e959ffba78fd 336 void init_tlc5940(Config &cfg)
mjr 35:e959ffba78fd 337 {
mjr 35:e959ffba78fd 338 if (cfg.tlc5940.nchips != 0)
mjr 35:e959ffba78fd 339 {
mjr 35:e959ffba78fd 340 tlc5940 = new TLC5940(cfg.tlc5940.sclk, cfg.tlc5940.sin, cfg.tlc5940.gsclk,
mjr 35:e959ffba78fd 341 cfg.tlc5940.blank, cfg.tlc5940.xlat, cfg.tlc5940.nchips);
mjr 35:e959ffba78fd 342 }
mjr 35:e959ffba78fd 343 }
mjr 26:cb71c4af2912 344
mjr 26:cb71c4af2912 345 // LwOut class for TLC5940 outputs. These are fully PWM capable.
mjr 26:cb71c4af2912 346 // The 'idx' value in the constructor is the output index in the
mjr 26:cb71c4af2912 347 // daisy-chained TLC5940 array. 0 is output #0 on the first chip,
mjr 26:cb71c4af2912 348 // 1 is #1 on the first chip, 15 is #15 on the first chip, 16 is
mjr 26:cb71c4af2912 349 // #0 on the second chip, 32 is #0 on the third chip, etc.
mjr 26:cb71c4af2912 350 class Lw5940Out: public LwOut
mjr 26:cb71c4af2912 351 {
mjr 26:cb71c4af2912 352 public:
mjr 26:cb71c4af2912 353 Lw5940Out(int idx) : idx(idx) { prv = -1; }
mjr 26:cb71c4af2912 354 virtual void set(float val)
mjr 26:cb71c4af2912 355 {
mjr 26:cb71c4af2912 356 if (val != prv)
mjr 35:e959ffba78fd 357 tlc5940->set(idx, (int)((prv = val) * 4095));
mjr 26:cb71c4af2912 358 }
mjr 26:cb71c4af2912 359 int idx;
mjr 26:cb71c4af2912 360 float prv;
mjr 26:cb71c4af2912 361 };
mjr 26:cb71c4af2912 362
mjr 33:d832bcab089e 363
mjr 34:6b981a2afab7 364 // 74HC595 interface object. Set this up with the port assignments in
mjr 34:6b981a2afab7 365 // config.h.
mjr 35:e959ffba78fd 366 HC595 *hc595 = 0;
mjr 35:e959ffba78fd 367
mjr 35:e959ffba78fd 368 // initialize the 74HC595 interface
mjr 35:e959ffba78fd 369 void init_hc595(Config &cfg)
mjr 35:e959ffba78fd 370 {
mjr 35:e959ffba78fd 371 if (cfg.hc595.nchips != 0)
mjr 35:e959ffba78fd 372 {
mjr 35:e959ffba78fd 373 hc595 = new HC595(cfg.hc595.nchips, cfg.hc595.sin, cfg.hc595.sclk, cfg.hc595.latch, cfg.hc595.ena);
mjr 35:e959ffba78fd 374 hc595->init();
mjr 35:e959ffba78fd 375 hc595->update();
mjr 35:e959ffba78fd 376 }
mjr 35:e959ffba78fd 377 }
mjr 34:6b981a2afab7 378
mjr 34:6b981a2afab7 379 // LwOut class for 74HC595 outputs. These are simple digial outs.
mjr 34:6b981a2afab7 380 // The 'idx' value in the constructor is the output index in the
mjr 34:6b981a2afab7 381 // daisy-chained 74HC595 array. 0 is output #0 on the first chip,
mjr 34:6b981a2afab7 382 // 1 is #1 on the first chip, 7 is #7 on the first chip, 8 is
mjr 34:6b981a2afab7 383 // #0 on the second chip, etc.
mjr 34:6b981a2afab7 384 class Lw595Out: public LwOut
mjr 33:d832bcab089e 385 {
mjr 33:d832bcab089e 386 public:
mjr 34:6b981a2afab7 387 Lw595Out(int idx) : idx(idx) { prv = -1; }
mjr 34:6b981a2afab7 388 virtual void set(float val)
mjr 34:6b981a2afab7 389 {
mjr 34:6b981a2afab7 390 if (val != prv)
mjr 35:e959ffba78fd 391 hc595->set(idx, (prv = val) == 0.0 ? 0 : 1);
mjr 34:6b981a2afab7 392 }
mjr 34:6b981a2afab7 393 int idx;
mjr 34:6b981a2afab7 394 float prv;
mjr 33:d832bcab089e 395 };
mjr 33:d832bcab089e 396
mjr 26:cb71c4af2912 397
mjr 26:cb71c4af2912 398 //
mjr 26:cb71c4af2912 399 // Default LedWiz mode - using on-board GPIO ports. In this mode, we
mjr 26:cb71c4af2912 400 // assign a KL25Z GPIO port to each LedWiz output. We have to use a
mjr 26:cb71c4af2912 401 // mix of PWM-capable and Digital-Only ports in this configuration,
mjr 26:cb71c4af2912 402 // since the KL25Z hardware only has 10 PWM channels, which isn't
mjr 26:cb71c4af2912 403 // enough to fill out the full complement of 32 LedWiz outputs.
mjr 26:cb71c4af2912 404 //
mjr 26:cb71c4af2912 405
mjr 26:cb71c4af2912 406 // LwOut class for a PWM-capable GPIO port
mjr 6:cc35eb643e8f 407 class LwPwmOut: public LwOut
mjr 6:cc35eb643e8f 408 {
mjr 6:cc35eb643e8f 409 public:
mjr 13:72dda449c3c0 410 LwPwmOut(PinName pin) : p(pin) { prv = -1; }
mjr 13:72dda449c3c0 411 virtual void set(float val)
mjr 13:72dda449c3c0 412 {
mjr 13:72dda449c3c0 413 if (val != prv)
mjr 13:72dda449c3c0 414 p.write(prv = val);
mjr 13:72dda449c3c0 415 }
mjr 6:cc35eb643e8f 416 PwmOut p;
mjr 13:72dda449c3c0 417 float prv;
mjr 6:cc35eb643e8f 418 };
mjr 26:cb71c4af2912 419
mjr 26:cb71c4af2912 420 // LwOut class for a Digital-Only (Non-PWM) GPIO port
mjr 6:cc35eb643e8f 421 class LwDigOut: public LwOut
mjr 6:cc35eb643e8f 422 {
mjr 6:cc35eb643e8f 423 public:
mjr 13:72dda449c3c0 424 LwDigOut(PinName pin) : p(pin) { prv = -1; }
mjr 13:72dda449c3c0 425 virtual void set(float val)
mjr 13:72dda449c3c0 426 {
mjr 13:72dda449c3c0 427 if (val != prv)
mjr 13:72dda449c3c0 428 p.write((prv = val) == 0.0 ? 0 : 1);
mjr 13:72dda449c3c0 429 }
mjr 6:cc35eb643e8f 430 DigitalOut p;
mjr 13:72dda449c3c0 431 float prv;
mjr 6:cc35eb643e8f 432 };
mjr 26:cb71c4af2912 433
mjr 29:582472d0bc57 434 // Array of output physical pin assignments. This array is indexed
mjr 29:582472d0bc57 435 // by LedWiz logical port number - lwPin[n] is the maping for LedWiz
mjr 35:e959ffba78fd 436 // port n (0-based).
mjr 35:e959ffba78fd 437 //
mjr 35:e959ffba78fd 438 // Each pin is handled by an interface object for the physical output
mjr 35:e959ffba78fd 439 // type for the port, as set in the configuration. The interface
mjr 35:e959ffba78fd 440 // objects handle the specifics of addressing the different hardware
mjr 35:e959ffba78fd 441 // types (GPIO PWM ports, GPIO digital ports, TLC5940 ports, and
mjr 35:e959ffba78fd 442 // 74HC595 ports).
mjr 33:d832bcab089e 443 static int numOutputs;
mjr 33:d832bcab089e 444 static LwOut **lwPin;
mjr 33:d832bcab089e 445
mjr 35:e959ffba78fd 446 // Number of LedWiz emulation outputs. This is the number of ports
mjr 35:e959ffba78fd 447 // accessible through the standard (non-extended) LedWiz protocol
mjr 35:e959ffba78fd 448 // messages. The protocol has a fixed set of 32 outputs, but we
mjr 35:e959ffba78fd 449 // might have fewer actual outputs. This is therefore set to the
mjr 35:e959ffba78fd 450 // lower of 32 or the actual number of outputs.
mjr 35:e959ffba78fd 451 static int numLwOutputs;
mjr 35:e959ffba78fd 452
mjr 33:d832bcab089e 453 // Current absolute brightness level for an output. This is a float
mjr 33:d832bcab089e 454 // value from 0.0 for fully off to 1.0 for fully on. This is the final
mjr 33:d832bcab089e 455 // derived value for the port. For outputs set by LedWiz messages,
mjr 33:d832bcab089e 456 // this is derived from the LedWiz state, and is updated on each pulse
mjr 33:d832bcab089e 457 // timer interrupt for lights in flashing states. For outputs set by
mjr 33:d832bcab089e 458 // extended protocol messages, this is simply the brightness last set.
mjr 33:d832bcab089e 459 static float *outLevel;
mjr 6:cc35eb643e8f 460
mjr 6:cc35eb643e8f 461 // initialize the output pin array
mjr 35:e959ffba78fd 462 void initLwOut(Config &cfg)
mjr 6:cc35eb643e8f 463 {
mjr 35:e959ffba78fd 464 // Count the outputs. The first disabled output determines the
mjr 35:e959ffba78fd 465 // total number of ports.
mjr 35:e959ffba78fd 466 numOutputs = MAX_OUT_PORTS;
mjr 33:d832bcab089e 467 int i;
mjr 35:e959ffba78fd 468 for (i = 0 ; i < MAX_OUT_PORTS ; ++i)
mjr 6:cc35eb643e8f 469 {
mjr 35:e959ffba78fd 470 if (cfg.outPort[i].typ == PortTypeDisabled)
mjr 34:6b981a2afab7 471 {
mjr 35:e959ffba78fd 472 numOutputs = i;
mjr 34:6b981a2afab7 473 break;
mjr 34:6b981a2afab7 474 }
mjr 33:d832bcab089e 475 }
mjr 33:d832bcab089e 476
mjr 35:e959ffba78fd 477 // the real LedWiz protocol can access at most 32 ports, or the
mjr 35:e959ffba78fd 478 // actual number of outputs, whichever is lower
mjr 35:e959ffba78fd 479 numLwOutputs = (numOutputs < 32 ? numOutputs : 32);
mjr 35:e959ffba78fd 480
mjr 33:d832bcab089e 481 // allocate the pin array
mjr 33:d832bcab089e 482 lwPin = new LwOut*[numOutputs];
mjr 33:d832bcab089e 483
mjr 35:e959ffba78fd 484 // Allocate the current brightness array.
mjr 35:e959ffba78fd 485 outLevel = new float[numOutputs < 32 ? 32 : numOutputs];
mjr 33:d832bcab089e 486
mjr 35:e959ffba78fd 487 // create the pin interface object for each port
mjr 35:e959ffba78fd 488 for (i = 0 ; i < numOutputs ; ++i)
mjr 35:e959ffba78fd 489 {
mjr 35:e959ffba78fd 490 // get this item's values
mjr 35:e959ffba78fd 491 int typ = cfg.outPort[i].typ;
mjr 35:e959ffba78fd 492 int pin = cfg.outPort[i].pin;
mjr 35:e959ffba78fd 493 int flags = cfg.outPort[i].flags;
mjr 35:e959ffba78fd 494 int activeLow = flags & PortFlagActiveLow;
mjr 26:cb71c4af2912 495
mjr 35:e959ffba78fd 496 // create the pin interface object according to the port type
mjr 34:6b981a2afab7 497 switch (typ)
mjr 33:d832bcab089e 498 {
mjr 35:e959ffba78fd 499 case PortTypeGPIOPWM:
mjr 35:e959ffba78fd 500 // PWM GPIO port
mjr 35:e959ffba78fd 501 lwPin[i] = new LwPwmOut(wirePinName(pin));
mjr 34:6b981a2afab7 502 break;
mjr 34:6b981a2afab7 503
mjr 35:e959ffba78fd 504 case PortTypeGPIODig:
mjr 35:e959ffba78fd 505 // Digital GPIO port
mjr 35:e959ffba78fd 506 lwPin[i] = new LwDigOut(wirePinName(pin));
mjr 34:6b981a2afab7 507 break;
mjr 34:6b981a2afab7 508
mjr 35:e959ffba78fd 509 case PortTypeTLC5940:
mjr 35:e959ffba78fd 510 // TLC5940 port
mjr 35:e959ffba78fd 511 lwPin[i] = new Lw5940Out(pin);
mjr 34:6b981a2afab7 512 break;
mjr 34:6b981a2afab7 513
mjr 35:e959ffba78fd 514 case PortType74HC595:
mjr 35:e959ffba78fd 515 // 74HC595 port
mjr 35:e959ffba78fd 516 lwPin[i] = new Lw595Out(pin);
mjr 34:6b981a2afab7 517 break;
mjr 35:e959ffba78fd 518
mjr 35:e959ffba78fd 519 case PortTypeVirtual:
mjr 34:6b981a2afab7 520 default:
mjr 35:e959ffba78fd 521 // virtual or unknown
mjr 35:e959ffba78fd 522 lwPin[i] = new LwVirtualOut();
mjr 34:6b981a2afab7 523 break;
mjr 33:d832bcab089e 524 }
mjr 34:6b981a2afab7 525
mjr 34:6b981a2afab7 526 // if it's Active Low, layer an inverter
mjr 34:6b981a2afab7 527 if (activeLow)
mjr 34:6b981a2afab7 528 lwPin[i] = new LwInvertedOut(lwPin[i]);
mjr 34:6b981a2afab7 529
mjr 34:6b981a2afab7 530 // turn it off initially
mjr 33:d832bcab089e 531 lwPin[i]->set(0);
mjr 6:cc35eb643e8f 532 }
mjr 6:cc35eb643e8f 533 }
mjr 6:cc35eb643e8f 534
mjr 29:582472d0bc57 535 // LedWiz output states.
mjr 29:582472d0bc57 536 //
mjr 29:582472d0bc57 537 // The LedWiz protocol has two separate control axes for each output.
mjr 29:582472d0bc57 538 // One axis is its on/off state; the other is its "profile" state, which
mjr 29:582472d0bc57 539 // is either a fixed brightness or a blinking pattern for the light.
mjr 29:582472d0bc57 540 // The two axes are independent.
mjr 29:582472d0bc57 541 //
mjr 29:582472d0bc57 542 // Note that the LedWiz protocol can only address 32 outputs, so the
mjr 29:582472d0bc57 543 // wizOn and wizVal arrays have fixed sizes of 32 elements no matter
mjr 29:582472d0bc57 544 // how many physical outputs we're using.
mjr 29:582472d0bc57 545
mjr 0:5acbbe3f4cf4 546 // on/off state for each LedWiz output
mjr 1:d913e0afb2ac 547 static uint8_t wizOn[32];
mjr 0:5acbbe3f4cf4 548
mjr 29:582472d0bc57 549 // Profile (brightness/blink) state for each LedWiz output. If the
mjr 29:582472d0bc57 550 // output was last updated through an LedWiz protocol message, it
mjr 29:582472d0bc57 551 // will have one of these values:
mjr 29:582472d0bc57 552 //
mjr 29:582472d0bc57 553 // 0-48 = fixed brightness 0% to 100%
mjr 29:582472d0bc57 554 // 129 = ramp up / ramp down
mjr 29:582472d0bc57 555 // 130 = flash on / off
mjr 29:582472d0bc57 556 // 131 = on / ramp down
mjr 29:582472d0bc57 557 // 132 = ramp up / on
mjr 29:582472d0bc57 558 //
mjr 29:582472d0bc57 559 // Special value 255: If the output was updated through the
mjr 29:582472d0bc57 560 // extended protocol, we'll set the wizVal entry to 255, which has
mjr 29:582472d0bc57 561 // no meaning in the LedWiz protocol. This tells us that the value
mjr 29:582472d0bc57 562 // in outLevel[] was set directly from the extended protocol, so it
mjr 29:582472d0bc57 563 // shouldn't be derived from wizVal[].
mjr 29:582472d0bc57 564 //
mjr 1:d913e0afb2ac 565 static uint8_t wizVal[32] = {
mjr 13:72dda449c3c0 566 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 567 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 568 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 569 48, 48, 48, 48, 48, 48, 48, 48
mjr 0:5acbbe3f4cf4 570 };
mjr 0:5acbbe3f4cf4 571
mjr 29:582472d0bc57 572 // LedWiz flash speed. This is a value from 1 to 7 giving the pulse
mjr 29:582472d0bc57 573 // rate for lights in blinking states.
mjr 29:582472d0bc57 574 static uint8_t wizSpeed = 2;
mjr 29:582472d0bc57 575
mjr 29:582472d0bc57 576 // Current LedWiz flash cycle counter.
mjr 29:582472d0bc57 577 static uint8_t wizFlashCounter = 0;
mjr 29:582472d0bc57 578
mjr 29:582472d0bc57 579 // Get the current brightness level for an LedWiz output.
mjr 1:d913e0afb2ac 580 static float wizState(int idx)
mjr 0:5acbbe3f4cf4 581 {
mjr 29:582472d0bc57 582 // if the output was last set with an extended protocol message,
mjr 29:582472d0bc57 583 // use the value set there, ignoring the output's LedWiz state
mjr 29:582472d0bc57 584 if (wizVal[idx] == 255)
mjr 29:582472d0bc57 585 return outLevel[idx];
mjr 29:582472d0bc57 586
mjr 29:582472d0bc57 587 // if it's off, show at zero intensity
mjr 29:582472d0bc57 588 if (!wizOn[idx])
mjr 29:582472d0bc57 589 return 0;
mjr 29:582472d0bc57 590
mjr 29:582472d0bc57 591 // check the state
mjr 29:582472d0bc57 592 uint8_t val = wizVal[idx];
mjr 29:582472d0bc57 593 if (val <= 48)
mjr 29:582472d0bc57 594 {
mjr 29:582472d0bc57 595 // PWM brightness/intensity level. Rescale from the LedWiz
mjr 29:582472d0bc57 596 // 0..48 integer range to our internal PwmOut 0..1 float range.
mjr 29:582472d0bc57 597 // Note that on the actual LedWiz, level 48 is actually about
mjr 29:582472d0bc57 598 // 98% on - contrary to the LedWiz documentation, level 49 is
mjr 29:582472d0bc57 599 // the true 100% level. (In the documentation, level 49 is
mjr 29:582472d0bc57 600 // simply not a valid setting.) Even so, we treat level 48 as
mjr 29:582472d0bc57 601 // 100% on to match the documentation. This won't be perfectly
mjr 29:582472d0bc57 602 // ocmpatible with the actual LedWiz, but it makes for such a
mjr 29:582472d0bc57 603 // small difference in brightness (if the output device is an
mjr 29:582472d0bc57 604 // LED, say) that no one should notice. It seems better to
mjr 29:582472d0bc57 605 // err in this direction, because while the difference in
mjr 29:582472d0bc57 606 // brightness when attached to an LED won't be noticeable, the
mjr 29:582472d0bc57 607 // difference in duty cycle when attached to something like a
mjr 29:582472d0bc57 608 // contactor *can* be noticeable - anything less than 100%
mjr 29:582472d0bc57 609 // can cause a contactor or relay to chatter. There's almost
mjr 29:582472d0bc57 610 // never a situation where you'd want values other than 0% and
mjr 29:582472d0bc57 611 // 100% for a contactor or relay, so treating level 48 as 100%
mjr 29:582472d0bc57 612 // makes us work properly with software that's expecting the
mjr 29:582472d0bc57 613 // documented LedWiz behavior and therefore uses level 48 to
mjr 29:582472d0bc57 614 // turn a contactor or relay fully on.
mjr 29:582472d0bc57 615 return val/48.0;
mjr 29:582472d0bc57 616 }
mjr 29:582472d0bc57 617 else if (val == 49)
mjr 13:72dda449c3c0 618 {
mjr 29:582472d0bc57 619 // 49 is undefined in the LedWiz documentation, but actually
mjr 29:582472d0bc57 620 // means 100% on. The documentation says that levels 1-48 are
mjr 29:582472d0bc57 621 // the full PWM range, but empirically it appears that the real
mjr 29:582472d0bc57 622 // range implemented in the firmware is 1-49. Some software on
mjr 29:582472d0bc57 623 // the PC side (notably DOF) is aware of this and uses level 49
mjr 29:582472d0bc57 624 // to mean "100% on". To ensure compatibility with existing
mjr 29:582472d0bc57 625 // PC-side software, we need to recognize level 49.
mjr 29:582472d0bc57 626 return 1.0;
mjr 29:582472d0bc57 627 }
mjr 29:582472d0bc57 628 else if (val == 129)
mjr 29:582472d0bc57 629 {
mjr 29:582472d0bc57 630 // 129 = ramp up / ramp down
mjr 30:6e9902f06f48 631 return wizFlashCounter < 128
mjr 30:6e9902f06f48 632 ? wizFlashCounter/128.0
mjr 30:6e9902f06f48 633 : (256 - wizFlashCounter)/128.0;
mjr 29:582472d0bc57 634 }
mjr 29:582472d0bc57 635 else if (val == 130)
mjr 29:582472d0bc57 636 {
mjr 29:582472d0bc57 637 // 130 = flash on / off
mjr 30:6e9902f06f48 638 return wizFlashCounter < 128 ? 1.0 : 0.0;
mjr 29:582472d0bc57 639 }
mjr 29:582472d0bc57 640 else if (val == 131)
mjr 29:582472d0bc57 641 {
mjr 29:582472d0bc57 642 // 131 = on / ramp down
mjr 30:6e9902f06f48 643 return wizFlashCounter < 128 ? 1.0 : (255 - wizFlashCounter)/128.0;
mjr 0:5acbbe3f4cf4 644 }
mjr 29:582472d0bc57 645 else if (val == 132)
mjr 29:582472d0bc57 646 {
mjr 29:582472d0bc57 647 // 132 = ramp up / on
mjr 30:6e9902f06f48 648 return wizFlashCounter < 128 ? wizFlashCounter/128.0 : 1.0;
mjr 29:582472d0bc57 649 }
mjr 29:582472d0bc57 650 else
mjr 13:72dda449c3c0 651 {
mjr 29:582472d0bc57 652 // Other values are undefined in the LedWiz documentation. Hosts
mjr 29:582472d0bc57 653 // *should* never send undefined values, since whatever behavior an
mjr 29:582472d0bc57 654 // LedWiz unit exhibits in response is accidental and could change
mjr 29:582472d0bc57 655 // in a future version. We'll treat all undefined values as equivalent
mjr 29:582472d0bc57 656 // to 48 (fully on).
mjr 29:582472d0bc57 657 return 1.0;
mjr 0:5acbbe3f4cf4 658 }
mjr 0:5acbbe3f4cf4 659 }
mjr 0:5acbbe3f4cf4 660
mjr 29:582472d0bc57 661 // LedWiz flash timer pulse. This fires periodically to update
mjr 29:582472d0bc57 662 // LedWiz flashing outputs. At the slowest pulse speed set via
mjr 29:582472d0bc57 663 // the SBA command, each waveform cycle has 256 steps, so we
mjr 29:582472d0bc57 664 // choose the pulse time base so that the slowest cycle completes
mjr 29:582472d0bc57 665 // in 2 seconds. This seems to roughly match the real LedWiz
mjr 29:582472d0bc57 666 // behavior. We run the pulse timer at the same rate regardless
mjr 29:582472d0bc57 667 // of the pulse speed; at higher pulse speeds, we simply use
mjr 29:582472d0bc57 668 // larger steps through the cycle on each interrupt. Running
mjr 29:582472d0bc57 669 // every 1/127 of a second = 8ms seems to be a pretty light load.
mjr 29:582472d0bc57 670 Timeout wizPulseTimer;
mjr 29:582472d0bc57 671 #define WIZ_PULSE_TIME_BASE (1.0/127.0)
mjr 29:582472d0bc57 672 static void wizPulse()
mjr 29:582472d0bc57 673 {
mjr 29:582472d0bc57 674 // increase the counter by the speed increment, and wrap at 256
mjr 29:582472d0bc57 675 wizFlashCounter += wizSpeed;
mjr 29:582472d0bc57 676 wizFlashCounter &= 0xff;
mjr 29:582472d0bc57 677
mjr 29:582472d0bc57 678 // if we have any flashing lights, update them
mjr 29:582472d0bc57 679 int ena = false;
mjr 35:e959ffba78fd 680 for (int i = 0 ; i < numLwOutputs ; ++i)
mjr 29:582472d0bc57 681 {
mjr 29:582472d0bc57 682 if (wizOn[i])
mjr 29:582472d0bc57 683 {
mjr 29:582472d0bc57 684 uint8_t s = wizVal[i];
mjr 29:582472d0bc57 685 if (s >= 129 && s <= 132)
mjr 29:582472d0bc57 686 {
mjr 29:582472d0bc57 687 lwPin[i]->set(wizState(i));
mjr 29:582472d0bc57 688 ena = true;
mjr 29:582472d0bc57 689 }
mjr 29:582472d0bc57 690 }
mjr 29:582472d0bc57 691 }
mjr 29:582472d0bc57 692
mjr 29:582472d0bc57 693 // Set up the next timer pulse only if we found anything flashing.
mjr 29:582472d0bc57 694 // To minimize overhead from this feature, we only enable the interrupt
mjr 29:582472d0bc57 695 // when we need it. This eliminates any performance penalty to other
mjr 29:582472d0bc57 696 // features when the host software doesn't care about the flashing
mjr 29:582472d0bc57 697 // modes. For example, DOF never uses these modes, so there's no
mjr 29:582472d0bc57 698 // need for them when running Visual Pinball.
mjr 29:582472d0bc57 699 if (ena)
mjr 29:582472d0bc57 700 wizPulseTimer.attach(wizPulse, WIZ_PULSE_TIME_BASE);
mjr 29:582472d0bc57 701 }
mjr 29:582472d0bc57 702
mjr 29:582472d0bc57 703 // Update the physical outputs connected to the LedWiz ports. This is
mjr 29:582472d0bc57 704 // called after any update from an LedWiz protocol message.
mjr 1:d913e0afb2ac 705 static void updateWizOuts()
mjr 1:d913e0afb2ac 706 {
mjr 29:582472d0bc57 707 // update each output
mjr 29:582472d0bc57 708 int pulse = false;
mjr 35:e959ffba78fd 709 for (int i = 0 ; i < numLwOutputs ; ++i)
mjr 29:582472d0bc57 710 {
mjr 29:582472d0bc57 711 pulse |= (wizVal[i] >= 129 && wizVal[i] <= 132);
mjr 6:cc35eb643e8f 712 lwPin[i]->set(wizState(i));
mjr 29:582472d0bc57 713 }
mjr 29:582472d0bc57 714
mjr 29:582472d0bc57 715 // if any outputs are set to flashing mode, and the pulse timer
mjr 29:582472d0bc57 716 // isn't running, turn it on
mjr 29:582472d0bc57 717 if (pulse)
mjr 29:582472d0bc57 718 wizPulseTimer.attach(wizPulse, WIZ_PULSE_TIME_BASE);
mjr 34:6b981a2afab7 719
mjr 34:6b981a2afab7 720 // flush changes to 74HC595 chips, if attached
mjr 35:e959ffba78fd 721 if (hc595 != 0)
mjr 35:e959ffba78fd 722 hc595->update();
mjr 1:d913e0afb2ac 723 }
mjr 34:6b981a2afab7 724
mjr 11:bd9da7088e6e 725 // ---------------------------------------------------------------------------
mjr 11:bd9da7088e6e 726 //
mjr 11:bd9da7088e6e 727 // Button input
mjr 11:bd9da7088e6e 728 //
mjr 11:bd9da7088e6e 729
mjr 18:5e890ebd0023 730 // button state
mjr 18:5e890ebd0023 731 struct ButtonState
mjr 18:5e890ebd0023 732 {
mjr 35:e959ffba78fd 733 ButtonState() : di(NULL), pressed(0), t(0), js(0), keymod(0), keycode(0) { }
mjr 35:e959ffba78fd 734
mjr 35:e959ffba78fd 735 // DigitalIn for the button
mjr 35:e959ffba78fd 736 DigitalIn *di;
mjr 35:e959ffba78fd 737
mjr 18:5e890ebd0023 738 // current on/off state
mjr 18:5e890ebd0023 739 int pressed;
mjr 18:5e890ebd0023 740
mjr 18:5e890ebd0023 741 // Sticky time remaining for current state. When a
mjr 18:5e890ebd0023 742 // state transition occurs, we set this to a debounce
mjr 18:5e890ebd0023 743 // period. Future state transitions will be ignored
mjr 18:5e890ebd0023 744 // until the debounce time elapses.
mjr 35:e959ffba78fd 745 float t;
mjr 35:e959ffba78fd 746
mjr 35:e959ffba78fd 747 // joystick button mask for the button, if mapped as a joystick button
mjr 35:e959ffba78fd 748 uint32_t js;
mjr 35:e959ffba78fd 749
mjr 35:e959ffba78fd 750 // keyboard modifier bits and scan code for the button, if mapped as a keyboard key
mjr 35:e959ffba78fd 751 uint8_t keymod;
mjr 35:e959ffba78fd 752 uint8_t keycode;
mjr 35:e959ffba78fd 753
mjr 35:e959ffba78fd 754 // media control key code
mjr 35:e959ffba78fd 755 uint8_t mediakey;
mjr 35:e959ffba78fd 756
mjr 35:e959ffba78fd 757
mjr 35:e959ffba78fd 758 } buttonState[MAX_BUTTONS];
mjr 18:5e890ebd0023 759
mjr 12:669df364a565 760 // timer for button reports
mjr 12:669df364a565 761 static Timer buttonTimer;
mjr 12:669df364a565 762
mjr 11:bd9da7088e6e 763 // initialize the button inputs
mjr 35:e959ffba78fd 764 void initButtons(Config &cfg, bool &kbKeys)
mjr 11:bd9da7088e6e 765 {
mjr 35:e959ffba78fd 766 // presume we'll find no keyboard keys
mjr 35:e959ffba78fd 767 kbKeys = false;
mjr 35:e959ffba78fd 768
mjr 11:bd9da7088e6e 769 // create the digital inputs
mjr 35:e959ffba78fd 770 ButtonState *bs = buttonState;
mjr 35:e959ffba78fd 771 for (int i = 0 ; i < MAX_BUTTONS ; ++i, ++bs)
mjr 11:bd9da7088e6e 772 {
mjr 35:e959ffba78fd 773 PinName pin = wirePinName(cfg.button[i].pin);
mjr 35:e959ffba78fd 774 if (pin != NC)
mjr 35:e959ffba78fd 775 {
mjr 35:e959ffba78fd 776 // set up the GPIO input pin for this button
mjr 35:e959ffba78fd 777 bs->di = new DigitalIn(pin);
mjr 35:e959ffba78fd 778
mjr 35:e959ffba78fd 779 // note if it's a keyboard key of some kind (including media keys)
mjr 35:e959ffba78fd 780 uint8_t val = cfg.button[i].val;
mjr 35:e959ffba78fd 781 switch (cfg.button[i].typ)
mjr 35:e959ffba78fd 782 {
mjr 35:e959ffba78fd 783 case BtnTypeJoystick:
mjr 35:e959ffba78fd 784 // joystick button - get the button bit mask
mjr 35:e959ffba78fd 785 bs->js = 1 << val;
mjr 35:e959ffba78fd 786 break;
mjr 35:e959ffba78fd 787
mjr 35:e959ffba78fd 788 case BtnTypeKey:
mjr 35:e959ffba78fd 789 // regular keyboard key - note the scan code
mjr 35:e959ffba78fd 790 bs->keycode = val;
mjr 35:e959ffba78fd 791 kbKeys = true;
mjr 35:e959ffba78fd 792 break;
mjr 35:e959ffba78fd 793
mjr 35:e959ffba78fd 794 case BtnTypeModKey:
mjr 35:e959ffba78fd 795 // keyboard mod key - note the modifier mask
mjr 35:e959ffba78fd 796 bs->keymod = val;
mjr 35:e959ffba78fd 797 kbKeys = true;
mjr 35:e959ffba78fd 798 break;
mjr 35:e959ffba78fd 799
mjr 35:e959ffba78fd 800 case BtnTypeMedia:
mjr 35:e959ffba78fd 801 // media key - note the code
mjr 35:e959ffba78fd 802 bs->mediakey = val;
mjr 35:e959ffba78fd 803 kbKeys = true;
mjr 35:e959ffba78fd 804 break;
mjr 35:e959ffba78fd 805 }
mjr 35:e959ffba78fd 806 }
mjr 11:bd9da7088e6e 807 }
mjr 12:669df364a565 808
mjr 12:669df364a565 809 // start the button timer
mjr 35:e959ffba78fd 810 buttonTimer.reset();
mjr 12:669df364a565 811 buttonTimer.start();
mjr 11:bd9da7088e6e 812 }
mjr 11:bd9da7088e6e 813
mjr 35:e959ffba78fd 814 // Button data
mjr 35:e959ffba78fd 815 uint32_t jsButtons = 0;
mjr 35:e959ffba78fd 816
mjr 35:e959ffba78fd 817 // Keyboard state
mjr 35:e959ffba78fd 818 struct
mjr 35:e959ffba78fd 819 {
mjr 35:e959ffba78fd 820 bool changed; // flag: changed since last report sent
mjr 35:e959ffba78fd 821 int nkeys; // number of active keys in the list
mjr 35:e959ffba78fd 822 uint8_t data[8]; // key state, in USB report format: byte 0 is the modifier key mask,
mjr 35:e959ffba78fd 823 // byte 1 is reserved, and bytes 2-7 are the currently pressed key codes
mjr 35:e959ffba78fd 824 } kbState = { false, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } };
mjr 35:e959ffba78fd 825
mjr 35:e959ffba78fd 826 // Media key state
mjr 35:e959ffba78fd 827 struct
mjr 35:e959ffba78fd 828 {
mjr 35:e959ffba78fd 829 bool changed; // flag: changed since last report sent
mjr 35:e959ffba78fd 830 uint8_t data; // key state byte for USB reports
mjr 35:e959ffba78fd 831 } mediaState = { false, 0 };
mjr 11:bd9da7088e6e 832
mjr 18:5e890ebd0023 833 // read the button input state
mjr 35:e959ffba78fd 834 void readButtons(Config &cfg)
mjr 11:bd9da7088e6e 835 {
mjr 35:e959ffba78fd 836 // start with an empty list of USB key codes
mjr 35:e959ffba78fd 837 uint8_t modkeys = 0;
mjr 35:e959ffba78fd 838 uint8_t keys[7] = { 0, 0, 0, 0, 0, 0, 0 };
mjr 35:e959ffba78fd 839 int nkeys = 0;
mjr 11:bd9da7088e6e 840
mjr 35:e959ffba78fd 841 // clear the joystick buttons
mjr 35:e959ffba78fd 842 jsButtons = 0;
mjr 35:e959ffba78fd 843
mjr 35:e959ffba78fd 844 // start with no media keys pressed
mjr 35:e959ffba78fd 845 uint8_t mediakeys = 0;
mjr 35:e959ffba78fd 846
mjr 18:5e890ebd0023 847 // figure the time elapsed since the last scan
mjr 35:e959ffba78fd 848 float dt = buttonTimer.read();
mjr 18:5e890ebd0023 849
mjr 35:e959ffba78fd 850 // reset the time for the next scan
mjr 18:5e890ebd0023 851 buttonTimer.reset();
mjr 18:5e890ebd0023 852
mjr 11:bd9da7088e6e 853 // scan the button list
mjr 18:5e890ebd0023 854 ButtonState *bs = buttonState;
mjr 35:e959ffba78fd 855 for (int i = 0 ; i < MAX_BUTTONS ; ++i, ++bs)
mjr 11:bd9da7088e6e 856 {
mjr 18:5e890ebd0023 857 // read this button
mjr 35:e959ffba78fd 858 if (bs->di != 0)
mjr 18:5e890ebd0023 859 {
mjr 18:5e890ebd0023 860 // deduct the elapsed time since the last update
mjr 18:5e890ebd0023 861 // from the button's remaining sticky time
mjr 18:5e890ebd0023 862 bs->t -= dt;
mjr 18:5e890ebd0023 863 if (bs->t < 0)
mjr 18:5e890ebd0023 864 bs->t = 0;
mjr 18:5e890ebd0023 865
mjr 18:5e890ebd0023 866 // If the sticky time has elapsed, note the new physical
mjr 18:5e890ebd0023 867 // state of the button. If we still have sticky time
mjr 18:5e890ebd0023 868 // remaining, ignore the physical state; the last state
mjr 18:5e890ebd0023 869 // change persists until the sticky time elapses so that
mjr 18:5e890ebd0023 870 // we smooth out any "bounce" (electrical transients that
mjr 18:5e890ebd0023 871 // occur when the switch contact is opened or closed).
mjr 18:5e890ebd0023 872 if (bs->t == 0)
mjr 18:5e890ebd0023 873 {
mjr 18:5e890ebd0023 874 // get the new physical state
mjr 35:e959ffba78fd 875 int pressed = !bs->di->read();
mjr 18:5e890ebd0023 876
mjr 18:5e890ebd0023 877 // update the button's logical state if this is a change
mjr 18:5e890ebd0023 878 if (pressed != bs->pressed)
mjr 18:5e890ebd0023 879 {
mjr 18:5e890ebd0023 880 // store the new state
mjr 18:5e890ebd0023 881 bs->pressed = pressed;
mjr 18:5e890ebd0023 882
mjr 18:5e890ebd0023 883 // start a new sticky period for debouncing this
mjr 18:5e890ebd0023 884 // state change
mjr 35:e959ffba78fd 885 bs->t = 0.005;
mjr 18:5e890ebd0023 886 }
mjr 18:5e890ebd0023 887 }
mjr 35:e959ffba78fd 888
mjr 35:e959ffba78fd 889 // if it's pressed, add it to the appropriate key state list
mjr 18:5e890ebd0023 890 if (bs->pressed)
mjr 35:e959ffba78fd 891 {
mjr 35:e959ffba78fd 892 // OR in the joystick button bit, mod key bits, and media key bits
mjr 35:e959ffba78fd 893 jsButtons |= bs->js;
mjr 35:e959ffba78fd 894 modkeys |= bs->keymod;
mjr 35:e959ffba78fd 895 mediakeys |= bs->mediakey;
mjr 35:e959ffba78fd 896
mjr 35:e959ffba78fd 897 // if it has a keyboard key, add the scan code to the active list
mjr 35:e959ffba78fd 898 if (bs->keycode != 0 && nkeys < 7)
mjr 35:e959ffba78fd 899 keys[nkeys++] = bs->keycode;
mjr 35:e959ffba78fd 900 }
mjr 18:5e890ebd0023 901 }
mjr 11:bd9da7088e6e 902 }
mjr 11:bd9da7088e6e 903
mjr 35:e959ffba78fd 904 // Check for changes to the keyboard keys
mjr 35:e959ffba78fd 905 if (kbState.data[0] != modkeys
mjr 35:e959ffba78fd 906 || kbState.nkeys != nkeys
mjr 35:e959ffba78fd 907 || memcmp(keys, &kbState.data[2], 6) != 0)
mjr 35:e959ffba78fd 908 {
mjr 35:e959ffba78fd 909 // we have changes - set the change flag and store the new key data
mjr 35:e959ffba78fd 910 kbState.changed = true;
mjr 35:e959ffba78fd 911 kbState.data[0] = modkeys;
mjr 35:e959ffba78fd 912 if (nkeys <= 6) {
mjr 35:e959ffba78fd 913 // 6 or fewer simultaneous keys - report the key codes
mjr 35:e959ffba78fd 914 kbState.nkeys = nkeys;
mjr 35:e959ffba78fd 915 memcpy(&kbState.data[2], keys, 6);
mjr 35:e959ffba78fd 916 }
mjr 35:e959ffba78fd 917 else {
mjr 35:e959ffba78fd 918 // more than 6 simultaneous keys - report rollover (all '1' key codes)
mjr 35:e959ffba78fd 919 kbState.nkeys = 6;
mjr 35:e959ffba78fd 920 memset(&kbState.data[2], 1, 6);
mjr 35:e959ffba78fd 921 }
mjr 35:e959ffba78fd 922 }
mjr 35:e959ffba78fd 923
mjr 35:e959ffba78fd 924 // Check for changes to media keys
mjr 35:e959ffba78fd 925 if (mediaState.data != mediakeys)
mjr 35:e959ffba78fd 926 {
mjr 35:e959ffba78fd 927 mediaState.changed = true;
mjr 35:e959ffba78fd 928 mediaState.data = mediakeys;
mjr 35:e959ffba78fd 929 }
mjr 11:bd9da7088e6e 930 }
mjr 11:bd9da7088e6e 931
mjr 5:a70c0bce770d 932 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 933 //
mjr 5:a70c0bce770d 934 // Customization joystick subbclass
mjr 5:a70c0bce770d 935 //
mjr 5:a70c0bce770d 936
mjr 5:a70c0bce770d 937 class MyUSBJoystick: public USBJoystick
mjr 5:a70c0bce770d 938 {
mjr 5:a70c0bce770d 939 public:
mjr 35:e959ffba78fd 940 MyUSBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release,
mjr 35:e959ffba78fd 941 bool waitForConnect, bool enableJoystick, bool useKB)
mjr 35:e959ffba78fd 942 : USBJoystick(vendor_id, product_id, product_release, waitForConnect, enableJoystick, useKB)
mjr 5:a70c0bce770d 943 {
mjr 5:a70c0bce770d 944 suspended_ = false;
mjr 5:a70c0bce770d 945 }
mjr 5:a70c0bce770d 946
mjr 5:a70c0bce770d 947 // are we connected?
mjr 5:a70c0bce770d 948 int isConnected() { return configured(); }
mjr 5:a70c0bce770d 949
mjr 5:a70c0bce770d 950 // Are we in suspend mode?
mjr 5:a70c0bce770d 951 int isSuspended() const { return suspended_; }
mjr 5:a70c0bce770d 952
mjr 5:a70c0bce770d 953 protected:
mjr 5:a70c0bce770d 954 virtual void suspendStateChanged(unsigned int suspended)
mjr 5:a70c0bce770d 955 { suspended_ = suspended; }
mjr 5:a70c0bce770d 956
mjr 5:a70c0bce770d 957 // are we suspended?
mjr 5:a70c0bce770d 958 int suspended_;
mjr 5:a70c0bce770d 959 };
mjr 5:a70c0bce770d 960
mjr 5:a70c0bce770d 961 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 962 //
mjr 5:a70c0bce770d 963 // Accelerometer (MMA8451Q)
mjr 5:a70c0bce770d 964 //
mjr 5:a70c0bce770d 965
mjr 5:a70c0bce770d 966 // The MMA8451Q is the KL25Z's on-board 3-axis accelerometer.
mjr 5:a70c0bce770d 967 //
mjr 5:a70c0bce770d 968 // This is a custom wrapper for the library code to interface to the
mjr 6:cc35eb643e8f 969 // MMA8451Q. This class encapsulates an interrupt handler and
mjr 6:cc35eb643e8f 970 // automatic calibration.
mjr 5:a70c0bce770d 971 //
mjr 5:a70c0bce770d 972 // We install an interrupt handler on the accelerometer "data ready"
mjr 6:cc35eb643e8f 973 // interrupt to ensure that we fetch each sample immediately when it
mjr 6:cc35eb643e8f 974 // becomes available. The accelerometer data rate is fiarly high
mjr 6:cc35eb643e8f 975 // (800 Hz), so it's not practical to keep up with it by polling.
mjr 6:cc35eb643e8f 976 // Using an interrupt handler lets us respond quickly and read
mjr 6:cc35eb643e8f 977 // every sample.
mjr 5:a70c0bce770d 978 //
mjr 6:cc35eb643e8f 979 // We automatically calibrate the accelerometer so that it's not
mjr 6:cc35eb643e8f 980 // necessary to get it exactly level when installing it, and so
mjr 6:cc35eb643e8f 981 // that it's also not necessary to calibrate it manually. There's
mjr 6:cc35eb643e8f 982 // lots of experience that tells us that manual calibration is a
mjr 6:cc35eb643e8f 983 // terrible solution, mostly because cabinets tend to shift slightly
mjr 6:cc35eb643e8f 984 // during use, requiring frequent recalibration. Instead, we
mjr 6:cc35eb643e8f 985 // calibrate automatically. We continuously monitor the acceleration
mjr 6:cc35eb643e8f 986 // data, watching for periods of constant (or nearly constant) values.
mjr 6:cc35eb643e8f 987 // Any time it appears that the machine has been at rest for a while
mjr 6:cc35eb643e8f 988 // (about 5 seconds), we'll average the readings during that rest
mjr 6:cc35eb643e8f 989 // period and use the result as the level rest position. This is
mjr 6:cc35eb643e8f 990 // is ongoing, so we'll quickly find the center point again if the
mjr 6:cc35eb643e8f 991 // machine is moved during play (by an especially aggressive bout
mjr 6:cc35eb643e8f 992 // of nudging, say).
mjr 5:a70c0bce770d 993 //
mjr 5:a70c0bce770d 994
mjr 17:ab3cec0c8bf4 995 // I2C address of the accelerometer (this is a constant of the KL25Z)
mjr 17:ab3cec0c8bf4 996 const int MMA8451_I2C_ADDRESS = (0x1d<<1);
mjr 17:ab3cec0c8bf4 997
mjr 17:ab3cec0c8bf4 998 // SCL and SDA pins for the accelerometer (constant for the KL25Z)
mjr 17:ab3cec0c8bf4 999 #define MMA8451_SCL_PIN PTE25
mjr 17:ab3cec0c8bf4 1000 #define MMA8451_SDA_PIN PTE24
mjr 17:ab3cec0c8bf4 1001
mjr 17:ab3cec0c8bf4 1002 // Digital in pin to use for the accelerometer interrupt. For the KL25Z,
mjr 17:ab3cec0c8bf4 1003 // this can be either PTA14 or PTA15, since those are the pins physically
mjr 17:ab3cec0c8bf4 1004 // wired on this board to the MMA8451 interrupt controller.
mjr 17:ab3cec0c8bf4 1005 #define MMA8451_INT_PIN PTA15
mjr 17:ab3cec0c8bf4 1006
mjr 17:ab3cec0c8bf4 1007
mjr 6:cc35eb643e8f 1008 // accelerometer input history item, for gathering calibration data
mjr 6:cc35eb643e8f 1009 struct AccHist
mjr 5:a70c0bce770d 1010 {
mjr 6:cc35eb643e8f 1011 AccHist() { x = y = d = 0.0; xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 1012 void set(float x, float y, AccHist *prv)
mjr 6:cc35eb643e8f 1013 {
mjr 6:cc35eb643e8f 1014 // save the raw position
mjr 6:cc35eb643e8f 1015 this->x = x;
mjr 6:cc35eb643e8f 1016 this->y = y;
mjr 6:cc35eb643e8f 1017 this->d = distance(prv);
mjr 6:cc35eb643e8f 1018 }
mjr 6:cc35eb643e8f 1019
mjr 6:cc35eb643e8f 1020 // reading for this entry
mjr 5:a70c0bce770d 1021 float x, y;
mjr 5:a70c0bce770d 1022
mjr 6:cc35eb643e8f 1023 // distance from previous entry
mjr 6:cc35eb643e8f 1024 float d;
mjr 5:a70c0bce770d 1025
mjr 6:cc35eb643e8f 1026 // total and count of samples averaged over this period
mjr 6:cc35eb643e8f 1027 float xtot, ytot;
mjr 6:cc35eb643e8f 1028 int cnt;
mjr 6:cc35eb643e8f 1029
mjr 6:cc35eb643e8f 1030 void clearAvg() { xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 1031 void addAvg(float x, float y) { xtot += x; ytot += y; ++cnt; }
mjr 6:cc35eb643e8f 1032 float xAvg() const { return xtot/cnt; }
mjr 6:cc35eb643e8f 1033 float yAvg() const { return ytot/cnt; }
mjr 5:a70c0bce770d 1034
mjr 6:cc35eb643e8f 1035 float distance(AccHist *p)
mjr 6:cc35eb643e8f 1036 { return sqrt(square(p->x - x) + square(p->y - y)); }
mjr 5:a70c0bce770d 1037 };
mjr 5:a70c0bce770d 1038
mjr 5:a70c0bce770d 1039 // accelerometer wrapper class
mjr 3:3514575d4f86 1040 class Accel
mjr 3:3514575d4f86 1041 {
mjr 3:3514575d4f86 1042 public:
mjr 3:3514575d4f86 1043 Accel(PinName sda, PinName scl, int i2cAddr, PinName irqPin)
mjr 3:3514575d4f86 1044 : mma_(sda, scl, i2cAddr), intIn_(irqPin)
mjr 3:3514575d4f86 1045 {
mjr 5:a70c0bce770d 1046 // remember the interrupt pin assignment
mjr 5:a70c0bce770d 1047 irqPin_ = irqPin;
mjr 5:a70c0bce770d 1048
mjr 5:a70c0bce770d 1049 // reset and initialize
mjr 5:a70c0bce770d 1050 reset();
mjr 5:a70c0bce770d 1051 }
mjr 5:a70c0bce770d 1052
mjr 5:a70c0bce770d 1053 void reset()
mjr 5:a70c0bce770d 1054 {
mjr 6:cc35eb643e8f 1055 // clear the center point
mjr 6:cc35eb643e8f 1056 cx_ = cy_ = 0.0;
mjr 6:cc35eb643e8f 1057
mjr 6:cc35eb643e8f 1058 // start the calibration timer
mjr 5:a70c0bce770d 1059 tCenter_.start();
mjr 5:a70c0bce770d 1060 iAccPrv_ = nAccPrv_ = 0;
mjr 6:cc35eb643e8f 1061
mjr 5:a70c0bce770d 1062 // reset and initialize the MMA8451Q
mjr 5:a70c0bce770d 1063 mma_.init();
mjr 6:cc35eb643e8f 1064
mjr 6:cc35eb643e8f 1065 // set the initial integrated velocity reading to zero
mjr 6:cc35eb643e8f 1066 vx_ = vy_ = 0;
mjr 3:3514575d4f86 1067
mjr 6:cc35eb643e8f 1068 // set up our accelerometer interrupt handling
mjr 6:cc35eb643e8f 1069 intIn_.rise(this, &Accel::isr);
mjr 5:a70c0bce770d 1070 mma_.setInterruptMode(irqPin_ == PTA14 ? 1 : 2);
mjr 3:3514575d4f86 1071
mjr 3:3514575d4f86 1072 // read the current registers to clear the data ready flag
mjr 6:cc35eb643e8f 1073 mma_.getAccXYZ(ax_, ay_, az_);
mjr 3:3514575d4f86 1074
mjr 3:3514575d4f86 1075 // start our timers
mjr 3:3514575d4f86 1076 tGet_.start();
mjr 3:3514575d4f86 1077 tInt_.start();
mjr 3:3514575d4f86 1078 }
mjr 3:3514575d4f86 1079
mjr 9:fd65b0a94720 1080 void get(int &x, int &y)
mjr 3:3514575d4f86 1081 {
mjr 3:3514575d4f86 1082 // disable interrupts while manipulating the shared data
mjr 3:3514575d4f86 1083 __disable_irq();
mjr 3:3514575d4f86 1084
mjr 3:3514575d4f86 1085 // read the shared data and store locally for calculations
mjr 6:cc35eb643e8f 1086 float ax = ax_, ay = ay_;
mjr 6:cc35eb643e8f 1087 float vx = vx_, vy = vy_;
mjr 5:a70c0bce770d 1088
mjr 6:cc35eb643e8f 1089 // reset the velocity sum for the next run
mjr 6:cc35eb643e8f 1090 vx_ = vy_ = 0;
mjr 3:3514575d4f86 1091
mjr 3:3514575d4f86 1092 // get the time since the last get() sample
mjr 3:3514575d4f86 1093 float dt = tGet_.read_us()/1.0e6;
mjr 3:3514575d4f86 1094 tGet_.reset();
mjr 3:3514575d4f86 1095
mjr 3:3514575d4f86 1096 // done manipulating the shared data
mjr 3:3514575d4f86 1097 __enable_irq();
mjr 3:3514575d4f86 1098
mjr 6:cc35eb643e8f 1099 // adjust the readings for the integration time
mjr 6:cc35eb643e8f 1100 vx /= dt;
mjr 6:cc35eb643e8f 1101 vy /= dt;
mjr 6:cc35eb643e8f 1102
mjr 6:cc35eb643e8f 1103 // add this sample to the current calibration interval's running total
mjr 6:cc35eb643e8f 1104 AccHist *p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 1105 p->addAvg(ax, ay);
mjr 6:cc35eb643e8f 1106
mjr 5:a70c0bce770d 1107 // check for auto-centering every so often
mjr 5:a70c0bce770d 1108 if (tCenter_.read_ms() > 1000)
mjr 5:a70c0bce770d 1109 {
mjr 5:a70c0bce770d 1110 // add the latest raw sample to the history list
mjr 6:cc35eb643e8f 1111 AccHist *prv = p;
mjr 5:a70c0bce770d 1112 iAccPrv_ = (iAccPrv_ + 1) % maxAccPrv;
mjr 6:cc35eb643e8f 1113 p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 1114 p->set(ax, ay, prv);
mjr 5:a70c0bce770d 1115
mjr 5:a70c0bce770d 1116 // if we have a full complement, check for stability
mjr 5:a70c0bce770d 1117 if (nAccPrv_ >= maxAccPrv)
mjr 5:a70c0bce770d 1118 {
mjr 5:a70c0bce770d 1119 // check if we've been stable for all recent samples
mjr 6:cc35eb643e8f 1120 static const float accTol = .01;
mjr 6:cc35eb643e8f 1121 AccHist *p0 = accPrv_;
mjr 6:cc35eb643e8f 1122 if (p0[0].d < accTol
mjr 6:cc35eb643e8f 1123 && p0[1].d < accTol
mjr 6:cc35eb643e8f 1124 && p0[2].d < accTol
mjr 6:cc35eb643e8f 1125 && p0[3].d < accTol
mjr 6:cc35eb643e8f 1126 && p0[4].d < accTol)
mjr 5:a70c0bce770d 1127 {
mjr 6:cc35eb643e8f 1128 // Figure the new calibration point as the average of
mjr 6:cc35eb643e8f 1129 // the samples over the rest period
mjr 6:cc35eb643e8f 1130 cx_ = (p0[0].xAvg() + p0[1].xAvg() + p0[2].xAvg() + p0[3].xAvg() + p0[4].xAvg())/5.0;
mjr 6:cc35eb643e8f 1131 cy_ = (p0[0].yAvg() + p0[1].yAvg() + p0[2].yAvg() + p0[3].yAvg() + p0[4].yAvg())/5.0;
mjr 5:a70c0bce770d 1132 }
mjr 5:a70c0bce770d 1133 }
mjr 5:a70c0bce770d 1134 else
mjr 5:a70c0bce770d 1135 {
mjr 5:a70c0bce770d 1136 // not enough samples yet; just up the count
mjr 5:a70c0bce770d 1137 ++nAccPrv_;
mjr 5:a70c0bce770d 1138 }
mjr 6:cc35eb643e8f 1139
mjr 6:cc35eb643e8f 1140 // clear the new item's running totals
mjr 6:cc35eb643e8f 1141 p->clearAvg();
mjr 5:a70c0bce770d 1142
mjr 5:a70c0bce770d 1143 // reset the timer
mjr 5:a70c0bce770d 1144 tCenter_.reset();
mjr 5:a70c0bce770d 1145 }
mjr 5:a70c0bce770d 1146
mjr 6:cc35eb643e8f 1147 // report our integrated velocity reading in x,y
mjr 6:cc35eb643e8f 1148 x = rawToReport(vx);
mjr 6:cc35eb643e8f 1149 y = rawToReport(vy);
mjr 5:a70c0bce770d 1150
mjr 6:cc35eb643e8f 1151 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 1152 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 1153 printf("%f %f %d %d %f\r\n", vx, vy, x, y, dt);
mjr 6:cc35eb643e8f 1154 #endif
mjr 3:3514575d4f86 1155 }
mjr 29:582472d0bc57 1156
mjr 3:3514575d4f86 1157 private:
mjr 6:cc35eb643e8f 1158 // adjust a raw acceleration figure to a usb report value
mjr 6:cc35eb643e8f 1159 int rawToReport(float v)
mjr 5:a70c0bce770d 1160 {
mjr 6:cc35eb643e8f 1161 // scale to the joystick report range and round to integer
mjr 6:cc35eb643e8f 1162 int i = int(round(v*JOYMAX));
mjr 5:a70c0bce770d 1163
mjr 6:cc35eb643e8f 1164 // if it's near the center, scale it roughly as 20*(i/20)^2,
mjr 6:cc35eb643e8f 1165 // to suppress noise near the rest position
mjr 6:cc35eb643e8f 1166 static const int filter[] = {
mjr 6:cc35eb643e8f 1167 -18, -16, -14, -13, -11, -10, -8, -7, -6, -5, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0,
mjr 6:cc35eb643e8f 1168 0,
mjr 6:cc35eb643e8f 1169 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 16, 18
mjr 6:cc35eb643e8f 1170 };
mjr 6:cc35eb643e8f 1171 return (i > 20 || i < -20 ? i : filter[i+20]);
mjr 5:a70c0bce770d 1172 }
mjr 5:a70c0bce770d 1173
mjr 3:3514575d4f86 1174 // interrupt handler
mjr 3:3514575d4f86 1175 void isr()
mjr 3:3514575d4f86 1176 {
mjr 3:3514575d4f86 1177 // Read the axes. Note that we have to read all three axes
mjr 3:3514575d4f86 1178 // (even though we only really use x and y) in order to clear
mjr 3:3514575d4f86 1179 // the "data ready" status bit in the accelerometer. The
mjr 3:3514575d4f86 1180 // interrupt only occurs when the "ready" bit transitions from
mjr 3:3514575d4f86 1181 // off to on, so we have to make sure it's off.
mjr 5:a70c0bce770d 1182 float x, y, z;
mjr 5:a70c0bce770d 1183 mma_.getAccXYZ(x, y, z);
mjr 3:3514575d4f86 1184
mjr 3:3514575d4f86 1185 // calculate the time since the last interrupt
mjr 3:3514575d4f86 1186 float dt = tInt_.read_us()/1.0e6;
mjr 3:3514575d4f86 1187 tInt_.reset();
mjr 6:cc35eb643e8f 1188
mjr 6:cc35eb643e8f 1189 // integrate the time slice from the previous reading to this reading
mjr 6:cc35eb643e8f 1190 vx_ += (x + ax_ - 2*cx_)*dt/2;
mjr 6:cc35eb643e8f 1191 vy_ += (y + ay_ - 2*cy_)*dt/2;
mjr 3:3514575d4f86 1192
mjr 6:cc35eb643e8f 1193 // store the updates
mjr 6:cc35eb643e8f 1194 ax_ = x;
mjr 6:cc35eb643e8f 1195 ay_ = y;
mjr 6:cc35eb643e8f 1196 az_ = z;
mjr 3:3514575d4f86 1197 }
mjr 3:3514575d4f86 1198
mjr 3:3514575d4f86 1199 // underlying accelerometer object
mjr 3:3514575d4f86 1200 MMA8451Q mma_;
mjr 3:3514575d4f86 1201
mjr 5:a70c0bce770d 1202 // last raw acceleration readings
mjr 6:cc35eb643e8f 1203 float ax_, ay_, az_;
mjr 5:a70c0bce770d 1204
mjr 6:cc35eb643e8f 1205 // integrated velocity reading since last get()
mjr 6:cc35eb643e8f 1206 float vx_, vy_;
mjr 6:cc35eb643e8f 1207
mjr 3:3514575d4f86 1208 // timer for measuring time between get() samples
mjr 3:3514575d4f86 1209 Timer tGet_;
mjr 3:3514575d4f86 1210
mjr 3:3514575d4f86 1211 // timer for measuring time between interrupts
mjr 3:3514575d4f86 1212 Timer tInt_;
mjr 5:a70c0bce770d 1213
mjr 6:cc35eb643e8f 1214 // Calibration reference point for accelerometer. This is the
mjr 6:cc35eb643e8f 1215 // average reading on the accelerometer when in the neutral position
mjr 6:cc35eb643e8f 1216 // at rest.
mjr 6:cc35eb643e8f 1217 float cx_, cy_;
mjr 5:a70c0bce770d 1218
mjr 5:a70c0bce770d 1219 // timer for atuo-centering
mjr 5:a70c0bce770d 1220 Timer tCenter_;
mjr 6:cc35eb643e8f 1221
mjr 6:cc35eb643e8f 1222 // Auto-centering history. This is a separate history list that
mjr 6:cc35eb643e8f 1223 // records results spaced out sparesely over time, so that we can
mjr 6:cc35eb643e8f 1224 // watch for long-lasting periods of rest. When we observe nearly
mjr 6:cc35eb643e8f 1225 // no motion for an extended period (on the order of 5 seconds), we
mjr 6:cc35eb643e8f 1226 // take this to mean that the cabinet is at rest in its neutral
mjr 6:cc35eb643e8f 1227 // position, so we take this as the calibration zero point for the
mjr 6:cc35eb643e8f 1228 // accelerometer. We update this history continuously, which allows
mjr 6:cc35eb643e8f 1229 // us to continuously re-calibrate the accelerometer. This ensures
mjr 6:cc35eb643e8f 1230 // that we'll automatically adjust to any actual changes in the
mjr 6:cc35eb643e8f 1231 // cabinet's orientation (e.g., if it gets moved slightly by an
mjr 6:cc35eb643e8f 1232 // especially strong nudge) as well as any systematic drift in the
mjr 6:cc35eb643e8f 1233 // accelerometer measurement bias (e.g., from temperature changes).
mjr 5:a70c0bce770d 1234 int iAccPrv_, nAccPrv_;
mjr 5:a70c0bce770d 1235 static const int maxAccPrv = 5;
mjr 6:cc35eb643e8f 1236 AccHist accPrv_[maxAccPrv];
mjr 6:cc35eb643e8f 1237
mjr 5:a70c0bce770d 1238 // interurupt pin name
mjr 5:a70c0bce770d 1239 PinName irqPin_;
mjr 5:a70c0bce770d 1240
mjr 5:a70c0bce770d 1241 // interrupt router
mjr 5:a70c0bce770d 1242 InterruptIn intIn_;
mjr 3:3514575d4f86 1243 };
mjr 3:3514575d4f86 1244
mjr 5:a70c0bce770d 1245
mjr 5:a70c0bce770d 1246 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 1247 //
mjr 14:df700b22ca08 1248 // Clear the I2C bus for the MMA8451Q. This seems necessary some of the time
mjr 5:a70c0bce770d 1249 // for reasons that aren't clear to me. Doing a hard power cycle has the same
mjr 5:a70c0bce770d 1250 // effect, but when we do a soft reset, the hardware sometimes seems to leave
mjr 5:a70c0bce770d 1251 // the MMA's SDA line stuck low. Forcing a series of 9 clock pulses through
mjr 14:df700b22ca08 1252 // the SCL line is supposed to clear this condition. I'm not convinced this
mjr 14:df700b22ca08 1253 // actually works with the way this component is wired on the KL25Z, but it
mjr 14:df700b22ca08 1254 // seems harmless, so we'll do it on reset in case it does some good. What
mjr 14:df700b22ca08 1255 // we really seem to need is a way to power cycle the MMA8451Q if it ever
mjr 14:df700b22ca08 1256 // gets stuck, but this is simply not possible in software on the KL25Z.
mjr 14:df700b22ca08 1257 //
mjr 14:df700b22ca08 1258 // If the accelerometer does get stuck, and a software reboot doesn't reset
mjr 14:df700b22ca08 1259 // it, the only workaround is to manually power cycle the whole KL25Z by
mjr 14:df700b22ca08 1260 // unplugging both of its USB connections.
mjr 5:a70c0bce770d 1261 //
mjr 5:a70c0bce770d 1262 void clear_i2c()
mjr 5:a70c0bce770d 1263 {
mjr 5:a70c0bce770d 1264 // assume a general-purpose output pin to the I2C clock
mjr 5:a70c0bce770d 1265 DigitalOut scl(MMA8451_SCL_PIN);
mjr 5:a70c0bce770d 1266 DigitalIn sda(MMA8451_SDA_PIN);
mjr 5:a70c0bce770d 1267
mjr 5:a70c0bce770d 1268 // clock the SCL 9 times
mjr 5:a70c0bce770d 1269 for (int i = 0 ; i < 9 ; ++i)
mjr 5:a70c0bce770d 1270 {
mjr 5:a70c0bce770d 1271 scl = 1;
mjr 5:a70c0bce770d 1272 wait_us(20);
mjr 5:a70c0bce770d 1273 scl = 0;
mjr 5:a70c0bce770d 1274 wait_us(20);
mjr 5:a70c0bce770d 1275 }
mjr 5:a70c0bce770d 1276 }
mjr 14:df700b22ca08 1277
mjr 14:df700b22ca08 1278 // ---------------------------------------------------------------------------
mjr 14:df700b22ca08 1279 //
mjr 33:d832bcab089e 1280 // Simple binary (on/off) input debouncer. Requires an input to be stable
mjr 33:d832bcab089e 1281 // for a given interval before allowing an update.
mjr 33:d832bcab089e 1282 //
mjr 33:d832bcab089e 1283 class Debouncer
mjr 33:d832bcab089e 1284 {
mjr 33:d832bcab089e 1285 public:
mjr 33:d832bcab089e 1286 Debouncer(bool initVal, float tmin)
mjr 33:d832bcab089e 1287 {
mjr 33:d832bcab089e 1288 t.start();
mjr 33:d832bcab089e 1289 this->stable = this->prv = initVal;
mjr 33:d832bcab089e 1290 this->tmin = tmin;
mjr 33:d832bcab089e 1291 }
mjr 33:d832bcab089e 1292
mjr 33:d832bcab089e 1293 // Get the current stable value
mjr 33:d832bcab089e 1294 bool val() const { return stable; }
mjr 33:d832bcab089e 1295
mjr 33:d832bcab089e 1296 // Apply a new sample. This tells us the new raw reading from the
mjr 33:d832bcab089e 1297 // input device.
mjr 33:d832bcab089e 1298 void sampleIn(bool val)
mjr 33:d832bcab089e 1299 {
mjr 33:d832bcab089e 1300 // If the new raw reading is different from the previous
mjr 33:d832bcab089e 1301 // raw reading, we've detected an edge - start the clock
mjr 33:d832bcab089e 1302 // on the sample reader.
mjr 33:d832bcab089e 1303 if (val != prv)
mjr 33:d832bcab089e 1304 {
mjr 33:d832bcab089e 1305 // we have an edge - reset the sample clock
mjr 33:d832bcab089e 1306 t.reset();
mjr 33:d832bcab089e 1307
mjr 33:d832bcab089e 1308 // this is now the previous raw sample for nxt time
mjr 33:d832bcab089e 1309 prv = val;
mjr 33:d832bcab089e 1310 }
mjr 33:d832bcab089e 1311 else if (val != stable)
mjr 33:d832bcab089e 1312 {
mjr 33:d832bcab089e 1313 // The new raw sample is the same as the last raw sample,
mjr 33:d832bcab089e 1314 // and different from the stable value. This means that
mjr 33:d832bcab089e 1315 // the sample value has been the same for the time currently
mjr 33:d832bcab089e 1316 // indicated by our timer. If enough time has elapsed to
mjr 33:d832bcab089e 1317 // consider the value stable, apply the new value.
mjr 33:d832bcab089e 1318 if (t.read() > tmin)
mjr 33:d832bcab089e 1319 stable = val;
mjr 33:d832bcab089e 1320 }
mjr 33:d832bcab089e 1321 }
mjr 33:d832bcab089e 1322
mjr 33:d832bcab089e 1323 private:
mjr 33:d832bcab089e 1324 // current stable value
mjr 33:d832bcab089e 1325 bool stable;
mjr 33:d832bcab089e 1326
mjr 33:d832bcab089e 1327 // last raw sample value
mjr 33:d832bcab089e 1328 bool prv;
mjr 33:d832bcab089e 1329
mjr 33:d832bcab089e 1330 // elapsed time since last raw input change
mjr 33:d832bcab089e 1331 Timer t;
mjr 33:d832bcab089e 1332
mjr 33:d832bcab089e 1333 // Minimum time interval for stability, in seconds. Input readings
mjr 33:d832bcab089e 1334 // must be stable for this long before the stable value is updated.
mjr 33:d832bcab089e 1335 float tmin;
mjr 33:d832bcab089e 1336 };
mjr 33:d832bcab089e 1337
mjr 33:d832bcab089e 1338
mjr 33:d832bcab089e 1339 // ---------------------------------------------------------------------------
mjr 33:d832bcab089e 1340 //
mjr 33:d832bcab089e 1341 // Turn off all outputs and restore everything to the default LedWiz
mjr 33:d832bcab089e 1342 // state. This sets outputs #1-32 to LedWiz profile value 48 (full
mjr 33:d832bcab089e 1343 // brightness) and switch state Off, sets all extended outputs (#33
mjr 33:d832bcab089e 1344 // and above) to zero brightness, and sets the LedWiz flash rate to 2.
mjr 33:d832bcab089e 1345 // This effectively restores the power-on conditions.
mjr 33:d832bcab089e 1346 //
mjr 33:d832bcab089e 1347 void allOutputsOff()
mjr 33:d832bcab089e 1348 {
mjr 33:d832bcab089e 1349 // reset all LedWiz outputs to OFF/48
mjr 35:e959ffba78fd 1350 for (int i = 0 ; i < numLwOutputs ; ++i)
mjr 33:d832bcab089e 1351 {
mjr 33:d832bcab089e 1352 outLevel[i] = 0;
mjr 33:d832bcab089e 1353 wizOn[i] = 0;
mjr 33:d832bcab089e 1354 wizVal[i] = 48;
mjr 33:d832bcab089e 1355 lwPin[i]->set(0);
mjr 33:d832bcab089e 1356 }
mjr 33:d832bcab089e 1357
mjr 33:d832bcab089e 1358 // reset all extended outputs (ports >32) to full off (brightness 0)
mjr 33:d832bcab089e 1359 for (int i = 32 ; i < numOutputs ; ++i)
mjr 33:d832bcab089e 1360 {
mjr 33:d832bcab089e 1361 outLevel[i] = 0;
mjr 33:d832bcab089e 1362 lwPin[i]->set(0);
mjr 33:d832bcab089e 1363 }
mjr 33:d832bcab089e 1364
mjr 33:d832bcab089e 1365 // restore default LedWiz flash rate
mjr 33:d832bcab089e 1366 wizSpeed = 2;
mjr 34:6b981a2afab7 1367
mjr 34:6b981a2afab7 1368 // flush changes to hc595, if applicable
mjr 35:e959ffba78fd 1369 if (hc595 != 0)
mjr 35:e959ffba78fd 1370 hc595->update();
mjr 33:d832bcab089e 1371 }
mjr 33:d832bcab089e 1372
mjr 33:d832bcab089e 1373 // ---------------------------------------------------------------------------
mjr 33:d832bcab089e 1374 //
mjr 33:d832bcab089e 1375 // TV ON timer. If this feature is enabled, we toggle a TV power switch
mjr 33:d832bcab089e 1376 // relay (connected to a GPIO pin) to turn on the cab's TV monitors shortly
mjr 33:d832bcab089e 1377 // after the system is powered. This is useful for TVs that don't remember
mjr 33:d832bcab089e 1378 // their power state and don't turn back on automatically after being
mjr 33:d832bcab089e 1379 // unplugged and plugged in again. This feature requires external
mjr 33:d832bcab089e 1380 // circuitry, which is built in to the expansion board and can also be
mjr 33:d832bcab089e 1381 // built separately - see the Build Guide for the circuit plan.
mjr 33:d832bcab089e 1382 //
mjr 33:d832bcab089e 1383 // Theory of operation: to use this feature, the cabinet must have a
mjr 33:d832bcab089e 1384 // secondary PC-style power supply (PSU2) for the feedback devices, and
mjr 33:d832bcab089e 1385 // this secondary supply must be plugged in to the same power strip or
mjr 33:d832bcab089e 1386 // switched outlet that controls power to the TVs. This lets us use PSU2
mjr 33:d832bcab089e 1387 // as a proxy for the TV power state - when PSU2 is on, the TV outlet is
mjr 33:d832bcab089e 1388 // powered, and when PSU2 is off, the TV outlet is off. We use a little
mjr 33:d832bcab089e 1389 // latch circuit powered by PSU2 to monitor the status. The latch has a
mjr 33:d832bcab089e 1390 // current state, ON or OFF, that we can read via a GPIO input pin, and
mjr 33:d832bcab089e 1391 // we can set the state to ON by pulsing a separate GPIO output pin. As
mjr 33:d832bcab089e 1392 // long as PSU2 is powered off, the latch stays in the OFF state, even if
mjr 33:d832bcab089e 1393 // we try to set it by pulsing the SET pin. When PSU2 is turned on after
mjr 33:d832bcab089e 1394 // being off, the latch starts receiving power but stays in the OFF state,
mjr 33:d832bcab089e 1395 // since this is the initial condition when the power first comes on. So
mjr 33:d832bcab089e 1396 // if our latch state pin is reading OFF, we know that PSU2 is either off
mjr 33:d832bcab089e 1397 // now or *was* off some time since we last checked. We use a timer to
mjr 33:d832bcab089e 1398 // check the state periodically. Each time we see the state is OFF, we
mjr 33:d832bcab089e 1399 // try pulsing the SET pin. If the state still reads as OFF, we know
mjr 33:d832bcab089e 1400 // that PSU2 is currently off; if the state changes to ON, though, we
mjr 33:d832bcab089e 1401 // know that PSU2 has gone from OFF to ON some time between now and the
mjr 33:d832bcab089e 1402 // previous check. When we see this condition, we start a countdown
mjr 33:d832bcab089e 1403 // timer, and pulse the TV switch relay when the countdown ends.
mjr 33:d832bcab089e 1404 //
mjr 33:d832bcab089e 1405 // This scheme might seem a little convoluted, but it neatly handles
mjr 33:d832bcab089e 1406 // all of the different cases that can occur:
mjr 33:d832bcab089e 1407 //
mjr 33:d832bcab089e 1408 // - Most cabinets systems are set up with "soft" PC power switches,
mjr 33:d832bcab089e 1409 // so that the PC goes into "Soft Off" mode (ACPI state S5, in Windows
mjr 33:d832bcab089e 1410 // parlance) when the user turns off the cabinet. In this state, the
mjr 33:d832bcab089e 1411 // motherboard supplies power to USB devices, so the KL25Z continues
mjr 33:d832bcab089e 1412 // running without interruption. The latch system lets us monitor
mjr 33:d832bcab089e 1413 // the power state even when we're never rebooted, since the latch
mjr 33:d832bcab089e 1414 // will turn off when PSU2 is off regardless of what the KL25Z is doing.
mjr 33:d832bcab089e 1415 //
mjr 33:d832bcab089e 1416 // - Some cabinet builders might prefer to use "hard" power switches,
mjr 33:d832bcab089e 1417 // cutting all power to the cabinet, including the PC motherboard (and
mjr 33:d832bcab089e 1418 // thus the KL25Z) every time the machine is turned off. This also
mjr 33:d832bcab089e 1419 // applies to the "soft" switch case above when the cabinet is unplugged,
mjr 33:d832bcab089e 1420 // a power outage occurs, etc. In these cases, the KL25Z will do a cold
mjr 33:d832bcab089e 1421 // boot when the PC is turned on. We don't know whether the KL25Z
mjr 33:d832bcab089e 1422 // will power up before or after PSU2, so it's not good enough to
mjr 33:d832bcab089e 1423 // observe the *current* state of PSU2 when we first check - if PSU2
mjr 33:d832bcab089e 1424 // were to come on first, checking the current state alone would fool
mjr 33:d832bcab089e 1425 // us into thinking that no action is required, because we would never
mjr 33:d832bcab089e 1426 // have known that PSU2 was ever off. The latch handles this case by
mjr 33:d832bcab089e 1427 // letting us see that PSU2 *was* off before we checked.
mjr 33:d832bcab089e 1428 //
mjr 33:d832bcab089e 1429 // - If the KL25Z is rebooted while the main system is running, or the
mjr 33:d832bcab089e 1430 // KL25Z is unplugged and plugged back in, we will correctly leave the
mjr 33:d832bcab089e 1431 // TVs as they are. The latch state is independent of the KL25Z's
mjr 33:d832bcab089e 1432 // power or software state, so it's won't affect the latch state when
mjr 33:d832bcab089e 1433 // the KL25Z is unplugged or rebooted; when we boot, we'll see that
mjr 33:d832bcab089e 1434 // the latch is already on and that we don't have to turn on the TVs.
mjr 33:d832bcab089e 1435 // This is important because TV ON buttons are usually on/off toggles,
mjr 33:d832bcab089e 1436 // so we don't want to push the button on a TV that's already on.
mjr 33:d832bcab089e 1437 //
mjr 33:d832bcab089e 1438 //
mjr 33:d832bcab089e 1439
mjr 33:d832bcab089e 1440 // Current PSU2 state:
mjr 33:d832bcab089e 1441 // 1 -> default: latch was on at last check, or we haven't checked yet
mjr 33:d832bcab089e 1442 // 2 -> latch was off at last check, SET pulsed high
mjr 33:d832bcab089e 1443 // 3 -> SET pulsed low, ready to check status
mjr 33:d832bcab089e 1444 // 4 -> TV timer countdown in progress
mjr 33:d832bcab089e 1445 // 5 -> TV relay on
mjr 33:d832bcab089e 1446 //
mjr 33:d832bcab089e 1447 int psu2_state = 1;
mjr 35:e959ffba78fd 1448
mjr 35:e959ffba78fd 1449 // PSU2 power sensing circuit connections
mjr 35:e959ffba78fd 1450 DigitalIn *psu2_status_sense;
mjr 35:e959ffba78fd 1451 DigitalOut *psu2_status_set;
mjr 35:e959ffba78fd 1452
mjr 35:e959ffba78fd 1453 // TV ON switch relay control
mjr 35:e959ffba78fd 1454 DigitalOut *tv_relay;
mjr 35:e959ffba78fd 1455
mjr 35:e959ffba78fd 1456 // Timer interrupt
mjr 35:e959ffba78fd 1457 Ticker tv_ticker;
mjr 35:e959ffba78fd 1458 float tv_delay_time;
mjr 33:d832bcab089e 1459 void TVTimerInt()
mjr 33:d832bcab089e 1460 {
mjr 35:e959ffba78fd 1461 // time since last state change
mjr 35:e959ffba78fd 1462 static Timer tv_timer;
mjr 35:e959ffba78fd 1463
mjr 33:d832bcab089e 1464 // Check our internal state
mjr 33:d832bcab089e 1465 switch (psu2_state)
mjr 33:d832bcab089e 1466 {
mjr 33:d832bcab089e 1467 case 1:
mjr 33:d832bcab089e 1468 // Default state. This means that the latch was on last
mjr 33:d832bcab089e 1469 // time we checked or that this is the first check. In
mjr 33:d832bcab089e 1470 // either case, if the latch is off, switch to state 2 and
mjr 33:d832bcab089e 1471 // try pulsing the latch. Next time we check, if the latch
mjr 33:d832bcab089e 1472 // stuck, it means that PSU2 is now on after being off.
mjr 35:e959ffba78fd 1473 if (!psu2_status_sense->read())
mjr 33:d832bcab089e 1474 {
mjr 33:d832bcab089e 1475 // switch to OFF state
mjr 33:d832bcab089e 1476 psu2_state = 2;
mjr 33:d832bcab089e 1477
mjr 33:d832bcab089e 1478 // try setting the latch
mjr 35:e959ffba78fd 1479 psu2_status_set->write(1);
mjr 33:d832bcab089e 1480 }
mjr 33:d832bcab089e 1481 break;
mjr 33:d832bcab089e 1482
mjr 33:d832bcab089e 1483 case 2:
mjr 33:d832bcab089e 1484 // PSU2 was off last time we checked, and we tried setting
mjr 33:d832bcab089e 1485 // the latch. Drop the SET signal and go to CHECK state.
mjr 35:e959ffba78fd 1486 psu2_status_set->write(0);
mjr 33:d832bcab089e 1487 psu2_state = 3;
mjr 33:d832bcab089e 1488 break;
mjr 33:d832bcab089e 1489
mjr 33:d832bcab089e 1490 case 3:
mjr 33:d832bcab089e 1491 // CHECK state: we pulsed SET, and we're now ready to see
mjr 33:d832bcab089e 1492 // if that stuck. If the latch is now on, PSU2 has transitioned
mjr 33:d832bcab089e 1493 // from OFF to ON, so start the TV countdown. If the latch is
mjr 33:d832bcab089e 1494 // off, our SET command didn't stick, so PSU2 is still off.
mjr 35:e959ffba78fd 1495 if (psu2_status_sense->read())
mjr 33:d832bcab089e 1496 {
mjr 33:d832bcab089e 1497 // The latch stuck, so PSU2 has transitioned from OFF
mjr 33:d832bcab089e 1498 // to ON. Start the TV countdown timer.
mjr 33:d832bcab089e 1499 tv_timer.reset();
mjr 33:d832bcab089e 1500 tv_timer.start();
mjr 33:d832bcab089e 1501 psu2_state = 4;
mjr 33:d832bcab089e 1502 }
mjr 33:d832bcab089e 1503 else
mjr 33:d832bcab089e 1504 {
mjr 33:d832bcab089e 1505 // The latch didn't stick, so PSU2 was still off at
mjr 33:d832bcab089e 1506 // our last check. Try pulsing it again in case PSU2
mjr 33:d832bcab089e 1507 // was turned on since the last check.
mjr 35:e959ffba78fd 1508 psu2_status_set->write(1);
mjr 33:d832bcab089e 1509 psu2_state = 2;
mjr 33:d832bcab089e 1510 }
mjr 33:d832bcab089e 1511 break;
mjr 33:d832bcab089e 1512
mjr 33:d832bcab089e 1513 case 4:
mjr 33:d832bcab089e 1514 // TV timer countdown in progress. If we've reached the
mjr 33:d832bcab089e 1515 // delay time, pulse the relay.
mjr 35:e959ffba78fd 1516 if (tv_timer.read() >= tv_delay_time)
mjr 33:d832bcab089e 1517 {
mjr 33:d832bcab089e 1518 // turn on the relay for one timer interval
mjr 35:e959ffba78fd 1519 tv_relay->write(1);
mjr 33:d832bcab089e 1520 psu2_state = 5;
mjr 33:d832bcab089e 1521 }
mjr 33:d832bcab089e 1522 break;
mjr 33:d832bcab089e 1523
mjr 33:d832bcab089e 1524 case 5:
mjr 33:d832bcab089e 1525 // TV timer relay on. We pulse this for one interval, so
mjr 33:d832bcab089e 1526 // it's now time to turn it off and return to the default state.
mjr 35:e959ffba78fd 1527 tv_relay->write(0);
mjr 33:d832bcab089e 1528 psu2_state = 1;
mjr 33:d832bcab089e 1529 break;
mjr 33:d832bcab089e 1530 }
mjr 33:d832bcab089e 1531 }
mjr 33:d832bcab089e 1532
mjr 35:e959ffba78fd 1533 // Start the TV ON checker. If the status sense circuit is enabled in
mjr 35:e959ffba78fd 1534 // the configuration, we'll set up the pin connections and start the
mjr 35:e959ffba78fd 1535 // interrupt handler that periodically checks the status. Does nothing
mjr 35:e959ffba78fd 1536 // if any of the pins are configured as NC.
mjr 35:e959ffba78fd 1537 void startTVTimer(Config &cfg)
mjr 35:e959ffba78fd 1538 {
mjr 35:e959ffba78fd 1539 // only start the timer if the status sense circuit pins are configured
mjr 35:e959ffba78fd 1540 if (cfg.TVON.statusPin != NC && cfg.TVON.latchPin != NC && cfg.TVON.relayPin != NC)
mjr 35:e959ffba78fd 1541 {
mjr 35:e959ffba78fd 1542 psu2_status_sense = new DigitalIn(cfg.TVON.statusPin);
mjr 35:e959ffba78fd 1543 psu2_status_set = new DigitalOut(cfg.TVON.latchPin);
mjr 35:e959ffba78fd 1544 tv_relay = new DigitalOut(cfg.TVON.relayPin);
mjr 35:e959ffba78fd 1545 tv_delay_time = cfg.TVON.delayTime;
mjr 35:e959ffba78fd 1546
mjr 35:e959ffba78fd 1547 // Set up our time routine to run every 1/4 second.
mjr 35:e959ffba78fd 1548 tv_ticker.attach(&TVTimerInt, 0.25);
mjr 35:e959ffba78fd 1549 }
mjr 35:e959ffba78fd 1550 }
mjr 35:e959ffba78fd 1551
mjr 35:e959ffba78fd 1552 // ---------------------------------------------------------------------------
mjr 35:e959ffba78fd 1553 //
mjr 35:e959ffba78fd 1554 // In-memory configuration data structure. This is the live version in RAM
mjr 35:e959ffba78fd 1555 // that we use to determine how things are set up.
mjr 35:e959ffba78fd 1556 //
mjr 35:e959ffba78fd 1557 // When we save the configuration settings, we copy this structure to
mjr 35:e959ffba78fd 1558 // non-volatile flash memory. At startup, we check the flash location where
mjr 35:e959ffba78fd 1559 // we might have saved settings on a previous run, and it's valid, we copy
mjr 35:e959ffba78fd 1560 // the flash data to this structure. Firmware updates wipe the flash
mjr 35:e959ffba78fd 1561 // memory area, so you have to use the PC config tool to send the settings
mjr 35:e959ffba78fd 1562 // again each time the firmware is updated.
mjr 35:e959ffba78fd 1563 //
mjr 35:e959ffba78fd 1564 NVM nvm;
mjr 35:e959ffba78fd 1565
mjr 35:e959ffba78fd 1566 // For convenience, a macro for the Config part of the NVM structure
mjr 35:e959ffba78fd 1567 #define cfg (nvm.d.c)
mjr 35:e959ffba78fd 1568
mjr 35:e959ffba78fd 1569 // flash memory controller interface
mjr 35:e959ffba78fd 1570 FreescaleIAP iap;
mjr 35:e959ffba78fd 1571
mjr 35:e959ffba78fd 1572 // figure the flash address as a pointer along with the number of sectors
mjr 35:e959ffba78fd 1573 // required to store the structure
mjr 35:e959ffba78fd 1574 NVM *configFlashAddr(int &addr, int &numSectors)
mjr 35:e959ffba78fd 1575 {
mjr 35:e959ffba78fd 1576 // figure how many flash sectors we span, rounding up to whole sectors
mjr 35:e959ffba78fd 1577 numSectors = (sizeof(NVM) + SECTOR_SIZE - 1)/SECTOR_SIZE;
mjr 35:e959ffba78fd 1578
mjr 35:e959ffba78fd 1579 // figure the address - this is the highest flash address where the
mjr 35:e959ffba78fd 1580 // structure will fit with the start aligned on a sector boundary
mjr 35:e959ffba78fd 1581 addr = iap.flash_size() - (numSectors * SECTOR_SIZE);
mjr 35:e959ffba78fd 1582
mjr 35:e959ffba78fd 1583 // return the address as a pointer
mjr 35:e959ffba78fd 1584 return (NVM *)addr;
mjr 35:e959ffba78fd 1585 }
mjr 35:e959ffba78fd 1586
mjr 35:e959ffba78fd 1587 // figure the flash address as a pointer
mjr 35:e959ffba78fd 1588 NVM *configFlashAddr()
mjr 35:e959ffba78fd 1589 {
mjr 35:e959ffba78fd 1590 int addr, numSectors;
mjr 35:e959ffba78fd 1591 return configFlashAddr(addr, numSectors);
mjr 35:e959ffba78fd 1592 }
mjr 35:e959ffba78fd 1593
mjr 35:e959ffba78fd 1594 // Load the config from flash
mjr 35:e959ffba78fd 1595 void loadConfigFromFlash()
mjr 35:e959ffba78fd 1596 {
mjr 35:e959ffba78fd 1597 // We want to use the KL25Z's on-board flash to store our configuration
mjr 35:e959ffba78fd 1598 // data persistently, so that we can restore it across power cycles.
mjr 35:e959ffba78fd 1599 // Unfortunatly, the mbed platform doesn't explicitly support this.
mjr 35:e959ffba78fd 1600 // mbed treats the on-board flash as a raw storage device for linker
mjr 35:e959ffba78fd 1601 // output, and assumes that the linker output is the only thing
mjr 35:e959ffba78fd 1602 // stored there. There's no file system and no allowance for shared
mjr 35:e959ffba78fd 1603 // use for other purposes. Fortunately, the linker ues the space in
mjr 35:e959ffba78fd 1604 // the obvious way, storing the entire linked program in a contiguous
mjr 35:e959ffba78fd 1605 // block starting at the lowest flash address. This means that the
mjr 35:e959ffba78fd 1606 // rest of flash - from the end of the linked program to the highest
mjr 35:e959ffba78fd 1607 // flash address - is all unused free space. Writing our data there
mjr 35:e959ffba78fd 1608 // won't conflict with anything else. Since the linker doesn't give
mjr 35:e959ffba78fd 1609 // us any programmatic access to the total linker output size, it's
mjr 35:e959ffba78fd 1610 // safest to just store our config data at the very end of the flash
mjr 35:e959ffba78fd 1611 // region (i.e., the highest address). As long as it's smaller than
mjr 35:e959ffba78fd 1612 // the free space, it won't collide with the linker area.
mjr 35:e959ffba78fd 1613
mjr 35:e959ffba78fd 1614 // Figure how many sectors we need for our structure
mjr 35:e959ffba78fd 1615 NVM *flash = configFlashAddr();
mjr 35:e959ffba78fd 1616
mjr 35:e959ffba78fd 1617 // if the flash is valid, load it; otherwise initialize to defaults
mjr 35:e959ffba78fd 1618 if (flash->valid())
mjr 35:e959ffba78fd 1619 {
mjr 35:e959ffba78fd 1620 // flash is valid - load it into the RAM copy of the structure
mjr 35:e959ffba78fd 1621 memcpy(&nvm, flash, sizeof(NVM));
mjr 35:e959ffba78fd 1622 }
mjr 35:e959ffba78fd 1623 else
mjr 35:e959ffba78fd 1624 {
mjr 35:e959ffba78fd 1625 // flash is invalid - load factory settings nito RAM structure
mjr 35:e959ffba78fd 1626 cfg.setFactoryDefaults();
mjr 35:e959ffba78fd 1627 }
mjr 35:e959ffba78fd 1628 }
mjr 35:e959ffba78fd 1629
mjr 35:e959ffba78fd 1630 void saveConfigToFlash()
mjr 33:d832bcab089e 1631 {
mjr 35:e959ffba78fd 1632 int addr, sectors;
mjr 35:e959ffba78fd 1633 configFlashAddr(addr, sectors);
mjr 35:e959ffba78fd 1634 nvm.save(iap, addr);
mjr 35:e959ffba78fd 1635 }
mjr 35:e959ffba78fd 1636
mjr 35:e959ffba78fd 1637 // ---------------------------------------------------------------------------
mjr 35:e959ffba78fd 1638 //
mjr 35:e959ffba78fd 1639 // Plunger Sensor
mjr 35:e959ffba78fd 1640 //
mjr 35:e959ffba78fd 1641
mjr 35:e959ffba78fd 1642 // the plunger sensor interface object
mjr 35:e959ffba78fd 1643 PlungerSensor *plungerSensor = 0;
mjr 35:e959ffba78fd 1644
mjr 35:e959ffba78fd 1645 // Create the plunger sensor based on the current configuration. If
mjr 35:e959ffba78fd 1646 // there's already a sensor object, we'll delete it.
mjr 35:e959ffba78fd 1647 void createPlunger()
mjr 35:e959ffba78fd 1648 {
mjr 35:e959ffba78fd 1649 // delete any existing sensor object
mjr 35:e959ffba78fd 1650 if (plungerSensor != 0)
mjr 35:e959ffba78fd 1651 delete plungerSensor;
mjr 35:e959ffba78fd 1652
mjr 35:e959ffba78fd 1653 // create the new sensor object according to the type
mjr 35:e959ffba78fd 1654 switch (cfg.plunger.sensorType)
mjr 35:e959ffba78fd 1655 {
mjr 35:e959ffba78fd 1656 case PlungerType_TSL1410RS:
mjr 35:e959ffba78fd 1657 // pins are: SI, CLOCK, AO
mjr 35:e959ffba78fd 1658 plungerSensor = new PlungerSensorTSL1410R(cfg.plunger.sensorPin[0], cfg.plunger.sensorPin[1], cfg.plunger.sensorPin[2], NC);
mjr 35:e959ffba78fd 1659 break;
mjr 35:e959ffba78fd 1660
mjr 35:e959ffba78fd 1661 case PlungerType_TSL1410RP:
mjr 35:e959ffba78fd 1662 // pins are: SI, CLOCK, AO1, AO2
mjr 35:e959ffba78fd 1663 plungerSensor = new PlungerSensorTSL1410R(cfg.plunger.sensorPin[0], cfg.plunger.sensorPin[1], cfg.plunger.sensorPin[2], cfg.plunger.sensorPin[3]);
mjr 35:e959ffba78fd 1664 break;
mjr 35:e959ffba78fd 1665
mjr 35:e959ffba78fd 1666 case PlungerType_TSL1412RS:
mjr 35:e959ffba78fd 1667 // pins are: SI, CLOCK, AO1, AO2
mjr 35:e959ffba78fd 1668 plungerSensor = new PlungerSensorTSL1412R(cfg.plunger.sensorPin[0], cfg.plunger.sensorPin[1], cfg.plunger.sensorPin[2], NC);
mjr 35:e959ffba78fd 1669 break;
mjr 35:e959ffba78fd 1670
mjr 35:e959ffba78fd 1671 case PlungerType_TSL1412RP:
mjr 35:e959ffba78fd 1672 // pins are: SI, CLOCK, AO1, AO2
mjr 35:e959ffba78fd 1673 plungerSensor = new PlungerSensorTSL1412R(cfg.plunger.sensorPin[0], cfg.plunger.sensorPin[1], cfg.plunger.sensorPin[2], cfg.plunger.sensorPin[3]);
mjr 35:e959ffba78fd 1674 break;
mjr 35:e959ffba78fd 1675
mjr 35:e959ffba78fd 1676 case PlungerType_Pot:
mjr 35:e959ffba78fd 1677 // pins are: AO
mjr 35:e959ffba78fd 1678 plungerSensor = new PlungerSensorPot(cfg.plunger.sensorPin[0]);
mjr 35:e959ffba78fd 1679 break;
mjr 35:e959ffba78fd 1680
mjr 35:e959ffba78fd 1681 case PlungerType_None:
mjr 35:e959ffba78fd 1682 default:
mjr 35:e959ffba78fd 1683 plungerSensor = new PlungerSensorNull();
mjr 35:e959ffba78fd 1684 break;
mjr 35:e959ffba78fd 1685 }
mjr 33:d832bcab089e 1686 }
mjr 33:d832bcab089e 1687
mjr 35:e959ffba78fd 1688 // ---------------------------------------------------------------------------
mjr 35:e959ffba78fd 1689 //
mjr 35:e959ffba78fd 1690 // Reboot - resets the microcontroller
mjr 35:e959ffba78fd 1691 //
mjr 35:e959ffba78fd 1692 void reboot(USBJoystick &js)
mjr 35:e959ffba78fd 1693 {
mjr 35:e959ffba78fd 1694 // disconnect from USB
mjr 35:e959ffba78fd 1695 js.disconnect();
mjr 35:e959ffba78fd 1696
mjr 35:e959ffba78fd 1697 // wait a few seconds to make sure the host notices the disconnect
mjr 35:e959ffba78fd 1698 wait(5);
mjr 35:e959ffba78fd 1699
mjr 35:e959ffba78fd 1700 // reset the device
mjr 35:e959ffba78fd 1701 NVIC_SystemReset();
mjr 35:e959ffba78fd 1702 while (true) { }
mjr 35:e959ffba78fd 1703 }
mjr 35:e959ffba78fd 1704
mjr 35:e959ffba78fd 1705 // ---------------------------------------------------------------------------
mjr 35:e959ffba78fd 1706 //
mjr 35:e959ffba78fd 1707 // Translate joystick readings from raw values to reported values, based
mjr 35:e959ffba78fd 1708 // on the orientation of the controller card in the cabinet.
mjr 35:e959ffba78fd 1709 //
mjr 35:e959ffba78fd 1710 void accelRotate(int &x, int &y)
mjr 35:e959ffba78fd 1711 {
mjr 35:e959ffba78fd 1712 int tmp;
mjr 35:e959ffba78fd 1713 switch (cfg.orientation)
mjr 35:e959ffba78fd 1714 {
mjr 35:e959ffba78fd 1715 case OrientationFront:
mjr 35:e959ffba78fd 1716 tmp = x;
mjr 35:e959ffba78fd 1717 x = y;
mjr 35:e959ffba78fd 1718 y = tmp;
mjr 35:e959ffba78fd 1719 break;
mjr 35:e959ffba78fd 1720
mjr 35:e959ffba78fd 1721 case OrientationLeft:
mjr 35:e959ffba78fd 1722 x = -x;
mjr 35:e959ffba78fd 1723 break;
mjr 35:e959ffba78fd 1724
mjr 35:e959ffba78fd 1725 case OrientationRight:
mjr 35:e959ffba78fd 1726 y = -y;
mjr 35:e959ffba78fd 1727 break;
mjr 35:e959ffba78fd 1728
mjr 35:e959ffba78fd 1729 case OrientationRear:
mjr 35:e959ffba78fd 1730 tmp = -x;
mjr 35:e959ffba78fd 1731 x = -y;
mjr 35:e959ffba78fd 1732 y = tmp;
mjr 35:e959ffba78fd 1733 break;
mjr 35:e959ffba78fd 1734 }
mjr 35:e959ffba78fd 1735 }
mjr 35:e959ffba78fd 1736
mjr 35:e959ffba78fd 1737 // ---------------------------------------------------------------------------
mjr 35:e959ffba78fd 1738 //
mjr 35:e959ffba78fd 1739 // Device status. We report this on each update so that the host config
mjr 35:e959ffba78fd 1740 // tool can detect our current settings. This is a bit mask consisting
mjr 35:e959ffba78fd 1741 // of these bits:
mjr 35:e959ffba78fd 1742 // 0x0001 -> plunger sensor enabled
mjr 35:e959ffba78fd 1743 // 0x8000 -> RESERVED - must always be zero
mjr 35:e959ffba78fd 1744 //
mjr 35:e959ffba78fd 1745 // Note that the high bit (0x8000) must always be 0, since we use that
mjr 35:e959ffba78fd 1746 // to distinguish special request reply packets.
mjr 35:e959ffba78fd 1747 uint16_t statusFlags;
mjr 35:e959ffba78fd 1748
mjr 35:e959ffba78fd 1749 // flag: send a pixel dump after the next read
mjr 35:e959ffba78fd 1750 bool reportPix = false;
mjr 35:e959ffba78fd 1751
mjr 33:d832bcab089e 1752
mjr 35:e959ffba78fd 1753 // ---------------------------------------------------------------------------
mjr 35:e959ffba78fd 1754 //
mjr 35:e959ffba78fd 1755 // Calibration button state:
mjr 35:e959ffba78fd 1756 // 0 = not pushed
mjr 35:e959ffba78fd 1757 // 1 = pushed, not yet debounced
mjr 35:e959ffba78fd 1758 // 2 = pushed, debounced, waiting for hold time
mjr 35:e959ffba78fd 1759 // 3 = pushed, hold time completed - in calibration mode
mjr 35:e959ffba78fd 1760 int calBtnState = 0;
mjr 35:e959ffba78fd 1761
mjr 35:e959ffba78fd 1762 // calibration button debounce timer
mjr 35:e959ffba78fd 1763 Timer calBtnTimer;
mjr 35:e959ffba78fd 1764
mjr 35:e959ffba78fd 1765 // calibration button light state
mjr 35:e959ffba78fd 1766 int calBtnLit = false;
mjr 35:e959ffba78fd 1767
mjr 35:e959ffba78fd 1768
mjr 35:e959ffba78fd 1769 // ---------------------------------------------------------------------------
mjr 35:e959ffba78fd 1770 //
mjr 35:e959ffba78fd 1771 // Handle a configuration variable update. 'data' is the USB message we
mjr 35:e959ffba78fd 1772 // received from the host.
mjr 35:e959ffba78fd 1773 //
mjr 35:e959ffba78fd 1774 void configVarMsg(uint8_t *data)
mjr 35:e959ffba78fd 1775 {
mjr 35:e959ffba78fd 1776 switch (data[1])
mjr 35:e959ffba78fd 1777 {
mjr 35:e959ffba78fd 1778 case 1:
mjr 35:e959ffba78fd 1779 // USB identification (Vendor ID, Product ID)
mjr 35:e959ffba78fd 1780 cfg.usbVendorID = wireUI16(data+2);
mjr 35:e959ffba78fd 1781 cfg.usbProductID = wireUI16(data+4);
mjr 35:e959ffba78fd 1782 break;
mjr 35:e959ffba78fd 1783
mjr 35:e959ffba78fd 1784 case 2:
mjr 35:e959ffba78fd 1785 // Pinscape Controller unit number - note that data[2] contains
mjr 35:e959ffba78fd 1786 // the nominal unit number, 1-16
mjr 35:e959ffba78fd 1787 if (data[2] >= 1 && data[2] <= 16)
mjr 35:e959ffba78fd 1788 cfg.psUnitNo = data[2];
mjr 35:e959ffba78fd 1789 break;
mjr 35:e959ffba78fd 1790
mjr 35:e959ffba78fd 1791 case 3:
mjr 35:e959ffba78fd 1792 // Enable/disable joystick
mjr 35:e959ffba78fd 1793 cfg.joystickEnabled = data[2];
mjr 35:e959ffba78fd 1794 break;
mjr 35:e959ffba78fd 1795
mjr 35:e959ffba78fd 1796 case 4:
mjr 35:e959ffba78fd 1797 // Accelerometer orientation
mjr 35:e959ffba78fd 1798 cfg.orientation = data[2];
mjr 35:e959ffba78fd 1799 break;
mjr 35:e959ffba78fd 1800
mjr 35:e959ffba78fd 1801 case 5:
mjr 35:e959ffba78fd 1802 // Plunger sensor type
mjr 35:e959ffba78fd 1803 cfg.plunger.sensorType = data[2];
mjr 35:e959ffba78fd 1804 break;
mjr 35:e959ffba78fd 1805
mjr 35:e959ffba78fd 1806 case 6:
mjr 35:e959ffba78fd 1807 // Set plunger pin assignments
mjr 35:e959ffba78fd 1808 cfg.plunger.sensorPin[0] = wirePinName(data[2]);
mjr 35:e959ffba78fd 1809 cfg.plunger.sensorPin[1] = wirePinName(data[3]);
mjr 35:e959ffba78fd 1810 cfg.plunger.sensorPin[2] = wirePinName(data[4]);
mjr 35:e959ffba78fd 1811 cfg.plunger.sensorPin[3] = wirePinName(data[5]);
mjr 35:e959ffba78fd 1812 break;
mjr 35:e959ffba78fd 1813
mjr 35:e959ffba78fd 1814 case 7:
mjr 35:e959ffba78fd 1815 // Plunger calibration button and indicator light pin assignments
mjr 35:e959ffba78fd 1816 cfg.plunger.cal.btn = wirePinName(data[2]);
mjr 35:e959ffba78fd 1817 cfg.plunger.cal.led = wirePinName(data[3]);
mjr 35:e959ffba78fd 1818 break;
mjr 35:e959ffba78fd 1819
mjr 35:e959ffba78fd 1820 case 8:
mjr 35:e959ffba78fd 1821 // ZB Launch Ball setup
mjr 35:e959ffba78fd 1822 cfg.plunger.zbLaunchBall.port = (int)(unsigned char)data[2];
mjr 35:e959ffba78fd 1823 cfg.plunger.zbLaunchBall.btn = (int)(unsigned char)data[3];
mjr 35:e959ffba78fd 1824 cfg.plunger.zbLaunchBall.pushDistance = (float)wireUI16(data+4) / 1000.0;
mjr 35:e959ffba78fd 1825 break;
mjr 35:e959ffba78fd 1826
mjr 35:e959ffba78fd 1827 case 9:
mjr 35:e959ffba78fd 1828 // TV ON setup
mjr 35:e959ffba78fd 1829 cfg.TVON.statusPin = wirePinName(data[2]);
mjr 35:e959ffba78fd 1830 cfg.TVON.latchPin = wirePinName(data[3]);
mjr 35:e959ffba78fd 1831 cfg.TVON.relayPin = wirePinName(data[4]);
mjr 35:e959ffba78fd 1832 cfg.TVON.delayTime = (float)wireUI16(data+5) / 100.0;
mjr 35:e959ffba78fd 1833 break;
mjr 35:e959ffba78fd 1834
mjr 35:e959ffba78fd 1835 case 10:
mjr 35:e959ffba78fd 1836 // TLC5940NT PWM controller chip setup
mjr 35:e959ffba78fd 1837 cfg.tlc5940.nchips = (int)(unsigned char)data[2];
mjr 35:e959ffba78fd 1838 cfg.tlc5940.sin = wirePinName(data[3]);
mjr 35:e959ffba78fd 1839 cfg.tlc5940.sclk = wirePinName(data[4]);
mjr 35:e959ffba78fd 1840 cfg.tlc5940.xlat = wirePinName(data[5]);
mjr 35:e959ffba78fd 1841 cfg.tlc5940.blank = wirePinName(data[6]);
mjr 35:e959ffba78fd 1842 cfg.tlc5940.gsclk = wirePinName(data[7]);
mjr 35:e959ffba78fd 1843 break;
mjr 35:e959ffba78fd 1844
mjr 35:e959ffba78fd 1845 case 11:
mjr 35:e959ffba78fd 1846 // 74HC595 shift register chip setup
mjr 35:e959ffba78fd 1847 cfg.hc595.nchips = (int)(unsigned char)data[2];
mjr 35:e959ffba78fd 1848 cfg.hc595.sin = wirePinName(data[3]);
mjr 35:e959ffba78fd 1849 cfg.hc595.sclk = wirePinName(data[4]);
mjr 35:e959ffba78fd 1850 cfg.hc595.latch = wirePinName(data[5]);
mjr 35:e959ffba78fd 1851 cfg.hc595.ena = wirePinName(data[6]);
mjr 35:e959ffba78fd 1852 break;
mjr 35:e959ffba78fd 1853
mjr 35:e959ffba78fd 1854 case 12:
mjr 35:e959ffba78fd 1855 // button setup
mjr 35:e959ffba78fd 1856 {
mjr 35:e959ffba78fd 1857 // get the button number
mjr 35:e959ffba78fd 1858 int idx = data[2];
mjr 35:e959ffba78fd 1859
mjr 35:e959ffba78fd 1860 // if it's in range, set the button data
mjr 35:e959ffba78fd 1861 if (idx > 0 && idx <= MAX_BUTTONS)
mjr 35:e959ffba78fd 1862 {
mjr 35:e959ffba78fd 1863 // adjust to an array index
mjr 35:e959ffba78fd 1864 --idx;
mjr 35:e959ffba78fd 1865
mjr 35:e959ffba78fd 1866 // set the values
mjr 35:e959ffba78fd 1867 cfg.button[idx].pin = data[3];
mjr 35:e959ffba78fd 1868 cfg.button[idx].typ = data[4];
mjr 35:e959ffba78fd 1869 cfg.button[idx].val = data[5];
mjr 35:e959ffba78fd 1870 }
mjr 35:e959ffba78fd 1871 }
mjr 35:e959ffba78fd 1872 break;
mjr 35:e959ffba78fd 1873
mjr 35:e959ffba78fd 1874 case 13:
mjr 35:e959ffba78fd 1875 // LedWiz output port setup
mjr 35:e959ffba78fd 1876 {
mjr 35:e959ffba78fd 1877 // get the port number
mjr 35:e959ffba78fd 1878 int idx = data[2];
mjr 35:e959ffba78fd 1879
mjr 35:e959ffba78fd 1880 // if it's in range, set the port data
mjr 35:e959ffba78fd 1881 if (idx > 0 && idx <= MAX_OUT_PORTS)
mjr 35:e959ffba78fd 1882 {
mjr 35:e959ffba78fd 1883 // adjust to an array index
mjr 35:e959ffba78fd 1884 --idx;
mjr 35:e959ffba78fd 1885
mjr 35:e959ffba78fd 1886 // set the values
mjr 35:e959ffba78fd 1887 cfg.outPort[idx].typ = data[3];
mjr 35:e959ffba78fd 1888 cfg.outPort[idx].pin = data[4];
mjr 35:e959ffba78fd 1889 cfg.outPort[idx].flags = data[5];
mjr 35:e959ffba78fd 1890 }
mjr 35:e959ffba78fd 1891 }
mjr 35:e959ffba78fd 1892 break;
mjr 35:e959ffba78fd 1893 }
mjr 35:e959ffba78fd 1894 }
mjr 35:e959ffba78fd 1895
mjr 35:e959ffba78fd 1896 // ---------------------------------------------------------------------------
mjr 35:e959ffba78fd 1897 //
mjr 35:e959ffba78fd 1898 // Handle an input report from the USB host. Input reports use our extended
mjr 35:e959ffba78fd 1899 // LedWiz protocol.
mjr 33:d832bcab089e 1900 //
mjr 35:e959ffba78fd 1901 void handleInputMsg(HID_REPORT &report, USBJoystick &js, int &z)
mjr 35:e959ffba78fd 1902 {
mjr 35:e959ffba78fd 1903 // all Led-Wiz reports are exactly 8 bytes
mjr 35:e959ffba78fd 1904 if (report.length == 8)
mjr 35:e959ffba78fd 1905 {
mjr 35:e959ffba78fd 1906 // LedWiz commands come in two varieties: SBA and PBA. An
mjr 35:e959ffba78fd 1907 // SBA is marked by the first byte having value 64 (0x40). In
mjr 35:e959ffba78fd 1908 // the real LedWiz protocol, any other value in the first byte
mjr 35:e959ffba78fd 1909 // means it's a PBA message. However, *valid* PBA messages
mjr 35:e959ffba78fd 1910 // always have a first byte (and in fact all 8 bytes) in the
mjr 35:e959ffba78fd 1911 // range 0-49 or 129-132. Anything else is invalid. We take
mjr 35:e959ffba78fd 1912 // advantage of this to implement private protocol extensions.
mjr 35:e959ffba78fd 1913 // So our full protocol is as follows:
mjr 35:e959ffba78fd 1914 //
mjr 35:e959ffba78fd 1915 // first byte =
mjr 35:e959ffba78fd 1916 // 0-48 -> LWZ-PBA
mjr 35:e959ffba78fd 1917 // 64 -> LWZ SBA
mjr 35:e959ffba78fd 1918 // 65 -> private control message; second byte specifies subtype
mjr 35:e959ffba78fd 1919 // 129-132 -> LWZ-PBA
mjr 35:e959ffba78fd 1920 // 200-228 -> extended bank brightness set for outputs N to N+6, where
mjr 35:e959ffba78fd 1921 // N is (first byte - 200)*7
mjr 35:e959ffba78fd 1922 // other -> reserved for future use
mjr 35:e959ffba78fd 1923 //
mjr 35:e959ffba78fd 1924 uint8_t *data = report.data;
mjr 35:e959ffba78fd 1925 if (data[0] == 64)
mjr 35:e959ffba78fd 1926 {
mjr 35:e959ffba78fd 1927 // LWZ-SBA - first four bytes are bit-packed on/off flags
mjr 35:e959ffba78fd 1928 // for the outputs; 5th byte is the pulse speed (1-7)
mjr 35:e959ffba78fd 1929 //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n",
mjr 35:e959ffba78fd 1930 // data[1], data[2], data[3], data[4], data[5]);
mjr 35:e959ffba78fd 1931
mjr 35:e959ffba78fd 1932 // update all on/off states
mjr 35:e959ffba78fd 1933 for (int i = 0, bit = 1, ri = 1 ; i < numLwOutputs ; ++i, bit <<= 1)
mjr 35:e959ffba78fd 1934 {
mjr 35:e959ffba78fd 1935 // figure the on/off state bit for this output
mjr 35:e959ffba78fd 1936 if (bit == 0x100) {
mjr 35:e959ffba78fd 1937 bit = 1;
mjr 35:e959ffba78fd 1938 ++ri;
mjr 35:e959ffba78fd 1939 }
mjr 35:e959ffba78fd 1940
mjr 35:e959ffba78fd 1941 // set the on/off state
mjr 35:e959ffba78fd 1942 wizOn[i] = ((data[ri] & bit) != 0);
mjr 35:e959ffba78fd 1943
mjr 35:e959ffba78fd 1944 // If the wizVal setting is 255, it means that this
mjr 35:e959ffba78fd 1945 // output was last set to a brightness value with the
mjr 35:e959ffba78fd 1946 // extended protocol. Return it to LedWiz control by
mjr 35:e959ffba78fd 1947 // rescaling the brightness setting to the LedWiz range
mjr 35:e959ffba78fd 1948 // and updating wizVal with the result. If it's any
mjr 35:e959ffba78fd 1949 // other value, it was previously set by a PBA message,
mjr 35:e959ffba78fd 1950 // so simply retain the last setting - in the normal
mjr 35:e959ffba78fd 1951 // LedWiz protocol, the "profile" (brightness) and on/off
mjr 35:e959ffba78fd 1952 // states are independent, so an SBA just turns an output
mjr 35:e959ffba78fd 1953 // on or off but retains its last brightness level.
mjr 35:e959ffba78fd 1954 if (wizVal[i] == 255)
mjr 35:e959ffba78fd 1955 wizVal[i] = (uint8_t)round(outLevel[i]*48);
mjr 35:e959ffba78fd 1956 }
mjr 35:e959ffba78fd 1957
mjr 35:e959ffba78fd 1958 // set the flash speed - enforce the value range 1-7
mjr 35:e959ffba78fd 1959 wizSpeed = data[5];
mjr 35:e959ffba78fd 1960 if (wizSpeed < 1)
mjr 35:e959ffba78fd 1961 wizSpeed = 1;
mjr 35:e959ffba78fd 1962 else if (wizSpeed > 7)
mjr 35:e959ffba78fd 1963 wizSpeed = 7;
mjr 35:e959ffba78fd 1964
mjr 35:e959ffba78fd 1965 // update the physical outputs
mjr 35:e959ffba78fd 1966 updateWizOuts();
mjr 35:e959ffba78fd 1967 if (hc595 != 0)
mjr 35:e959ffba78fd 1968 hc595->update();
mjr 35:e959ffba78fd 1969
mjr 35:e959ffba78fd 1970 // reset the PBA counter
mjr 35:e959ffba78fd 1971 pbaIdx = 0;
mjr 35:e959ffba78fd 1972 }
mjr 35:e959ffba78fd 1973 else if (data[0] == 65)
mjr 35:e959ffba78fd 1974 {
mjr 35:e959ffba78fd 1975 // Private control message. This isn't an LedWiz message - it's
mjr 35:e959ffba78fd 1976 // an extension for this device. 65 is an invalid PBA setting,
mjr 35:e959ffba78fd 1977 // and isn't used for any other LedWiz message, so we appropriate
mjr 35:e959ffba78fd 1978 // it for our own private use. The first byte specifies the
mjr 35:e959ffba78fd 1979 // message type.
mjr 35:e959ffba78fd 1980 if (data[1] == 1)
mjr 35:e959ffba78fd 1981 {
mjr 35:e959ffba78fd 1982 // 1 = Old Set Configuration:
mjr 35:e959ffba78fd 1983 // data[2] = LedWiz unit number (0x00 to 0x0f)
mjr 35:e959ffba78fd 1984 // data[3] = feature enable bit mask:
mjr 35:e959ffba78fd 1985 // 0x01 = enable plunger sensor
mjr 35:e959ffba78fd 1986
mjr 35:e959ffba78fd 1987 // get the new LedWiz unit number - this is 0-15, whereas we
mjr 35:e959ffba78fd 1988 // we save the *nominal* unit number 1-16 in the config
mjr 35:e959ffba78fd 1989 uint8_t newUnitNo = (data[2] & 0x0f) + 1;
mjr 33:d832bcab089e 1990
mjr 35:e959ffba78fd 1991 // we'll need a reset if the LedWiz unit number is changing
mjr 35:e959ffba78fd 1992 bool needReset = (newUnitNo != cfg.psUnitNo);
mjr 35:e959ffba78fd 1993
mjr 35:e959ffba78fd 1994 // set the configuration parameters from the message
mjr 35:e959ffba78fd 1995 cfg.psUnitNo = newUnitNo;
mjr 35:e959ffba78fd 1996 cfg.plunger.enabled = data[3] & 0x01;
mjr 35:e959ffba78fd 1997
mjr 35:e959ffba78fd 1998 // update the status flags
mjr 35:e959ffba78fd 1999 statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01);
mjr 35:e959ffba78fd 2000
mjr 35:e959ffba78fd 2001 // if the plunger is no longer enabled, use 0 for z reports
mjr 35:e959ffba78fd 2002 if (!cfg.plunger.enabled)
mjr 35:e959ffba78fd 2003 z = 0;
mjr 35:e959ffba78fd 2004
mjr 35:e959ffba78fd 2005 // save the configuration
mjr 35:e959ffba78fd 2006 saveConfigToFlash();
mjr 35:e959ffba78fd 2007
mjr 35:e959ffba78fd 2008 // reboot if necessary
mjr 35:e959ffba78fd 2009 if (needReset)
mjr 35:e959ffba78fd 2010 reboot(js);
mjr 35:e959ffba78fd 2011 }
mjr 35:e959ffba78fd 2012 else if (data[1] == 2)
mjr 35:e959ffba78fd 2013 {
mjr 35:e959ffba78fd 2014 // 2 = Calibrate plunger
mjr 35:e959ffba78fd 2015 // (No parameters)
mjr 35:e959ffba78fd 2016
mjr 35:e959ffba78fd 2017 // enter calibration mode
mjr 35:e959ffba78fd 2018 calBtnState = 3;
mjr 35:e959ffba78fd 2019 calBtnTimer.reset();
mjr 35:e959ffba78fd 2020 cfg.plunger.cal.reset(plungerSensor->npix);
mjr 35:e959ffba78fd 2021 }
mjr 35:e959ffba78fd 2022 else if (data[1] == 3)
mjr 35:e959ffba78fd 2023 {
mjr 35:e959ffba78fd 2024 // 3 = pixel dump
mjr 35:e959ffba78fd 2025 // (No parameters)
mjr 35:e959ffba78fd 2026 reportPix = true;
mjr 35:e959ffba78fd 2027
mjr 35:e959ffba78fd 2028 // show purple until we finish sending the report
mjr 35:e959ffba78fd 2029 ledR = 0;
mjr 35:e959ffba78fd 2030 ledB = 0;
mjr 35:e959ffba78fd 2031 ledG = 1;
mjr 35:e959ffba78fd 2032 }
mjr 35:e959ffba78fd 2033 else if (data[1] == 4)
mjr 35:e959ffba78fd 2034 {
mjr 35:e959ffba78fd 2035 // 4 = hardware configuration query
mjr 35:e959ffba78fd 2036 // (No parameters)
mjr 35:e959ffba78fd 2037 wait_ms(1);
mjr 35:e959ffba78fd 2038 js.reportConfig(
mjr 35:e959ffba78fd 2039 numOutputs,
mjr 35:e959ffba78fd 2040 cfg.psUnitNo - 1, // report 0-15 range for unit number (we store 1-16 internally)
mjr 35:e959ffba78fd 2041 cfg.plunger.cal.zero, cfg.plunger.cal.max);
mjr 35:e959ffba78fd 2042 }
mjr 35:e959ffba78fd 2043 else if (data[1] == 5)
mjr 35:e959ffba78fd 2044 {
mjr 35:e959ffba78fd 2045 // 5 = all outputs off, reset to LedWiz defaults
mjr 35:e959ffba78fd 2046 allOutputsOff();
mjr 35:e959ffba78fd 2047 }
mjr 35:e959ffba78fd 2048 else if (data[1] == 6)
mjr 35:e959ffba78fd 2049 {
mjr 35:e959ffba78fd 2050 // 6 = Save configuration to flash.
mjr 35:e959ffba78fd 2051 saveConfigToFlash();
mjr 35:e959ffba78fd 2052
mjr 35:e959ffba78fd 2053 // Reboot the microcontroller. Nearly all config changes
mjr 35:e959ffba78fd 2054 // require a reset, and a reset only takes a few seconds,
mjr 35:e959ffba78fd 2055 // so we don't bother tracking whether or not a reboot is
mjr 35:e959ffba78fd 2056 // really needed.
mjr 35:e959ffba78fd 2057 reboot(js);
mjr 35:e959ffba78fd 2058 }
mjr 35:e959ffba78fd 2059 }
mjr 35:e959ffba78fd 2060 else if (data[0] == 66)
mjr 35:e959ffba78fd 2061 {
mjr 35:e959ffba78fd 2062 // Extended protocol - Set configuration variable.
mjr 35:e959ffba78fd 2063 // The second byte of the message is the ID of the variable
mjr 35:e959ffba78fd 2064 // to update, and the remaining bytes give the new value,
mjr 35:e959ffba78fd 2065 // in a variable-dependent format.
mjr 35:e959ffba78fd 2066 configVarMsg(data);
mjr 35:e959ffba78fd 2067 }
mjr 35:e959ffba78fd 2068 else if (data[0] >= 200 && data[0] <= 228)
mjr 35:e959ffba78fd 2069 {
mjr 35:e959ffba78fd 2070 // Extended protocol - Extended output port brightness update.
mjr 35:e959ffba78fd 2071 // data[0]-200 gives us the bank of 7 outputs we're setting:
mjr 35:e959ffba78fd 2072 // 200 is outputs 0-6, 201 is outputs 7-13, 202 is 14-20, etc.
mjr 35:e959ffba78fd 2073 // The remaining bytes are brightness levels, 0-255, for the
mjr 35:e959ffba78fd 2074 // seven outputs in the selected bank. The LedWiz flashing
mjr 35:e959ffba78fd 2075 // modes aren't accessible in this message type; we can only
mjr 35:e959ffba78fd 2076 // set a fixed brightness, but in exchange we get 8-bit
mjr 35:e959ffba78fd 2077 // resolution rather than the paltry 0-48 scale that the real
mjr 35:e959ffba78fd 2078 // LedWiz uses. There's no separate on/off status for outputs
mjr 35:e959ffba78fd 2079 // adjusted with this message type, either, as there would be
mjr 35:e959ffba78fd 2080 // for a PBA message - setting a non-zero value immediately
mjr 35:e959ffba78fd 2081 // turns the output, overriding the last SBA setting.
mjr 35:e959ffba78fd 2082 //
mjr 35:e959ffba78fd 2083 // For outputs 0-31, this overrides any previous PBA/SBA
mjr 35:e959ffba78fd 2084 // settings for the port. Any subsequent PBA/SBA message will
mjr 35:e959ffba78fd 2085 // in turn override the setting made here. It's simple - the
mjr 35:e959ffba78fd 2086 // most recent message of either type takes precedence. For
mjr 35:e959ffba78fd 2087 // outputs above the LedWiz range, PBA/SBA messages can't
mjr 35:e959ffba78fd 2088 // address those ports anyway.
mjr 35:e959ffba78fd 2089 int i0 = (data[0] - 200)*7;
mjr 35:e959ffba78fd 2090 int i1 = i0 + 7 < numOutputs ? i0 + 7 : numOutputs;
mjr 35:e959ffba78fd 2091 for (int i = i0 ; i < i1 ; ++i)
mjr 35:e959ffba78fd 2092 {
mjr 35:e959ffba78fd 2093 // set the brightness level for the output
mjr 35:e959ffba78fd 2094 float b = data[i-i0+1]/255.0;
mjr 35:e959ffba78fd 2095 outLevel[i] = b;
mjr 35:e959ffba78fd 2096
mjr 35:e959ffba78fd 2097 // if it's in the basic LedWiz output set, set the LedWiz
mjr 35:e959ffba78fd 2098 // profile value to 255, which means "use outLevel"
mjr 35:e959ffba78fd 2099 if (i < 32)
mjr 35:e959ffba78fd 2100 wizVal[i] = 255;
mjr 35:e959ffba78fd 2101
mjr 35:e959ffba78fd 2102 // set the output
mjr 35:e959ffba78fd 2103 lwPin[i]->set(b);
mjr 35:e959ffba78fd 2104 }
mjr 35:e959ffba78fd 2105
mjr 35:e959ffba78fd 2106 // update 74HC595 outputs, if attached
mjr 35:e959ffba78fd 2107 if (hc595 != 0)
mjr 35:e959ffba78fd 2108 hc595->update();
mjr 35:e959ffba78fd 2109 }
mjr 35:e959ffba78fd 2110 else
mjr 35:e959ffba78fd 2111 {
mjr 35:e959ffba78fd 2112 // Everything else is LWZ-PBA. This is a full "profile"
mjr 35:e959ffba78fd 2113 // dump from the host for one bank of 8 outputs. Each
mjr 35:e959ffba78fd 2114 // byte sets one output in the current bank. The current
mjr 35:e959ffba78fd 2115 // bank is implied; the bank starts at 0 and is reset to 0
mjr 35:e959ffba78fd 2116 // by any LWZ-SBA message, and is incremented to the next
mjr 35:e959ffba78fd 2117 // bank by each LWZ-PBA message. Our variable pbaIdx keeps
mjr 35:e959ffba78fd 2118 // track of our notion of the current bank. There's no direct
mjr 35:e959ffba78fd 2119 // way for the host to select the bank; it just has to count
mjr 35:e959ffba78fd 2120 // on us staying in sync. In practice, the host will always
mjr 35:e959ffba78fd 2121 // send a full set of 4 PBA messages in a row to set all 32
mjr 35:e959ffba78fd 2122 // outputs.
mjr 35:e959ffba78fd 2123 //
mjr 35:e959ffba78fd 2124 // Note that a PBA implicitly overrides our extended profile
mjr 35:e959ffba78fd 2125 // messages (message prefix 200-219), because this sets the
mjr 35:e959ffba78fd 2126 // wizVal[] entry for each output, and that takes precedence
mjr 35:e959ffba78fd 2127 // over the extended protocol settings.
mjr 35:e959ffba78fd 2128 //
mjr 35:e959ffba78fd 2129 //printf("LWZ-PBA[%d] %02x %02x %02x %02x %02x %02x %02x %02x\r\n",
mjr 35:e959ffba78fd 2130 // pbaIdx, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
mjr 35:e959ffba78fd 2131
mjr 35:e959ffba78fd 2132 // Update all output profile settings
mjr 35:e959ffba78fd 2133 for (int i = 0 ; i < 8 ; ++i)
mjr 35:e959ffba78fd 2134 wizVal[pbaIdx + i] = data[i];
mjr 35:e959ffba78fd 2135
mjr 35:e959ffba78fd 2136 // Update the physical LED state if this is the last bank.
mjr 35:e959ffba78fd 2137 // Note that hosts always send a full set of four PBA
mjr 35:e959ffba78fd 2138 // messages, so there's no need to do a physical update
mjr 35:e959ffba78fd 2139 // until we've received the last bank's PBA message.
mjr 35:e959ffba78fd 2140 if (pbaIdx == 24)
mjr 35:e959ffba78fd 2141 {
mjr 35:e959ffba78fd 2142 updateWizOuts();
mjr 35:e959ffba78fd 2143 if (hc595 != 0)
mjr 35:e959ffba78fd 2144 hc595->update();
mjr 35:e959ffba78fd 2145 pbaIdx = 0;
mjr 35:e959ffba78fd 2146 }
mjr 35:e959ffba78fd 2147 else
mjr 35:e959ffba78fd 2148 pbaIdx += 8;
mjr 35:e959ffba78fd 2149 }
mjr 35:e959ffba78fd 2150 }
mjr 35:e959ffba78fd 2151 }
mjr 17:ab3cec0c8bf4 2152
mjr 17:ab3cec0c8bf4 2153 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 2154 //
mjr 5:a70c0bce770d 2155 // Main program loop. This is invoked on startup and runs forever. Our
mjr 5:a70c0bce770d 2156 // main work is to read our devices (the accelerometer and the CCD), process
mjr 5:a70c0bce770d 2157 // the readings into nudge and plunger position data, and send the results
mjr 5:a70c0bce770d 2158 // to the host computer via the USB joystick interface. We also monitor
mjr 5:a70c0bce770d 2159 // the USB connection for incoming LedWiz commands and process those into
mjr 5:a70c0bce770d 2160 // port outputs.
mjr 5:a70c0bce770d 2161 //
mjr 0:5acbbe3f4cf4 2162 int main(void)
mjr 0:5acbbe3f4cf4 2163 {
mjr 1:d913e0afb2ac 2164 // turn off our on-board indicator LED
mjr 4:02c7cd7b2183 2165 ledR = 1;
mjr 4:02c7cd7b2183 2166 ledG = 1;
mjr 4:02c7cd7b2183 2167 ledB = 1;
mjr 1:d913e0afb2ac 2168
mjr 35:e959ffba78fd 2169 // clear the I2C bus for the accelerometer
mjr 35:e959ffba78fd 2170 clear_i2c();
mjr 35:e959ffba78fd 2171
mjr 35:e959ffba78fd 2172 // load the saved configuration
mjr 35:e959ffba78fd 2173 loadConfigFromFlash();
mjr 35:e959ffba78fd 2174
mjr 33:d832bcab089e 2175 // start the TV timer, if applicable
mjr 35:e959ffba78fd 2176 startTVTimer(cfg);
mjr 33:d832bcab089e 2177
mjr 33:d832bcab089e 2178 // we're not connected/awake yet
mjr 33:d832bcab089e 2179 bool connected = false;
mjr 33:d832bcab089e 2180 time_t connectChangeTime = time(0);
mjr 33:d832bcab089e 2181
mjr 35:e959ffba78fd 2182 // create the plunger sensor interface
mjr 35:e959ffba78fd 2183 createPlunger();
mjr 33:d832bcab089e 2184
mjr 35:e959ffba78fd 2185 // set up the TLC5940 interface and start the TLC5940 clock, if applicable
mjr 35:e959ffba78fd 2186 init_tlc5940(cfg);
mjr 34:6b981a2afab7 2187
mjr 34:6b981a2afab7 2188 // enable the 74HC595 chips, if present
mjr 35:e959ffba78fd 2189 init_hc595(cfg);
mjr 6:cc35eb643e8f 2190
mjr 35:e959ffba78fd 2191 // initialize the LedWiz ports
mjr 35:e959ffba78fd 2192 initLwOut(cfg);
mjr 2:c174f9ee414a 2193
mjr 35:e959ffba78fd 2194 // start the TLC5940 clock
mjr 35:e959ffba78fd 2195 if (tlc5940 != 0)
mjr 35:e959ffba78fd 2196 tlc5940->start();
mjr 35:e959ffba78fd 2197
mjr 35:e959ffba78fd 2198 // initialize the button input ports
mjr 35:e959ffba78fd 2199 bool kbKeys = false;
mjr 35:e959ffba78fd 2200 initButtons(cfg, kbKeys);
mjr 35:e959ffba78fd 2201
mjr 6:cc35eb643e8f 2202 // Create the joystick USB client. Note that we use the LedWiz unit
mjr 6:cc35eb643e8f 2203 // number from the saved configuration.
mjr 35:e959ffba78fd 2204 MyUSBJoystick js(cfg.usbVendorID, cfg.usbProductID, USB_VERSION_NO, true, cfg.joystickEnabled, kbKeys);
mjr 17:ab3cec0c8bf4 2205
mjr 17:ab3cec0c8bf4 2206 // last report timer - we use this to throttle reports, since VP
mjr 17:ab3cec0c8bf4 2207 // doesn't want to hear from us more than about every 10ms
mjr 17:ab3cec0c8bf4 2208 Timer reportTimer;
mjr 17:ab3cec0c8bf4 2209 reportTimer.start();
mjr 35:e959ffba78fd 2210
mjr 35:e959ffba78fd 2211 // set the initial status flags
mjr 35:e959ffba78fd 2212 statusFlags = (cfg.plunger.enabled ? 0x01 : 0x00);
mjr 17:ab3cec0c8bf4 2213
mjr 17:ab3cec0c8bf4 2214 // initialize the calibration buttons, if present
mjr 35:e959ffba78fd 2215 DigitalIn *calBtn = (cfg.plunger.cal.btn == NC ? 0 : new DigitalIn(cfg.plunger.cal.btn));
mjr 35:e959ffba78fd 2216 DigitalOut *calBtnLed = (cfg.plunger.cal.led == NC ? 0 : new DigitalOut(cfg.plunger.cal.led));
mjr 6:cc35eb643e8f 2217
mjr 35:e959ffba78fd 2218 // initialize the calibration button
mjr 1:d913e0afb2ac 2219 calBtnTimer.start();
mjr 35:e959ffba78fd 2220 calBtnState = 0;
mjr 1:d913e0afb2ac 2221
mjr 1:d913e0afb2ac 2222 // set up a timer for our heartbeat indicator
mjr 1:d913e0afb2ac 2223 Timer hbTimer;
mjr 1:d913e0afb2ac 2224 hbTimer.start();
mjr 1:d913e0afb2ac 2225 int hb = 0;
mjr 5:a70c0bce770d 2226 uint16_t hbcnt = 0;
mjr 1:d913e0afb2ac 2227
mjr 1:d913e0afb2ac 2228 // set a timer for accelerometer auto-centering
mjr 1:d913e0afb2ac 2229 Timer acTimer;
mjr 1:d913e0afb2ac 2230 acTimer.start();
mjr 1:d913e0afb2ac 2231
mjr 0:5acbbe3f4cf4 2232 // create the accelerometer object
mjr 5:a70c0bce770d 2233 Accel accel(MMA8451_SCL_PIN, MMA8451_SDA_PIN, MMA8451_I2C_ADDRESS, MMA8451_INT_PIN);
mjr 0:5acbbe3f4cf4 2234
mjr 17:ab3cec0c8bf4 2235 // last accelerometer report, in joystick units (we report the nudge
mjr 17:ab3cec0c8bf4 2236 // acceleration via the joystick x & y axes, per the VP convention)
mjr 17:ab3cec0c8bf4 2237 int x = 0, y = 0;
mjr 17:ab3cec0c8bf4 2238
mjr 17:ab3cec0c8bf4 2239 // last plunger report position, in 'npix' normalized pixel units
mjr 17:ab3cec0c8bf4 2240 int pos = 0;
mjr 17:ab3cec0c8bf4 2241
mjr 17:ab3cec0c8bf4 2242 // last plunger report, in joystick units (we report the plunger as the
mjr 17:ab3cec0c8bf4 2243 // "z" axis of the joystick, per the VP convention)
mjr 17:ab3cec0c8bf4 2244 int z = 0;
mjr 17:ab3cec0c8bf4 2245
mjr 17:ab3cec0c8bf4 2246 // most recent prior plunger readings, for tracking release events(z0 is
mjr 17:ab3cec0c8bf4 2247 // reading just before the last one we reported, z1 is the one before that,
mjr 17:ab3cec0c8bf4 2248 // z2 the next before that)
mjr 17:ab3cec0c8bf4 2249 int z0 = 0, z1 = 0, z2 = 0;
mjr 17:ab3cec0c8bf4 2250
mjr 17:ab3cec0c8bf4 2251 // Simulated "bounce" position when firing. We model the bounce off of
mjr 17:ab3cec0c8bf4 2252 // the barrel spring when the plunger is released as proportional to the
mjr 17:ab3cec0c8bf4 2253 // distance it was retracted just before being released.
mjr 17:ab3cec0c8bf4 2254 int zBounce = 0;
mjr 2:c174f9ee414a 2255
mjr 17:ab3cec0c8bf4 2256 // Simulated Launch Ball button state. If a "ZB Launch Ball" port is
mjr 17:ab3cec0c8bf4 2257 // defined for our LedWiz port mapping, any time that port is turned ON,
mjr 17:ab3cec0c8bf4 2258 // we'll simulate pushing the Launch Ball button if the player pulls
mjr 17:ab3cec0c8bf4 2259 // back and releases the plunger, or simply pushes on the plunger from
mjr 17:ab3cec0c8bf4 2260 // the rest position. This allows the plunger to be used in lieu of a
mjr 17:ab3cec0c8bf4 2261 // physical Launch Ball button for tables that don't have plungers.
mjr 17:ab3cec0c8bf4 2262 //
mjr 17:ab3cec0c8bf4 2263 // States:
mjr 17:ab3cec0c8bf4 2264 // 0 = default
mjr 17:ab3cec0c8bf4 2265 // 1 = cocked (plunger has been pulled back about 1" from state 0)
mjr 17:ab3cec0c8bf4 2266 // 2 = uncocked (plunger is pulled back less than 1" from state 1)
mjr 21:5048e16cc9ef 2267 // 3 = launching, plunger is forward beyond park position
mjr 21:5048e16cc9ef 2268 // 4 = launching, plunger is behind park position
mjr 21:5048e16cc9ef 2269 // 5 = pressed and holding (plunger has been pressed forward beyond
mjr 21:5048e16cc9ef 2270 // the park position from state 0)
mjr 17:ab3cec0c8bf4 2271 int lbState = 0;
mjr 6:cc35eb643e8f 2272
mjr 35:e959ffba78fd 2273 // button bit for ZB launch ball button
mjr 35:e959ffba78fd 2274 const uint32_t lbButtonBit = (1 << (cfg.plunger.zbLaunchBall.btn - 1));
mjr 35:e959ffba78fd 2275
mjr 17:ab3cec0c8bf4 2276 // Time since last lbState transition. Some of the states are time-
mjr 17:ab3cec0c8bf4 2277 // sensitive. In the "uncocked" state, we'll return to state 0 if
mjr 17:ab3cec0c8bf4 2278 // we remain in this state for more than a few milliseconds, since
mjr 17:ab3cec0c8bf4 2279 // it indicates that the plunger is being slowly returned to rest
mjr 17:ab3cec0c8bf4 2280 // rather than released. In the "launching" state, we need to release
mjr 17:ab3cec0c8bf4 2281 // the Launch Ball button after a moment, and we need to wait for
mjr 17:ab3cec0c8bf4 2282 // the plunger to come to rest before returning to state 0.
mjr 17:ab3cec0c8bf4 2283 Timer lbTimer;
mjr 17:ab3cec0c8bf4 2284 lbTimer.start();
mjr 17:ab3cec0c8bf4 2285
mjr 18:5e890ebd0023 2286 // Launch Ball simulated push timer. We start this when we simulate
mjr 18:5e890ebd0023 2287 // the button push, and turn off the simulated button when enough time
mjr 18:5e890ebd0023 2288 // has elapsed.
mjr 18:5e890ebd0023 2289 Timer lbBtnTimer;
mjr 18:5e890ebd0023 2290
mjr 17:ab3cec0c8bf4 2291 // Simulated button states. This is a vector of button states
mjr 17:ab3cec0c8bf4 2292 // for the simulated buttons. We combine this with the physical
mjr 17:ab3cec0c8bf4 2293 // button states on each USB joystick report, so we will report
mjr 17:ab3cec0c8bf4 2294 // a button as pressed if either the physical button is being pressed
mjr 17:ab3cec0c8bf4 2295 // or we're simulating a press on the button. This is used for the
mjr 17:ab3cec0c8bf4 2296 // simulated Launch Ball button.
mjr 17:ab3cec0c8bf4 2297 uint32_t simButtons = 0;
mjr 6:cc35eb643e8f 2298
mjr 6:cc35eb643e8f 2299 // Firing in progress: we set this when we detect the start of rapid
mjr 6:cc35eb643e8f 2300 // plunger movement from a retracted position towards the rest position.
mjr 17:ab3cec0c8bf4 2301 //
mjr 17:ab3cec0c8bf4 2302 // When we detect a firing event, we send VP a series of synthetic
mjr 17:ab3cec0c8bf4 2303 // reports simulating the idealized plunger motion. The actual physical
mjr 17:ab3cec0c8bf4 2304 // motion is much too fast to report to VP; in the time between two USB
mjr 17:ab3cec0c8bf4 2305 // reports, the plunger can shoot all the way forward, rebound off of
mjr 17:ab3cec0c8bf4 2306 // the barrel spring, bounce back part way, and bounce forward again,
mjr 17:ab3cec0c8bf4 2307 // or even do all of this more than once. This means that sampling the
mjr 17:ab3cec0c8bf4 2308 // physical motion at the USB report rate would create a misleading
mjr 17:ab3cec0c8bf4 2309 // picture of the plunger motion, since our samples would catch the
mjr 17:ab3cec0c8bf4 2310 // plunger at random points in this oscillating motion. From the
mjr 17:ab3cec0c8bf4 2311 // user's perspective, the physical action that occurred is simply that
mjr 17:ab3cec0c8bf4 2312 // the plunger was released from a particular distance, so it's this
mjr 17:ab3cec0c8bf4 2313 // high-level event that we want to convey to VP. To do this, we
mjr 17:ab3cec0c8bf4 2314 // synthesize a series of reports to convey an idealized version of
mjr 17:ab3cec0c8bf4 2315 // the release motion that's perfectly synchronized to the VP reports.
mjr 17:ab3cec0c8bf4 2316 // Essentially we pretend that our USB position samples are exactly
mjr 17:ab3cec0c8bf4 2317 // aligned in time with (1) the point of retraction just before the
mjr 17:ab3cec0c8bf4 2318 // user released the plunger, (2) the point of maximum forward motion
mjr 17:ab3cec0c8bf4 2319 // just after the user released the plunger (the point of maximum
mjr 17:ab3cec0c8bf4 2320 // compression as the plunger bounces off of the barrel spring), and
mjr 17:ab3cec0c8bf4 2321 // (3) the plunger coming to rest at the park position. This series
mjr 17:ab3cec0c8bf4 2322 // of reports is synthetic in the sense that it's not what we actually
mjr 17:ab3cec0c8bf4 2323 // see on the CCD at the times of these reports - the true plunger
mjr 17:ab3cec0c8bf4 2324 // position is oscillating at high speed during this period. But at
mjr 17:ab3cec0c8bf4 2325 // the same time it conveys a more faithful picture of the true physical
mjr 17:ab3cec0c8bf4 2326 // motion to VP, and allows VP to reproduce the true physical motion
mjr 17:ab3cec0c8bf4 2327 // more faithfully in its simulation model, by correcting for the
mjr 17:ab3cec0c8bf4 2328 // relatively low sampling rate in the communication path between the
mjr 17:ab3cec0c8bf4 2329 // real plunger and VP's model plunger.
mjr 17:ab3cec0c8bf4 2330 //
mjr 17:ab3cec0c8bf4 2331 // If 'firing' is non-zero, it's the index of our current report in
mjr 17:ab3cec0c8bf4 2332 // the synthetic firing report series.
mjr 9:fd65b0a94720 2333 int firing = 0;
mjr 2:c174f9ee414a 2334
mjr 2:c174f9ee414a 2335 // start the first CCD integration cycle
mjr 35:e959ffba78fd 2336 plungerSensor->init();
mjr 10:976666ffa4ef 2337
mjr 1:d913e0afb2ac 2338 // we're all set up - now just loop, processing sensor reports and
mjr 1:d913e0afb2ac 2339 // host requests
mjr 0:5acbbe3f4cf4 2340 for (;;)
mjr 0:5acbbe3f4cf4 2341 {
mjr 18:5e890ebd0023 2342 // Look for an incoming report. Process a few input reports in
mjr 18:5e890ebd0023 2343 // a row, but stop after a few so that a barrage of inputs won't
mjr 20:4c43877327ab 2344 // starve our output event processing. Also, pause briefly between
mjr 20:4c43877327ab 2345 // reads; allowing reads to occur back-to-back seems to occasionally
mjr 20:4c43877327ab 2346 // stall the USB pipeline (for reasons unknown; I'd fix the underlying
mjr 20:4c43877327ab 2347 // problem if I knew what it was).
mjr 0:5acbbe3f4cf4 2348 HID_REPORT report;
mjr 20:4c43877327ab 2349 for (int rr = 0 ; rr < 4 && js.readNB(&report) ; ++rr, wait_ms(1))
mjr 0:5acbbe3f4cf4 2350 {
mjr 35:e959ffba78fd 2351 handleInputMsg(report, js, z);
mjr 0:5acbbe3f4cf4 2352 }
mjr 1:d913e0afb2ac 2353
mjr 1:d913e0afb2ac 2354 // check for plunger calibration
mjr 17:ab3cec0c8bf4 2355 if (calBtn != 0 && !calBtn->read())
mjr 0:5acbbe3f4cf4 2356 {
mjr 1:d913e0afb2ac 2357 // check the state
mjr 1:d913e0afb2ac 2358 switch (calBtnState)
mjr 0:5acbbe3f4cf4 2359 {
mjr 1:d913e0afb2ac 2360 case 0:
mjr 1:d913e0afb2ac 2361 // button not yet pushed - start debouncing
mjr 1:d913e0afb2ac 2362 calBtnTimer.reset();
mjr 1:d913e0afb2ac 2363 calBtnState = 1;
mjr 1:d913e0afb2ac 2364 break;
mjr 1:d913e0afb2ac 2365
mjr 1:d913e0afb2ac 2366 case 1:
mjr 1:d913e0afb2ac 2367 // pushed, not yet debounced - if the debounce time has
mjr 1:d913e0afb2ac 2368 // passed, start the hold period
mjr 9:fd65b0a94720 2369 if (calBtnTimer.read_ms() > 50)
mjr 1:d913e0afb2ac 2370 calBtnState = 2;
mjr 1:d913e0afb2ac 2371 break;
mjr 1:d913e0afb2ac 2372
mjr 1:d913e0afb2ac 2373 case 2:
mjr 1:d913e0afb2ac 2374 // in the hold period - if the button has been held down
mjr 1:d913e0afb2ac 2375 // for the entire hold period, move to calibration mode
mjr 9:fd65b0a94720 2376 if (calBtnTimer.read_ms() > 2050)
mjr 1:d913e0afb2ac 2377 {
mjr 1:d913e0afb2ac 2378 // enter calibration mode
mjr 1:d913e0afb2ac 2379 calBtnState = 3;
mjr 9:fd65b0a94720 2380 calBtnTimer.reset();
mjr 35:e959ffba78fd 2381
mjr 35:e959ffba78fd 2382 // reset the plunger calibration limits
mjr 35:e959ffba78fd 2383 cfg.plunger.cal.reset(plungerSensor->npix);
mjr 1:d913e0afb2ac 2384 }
mjr 1:d913e0afb2ac 2385 break;
mjr 2:c174f9ee414a 2386
mjr 2:c174f9ee414a 2387 case 3:
mjr 9:fd65b0a94720 2388 // Already in calibration mode - pushing the button here
mjr 9:fd65b0a94720 2389 // doesn't change the current state, but we won't leave this
mjr 9:fd65b0a94720 2390 // state as long as it's held down. So nothing changes here.
mjr 2:c174f9ee414a 2391 break;
mjr 0:5acbbe3f4cf4 2392 }
mjr 0:5acbbe3f4cf4 2393 }
mjr 1:d913e0afb2ac 2394 else
mjr 1:d913e0afb2ac 2395 {
mjr 2:c174f9ee414a 2396 // Button released. If we're in calibration mode, and
mjr 2:c174f9ee414a 2397 // the calibration time has elapsed, end the calibration
mjr 2:c174f9ee414a 2398 // and save the results to flash.
mjr 2:c174f9ee414a 2399 //
mjr 2:c174f9ee414a 2400 // Otherwise, return to the base state without saving anything.
mjr 2:c174f9ee414a 2401 // If the button is released before we make it to calibration
mjr 2:c174f9ee414a 2402 // mode, it simply cancels the attempt.
mjr 9:fd65b0a94720 2403 if (calBtnState == 3 && calBtnTimer.read_ms() > 15000)
mjr 2:c174f9ee414a 2404 {
mjr 2:c174f9ee414a 2405 // exit calibration mode
mjr 1:d913e0afb2ac 2406 calBtnState = 0;
mjr 2:c174f9ee414a 2407
mjr 6:cc35eb643e8f 2408 // save the updated configuration
mjr 35:e959ffba78fd 2409 cfg.plunger.cal.calibrated = 1;
mjr 35:e959ffba78fd 2410 saveConfigToFlash();
mjr 2:c174f9ee414a 2411 }
mjr 2:c174f9ee414a 2412 else if (calBtnState != 3)
mjr 2:c174f9ee414a 2413 {
mjr 2:c174f9ee414a 2414 // didn't make it to calibration mode - cancel the operation
mjr 1:d913e0afb2ac 2415 calBtnState = 0;
mjr 2:c174f9ee414a 2416 }
mjr 1:d913e0afb2ac 2417 }
mjr 1:d913e0afb2ac 2418
mjr 1:d913e0afb2ac 2419 // light/flash the calibration button light, if applicable
mjr 1:d913e0afb2ac 2420 int newCalBtnLit = calBtnLit;
mjr 1:d913e0afb2ac 2421 switch (calBtnState)
mjr 0:5acbbe3f4cf4 2422 {
mjr 1:d913e0afb2ac 2423 case 2:
mjr 1:d913e0afb2ac 2424 // in the hold period - flash the light
mjr 9:fd65b0a94720 2425 newCalBtnLit = ((calBtnTimer.read_ms()/250) & 1);
mjr 1:d913e0afb2ac 2426 break;
mjr 1:d913e0afb2ac 2427
mjr 1:d913e0afb2ac 2428 case 3:
mjr 1:d913e0afb2ac 2429 // calibration mode - show steady on
mjr 1:d913e0afb2ac 2430 newCalBtnLit = true;
mjr 1:d913e0afb2ac 2431 break;
mjr 1:d913e0afb2ac 2432
mjr 1:d913e0afb2ac 2433 default:
mjr 1:d913e0afb2ac 2434 // not calibrating/holding - show steady off
mjr 1:d913e0afb2ac 2435 newCalBtnLit = false;
mjr 1:d913e0afb2ac 2436 break;
mjr 1:d913e0afb2ac 2437 }
mjr 3:3514575d4f86 2438
mjr 3:3514575d4f86 2439 // light or flash the external calibration button LED, and
mjr 3:3514575d4f86 2440 // do the same with the on-board blue LED
mjr 1:d913e0afb2ac 2441 if (calBtnLit != newCalBtnLit)
mjr 1:d913e0afb2ac 2442 {
mjr 1:d913e0afb2ac 2443 calBtnLit = newCalBtnLit;
mjr 2:c174f9ee414a 2444 if (calBtnLit) {
mjr 17:ab3cec0c8bf4 2445 if (calBtnLed != 0)
mjr 17:ab3cec0c8bf4 2446 calBtnLed->write(1);
mjr 4:02c7cd7b2183 2447 ledR = 1;
mjr 4:02c7cd7b2183 2448 ledG = 1;
mjr 9:fd65b0a94720 2449 ledB = 0;
mjr 2:c174f9ee414a 2450 }
mjr 2:c174f9ee414a 2451 else {
mjr 17:ab3cec0c8bf4 2452 if (calBtnLed != 0)
mjr 17:ab3cec0c8bf4 2453 calBtnLed->write(0);
mjr 4:02c7cd7b2183 2454 ledR = 1;
mjr 4:02c7cd7b2183 2455 ledG = 1;
mjr 9:fd65b0a94720 2456 ledB = 1;
mjr 2:c174f9ee414a 2457 }
mjr 1:d913e0afb2ac 2458 }
mjr 1:d913e0afb2ac 2459
mjr 17:ab3cec0c8bf4 2460 // If the plunger is enabled, and we're not already in a firing event,
mjr 17:ab3cec0c8bf4 2461 // and the last plunger reading had the plunger pulled back at least
mjr 17:ab3cec0c8bf4 2462 // a bit, watch for plunger release events until it's time for our next
mjr 17:ab3cec0c8bf4 2463 // USB report.
mjr 35:e959ffba78fd 2464 if (!firing && cfg.plunger.enabled && z >= JOYMAX/6)
mjr 17:ab3cec0c8bf4 2465 {
mjr 17:ab3cec0c8bf4 2466 // monitor the plunger until it's time for our next report
mjr 17:ab3cec0c8bf4 2467 while (reportTimer.read_ms() < 15)
mjr 17:ab3cec0c8bf4 2468 {
mjr 17:ab3cec0c8bf4 2469 // do a fast low-res scan; if it's at or past the zero point,
mjr 17:ab3cec0c8bf4 2470 // start a firing event
mjr 35:e959ffba78fd 2471 int pos0;
mjr 35:e959ffba78fd 2472 if (plungerSensor->lowResScan(pos0) && pos0 <= cfg.plunger.cal.zero)
mjr 17:ab3cec0c8bf4 2473 firing = 1;
mjr 17:ab3cec0c8bf4 2474 }
mjr 17:ab3cec0c8bf4 2475 }
mjr 17:ab3cec0c8bf4 2476
mjr 6:cc35eb643e8f 2477 // read the plunger sensor, if it's enabled
mjr 35:e959ffba78fd 2478 if (cfg.plunger.enabled)
mjr 6:cc35eb643e8f 2479 {
mjr 6:cc35eb643e8f 2480 // start with the previous reading, in case we don't have a
mjr 6:cc35eb643e8f 2481 // clear result on this frame
mjr 6:cc35eb643e8f 2482 int znew = z;
mjr 35:e959ffba78fd 2483 if (plungerSensor->highResScan(pos))
mjr 6:cc35eb643e8f 2484 {
mjr 17:ab3cec0c8bf4 2485 // We got a new reading. If we're in calibration mode, use it
mjr 17:ab3cec0c8bf4 2486 // to figure the new calibration, otherwise adjust the new reading
mjr 17:ab3cec0c8bf4 2487 // for the established calibration.
mjr 17:ab3cec0c8bf4 2488 if (calBtnState == 3)
mjr 6:cc35eb643e8f 2489 {
mjr 17:ab3cec0c8bf4 2490 // Calibration mode. If this reading is outside of the current
mjr 17:ab3cec0c8bf4 2491 // calibration bounds, expand the bounds.
mjr 35:e959ffba78fd 2492 if (pos < cfg.plunger.cal.min)
mjr 35:e959ffba78fd 2493 cfg.plunger.cal.min = pos;
mjr 35:e959ffba78fd 2494 if (pos < cfg.plunger.cal.zero)
mjr 35:e959ffba78fd 2495 cfg.plunger.cal.zero = pos;
mjr 35:e959ffba78fd 2496 if (pos > cfg.plunger.cal.max)
mjr 35:e959ffba78fd 2497 cfg.plunger.cal.max = pos;
mjr 6:cc35eb643e8f 2498
mjr 17:ab3cec0c8bf4 2499 // normalize to the full physical range while calibrating
mjr 35:e959ffba78fd 2500 znew = int(round(float(pos)/plungerSensor->npix * JOYMAX));
mjr 17:ab3cec0c8bf4 2501 }
mjr 17:ab3cec0c8bf4 2502 else
mjr 17:ab3cec0c8bf4 2503 {
mjr 17:ab3cec0c8bf4 2504 // Not in calibration mode, so normalize the new reading to the
mjr 17:ab3cec0c8bf4 2505 // established calibration range.
mjr 17:ab3cec0c8bf4 2506 //
mjr 17:ab3cec0c8bf4 2507 // Note that negative values are allowed. Zero represents the
mjr 17:ab3cec0c8bf4 2508 // "park" position, where the plunger sits when at rest. A mechanical
mjr 23:14f8c5004cd0 2509 // plunger has a small amount of travel in the "push" direction,
mjr 17:ab3cec0c8bf4 2510 // since the barrel spring can be compressed slightly. Negative
mjr 17:ab3cec0c8bf4 2511 // values represent travel in the push direction.
mjr 35:e959ffba78fd 2512 if (pos > cfg.plunger.cal.max)
mjr 35:e959ffba78fd 2513 pos = cfg.plunger.cal.max;
mjr 35:e959ffba78fd 2514 znew = int(round(float(pos - cfg.plunger.cal.zero)
mjr 35:e959ffba78fd 2515 / (cfg.plunger.cal.max - cfg.plunger.cal.zero + 1) * JOYMAX));
mjr 6:cc35eb643e8f 2516 }
mjr 6:cc35eb643e8f 2517 }
mjr 7:100a25f8bf56 2518
mjr 17:ab3cec0c8bf4 2519 // If we're not already in a firing event, check to see if the
mjr 17:ab3cec0c8bf4 2520 // new position is forward of the last report. If it is, a firing
mjr 17:ab3cec0c8bf4 2521 // event might have started during the high-res scan. This might
mjr 17:ab3cec0c8bf4 2522 // seem unlikely given that the scan only takes about 5ms, but that
mjr 17:ab3cec0c8bf4 2523 // 5ms represents about 25-30% of our total time between reports,
mjr 17:ab3cec0c8bf4 2524 // there's about a 1 in 4 chance that a release starts during a
mjr 17:ab3cec0c8bf4 2525 // scan.
mjr 17:ab3cec0c8bf4 2526 if (!firing && z0 > 0 && znew < z0)
mjr 17:ab3cec0c8bf4 2527 {
mjr 17:ab3cec0c8bf4 2528 // The plunger has moved forward since the previous report.
mjr 17:ab3cec0c8bf4 2529 // Watch it for a few more ms to see if we can get a stable
mjr 17:ab3cec0c8bf4 2530 // new position.
mjr 35:e959ffba78fd 2531 int pos0;
mjr 35:e959ffba78fd 2532 if (plungerSensor->lowResScan(pos0))
mjr 17:ab3cec0c8bf4 2533 {
mjr 35:e959ffba78fd 2534 int pos1 = pos0;
mjr 35:e959ffba78fd 2535 Timer tw;
mjr 35:e959ffba78fd 2536 tw.start();
mjr 35:e959ffba78fd 2537 while (tw.read_ms() < 6)
mjr 17:ab3cec0c8bf4 2538 {
mjr 35:e959ffba78fd 2539 // read the new position
mjr 35:e959ffba78fd 2540 int pos2;
mjr 35:e959ffba78fd 2541 if (plungerSensor->lowResScan(pos2))
mjr 35:e959ffba78fd 2542 {
mjr 35:e959ffba78fd 2543 // If it's stable over consecutive readings, stop looping.
mjr 35:e959ffba78fd 2544 // (Count it as stable if the position is within about 1/8".
mjr 35:e959ffba78fd 2545 // pos1 and pos2 are reported in pixels, so they range from
mjr 35:e959ffba78fd 2546 // 0 to npix. The overall travel of a standard plunger is
mjr 35:e959ffba78fd 2547 // about 3.2", so we have (npix/3.2) pixels per inch, hence
mjr 35:e959ffba78fd 2548 // 1/8" is (npix/3.2)*(1/8) pixels.)
mjr 35:e959ffba78fd 2549 if (abs(pos2 - pos1) < int(plungerSensor->npix/(3.2*8)))
mjr 35:e959ffba78fd 2550 break;
mjr 35:e959ffba78fd 2551
mjr 35:e959ffba78fd 2552 // If we've crossed the rest position, and we've moved by
mjr 35:e959ffba78fd 2553 // a minimum distance from where we starting this loop, begin
mjr 35:e959ffba78fd 2554 // a firing event. (We require a minimum distance to prevent
mjr 35:e959ffba78fd 2555 // spurious firing from random analog noise in the readings
mjr 35:e959ffba78fd 2556 // when the plunger is actually just sitting still at the
mjr 35:e959ffba78fd 2557 // rest position. If it's at rest, it's normal to see small
mjr 35:e959ffba78fd 2558 // random fluctuations in the analog reading +/- 1% or so
mjr 35:e959ffba78fd 2559 // from the 0 point, especially with a sensor like a
mjr 35:e959ffba78fd 2560 // potentionemeter that reports the position as a single
mjr 35:e959ffba78fd 2561 // analog voltage.) Note that we compare the latest reading
mjr 35:e959ffba78fd 2562 // to the first reading of the loop - we don't require the
mjr 35:e959ffba78fd 2563 // threshold motion over consecutive readings, but any time
mjr 35:e959ffba78fd 2564 // over the stability wait loop.
mjr 35:e959ffba78fd 2565 if (pos1 < cfg.plunger.cal.zero
mjr 35:e959ffba78fd 2566 && abs(pos2 - pos0) > int(plungerSensor->npix/(3.2*8)))
mjr 35:e959ffba78fd 2567 {
mjr 35:e959ffba78fd 2568 firing = 1;
mjr 35:e959ffba78fd 2569 break;
mjr 35:e959ffba78fd 2570 }
mjr 35:e959ffba78fd 2571
mjr 35:e959ffba78fd 2572 // the new reading is now the prior reading
mjr 35:e959ffba78fd 2573 pos1 = pos2;
mjr 35:e959ffba78fd 2574 }
mjr 17:ab3cec0c8bf4 2575 }
mjr 17:ab3cec0c8bf4 2576 }
mjr 17:ab3cec0c8bf4 2577 }
mjr 17:ab3cec0c8bf4 2578
mjr 17:ab3cec0c8bf4 2579 // Check for a simulated Launch Ball button press, if enabled
mjr 35:e959ffba78fd 2580 if (cfg.plunger.zbLaunchBall.port != 0)
mjr 17:ab3cec0c8bf4 2581 {
mjr 18:5e890ebd0023 2582 const int cockThreshold = JOYMAX/3;
mjr 35:e959ffba78fd 2583 const int pushThreshold = int(-JOYMAX/3 * cfg.plunger.zbLaunchBall.pushDistance);
mjr 17:ab3cec0c8bf4 2584 int newState = lbState;
mjr 17:ab3cec0c8bf4 2585 switch (lbState)
mjr 17:ab3cec0c8bf4 2586 {
mjr 17:ab3cec0c8bf4 2587 case 0:
mjr 17:ab3cec0c8bf4 2588 // Base state. If the plunger is pulled back by an inch
mjr 17:ab3cec0c8bf4 2589 // or more, go to "cocked" state. If the plunger is pushed
mjr 21:5048e16cc9ef 2590 // forward by 1/4" or more, go to "pressed" state.
mjr 18:5e890ebd0023 2591 if (znew >= cockThreshold)
mjr 17:ab3cec0c8bf4 2592 newState = 1;
mjr 18:5e890ebd0023 2593 else if (znew <= pushThreshold)
mjr 21:5048e16cc9ef 2594 newState = 5;
mjr 17:ab3cec0c8bf4 2595 break;
mjr 17:ab3cec0c8bf4 2596
mjr 17:ab3cec0c8bf4 2597 case 1:
mjr 17:ab3cec0c8bf4 2598 // Cocked state. If a firing event is now in progress,
mjr 17:ab3cec0c8bf4 2599 // go to "launch" state. Otherwise, if the plunger is less
mjr 17:ab3cec0c8bf4 2600 // than 1" retracted, go to "uncocked" state - the player
mjr 17:ab3cec0c8bf4 2601 // might be slowly returning the plunger to rest so as not
mjr 17:ab3cec0c8bf4 2602 // to trigger a launch.
mjr 17:ab3cec0c8bf4 2603 if (firing || znew <= 0)
mjr 17:ab3cec0c8bf4 2604 newState = 3;
mjr 18:5e890ebd0023 2605 else if (znew < cockThreshold)
mjr 17:ab3cec0c8bf4 2606 newState = 2;
mjr 17:ab3cec0c8bf4 2607 break;
mjr 17:ab3cec0c8bf4 2608
mjr 17:ab3cec0c8bf4 2609 case 2:
mjr 17:ab3cec0c8bf4 2610 // Uncocked state. If the plunger is more than an inch
mjr 17:ab3cec0c8bf4 2611 // retracted, return to cocked state. If we've been in
mjr 17:ab3cec0c8bf4 2612 // the uncocked state for more than half a second, return
mjr 18:5e890ebd0023 2613 // to the base state. This allows the user to return the
mjr 18:5e890ebd0023 2614 // plunger to rest without triggering a launch, by moving
mjr 18:5e890ebd0023 2615 // it at manual speed to the rest position rather than
mjr 18:5e890ebd0023 2616 // releasing it.
mjr 18:5e890ebd0023 2617 if (znew >= cockThreshold)
mjr 17:ab3cec0c8bf4 2618 newState = 1;
mjr 17:ab3cec0c8bf4 2619 else if (lbTimer.read_ms() > 500)
mjr 17:ab3cec0c8bf4 2620 newState = 0;
mjr 17:ab3cec0c8bf4 2621 break;
mjr 17:ab3cec0c8bf4 2622
mjr 17:ab3cec0c8bf4 2623 case 3:
mjr 17:ab3cec0c8bf4 2624 // Launch state. If the plunger is no longer pushed
mjr 17:ab3cec0c8bf4 2625 // forward, switch to launch rest state.
mjr 18:5e890ebd0023 2626 if (znew >= 0)
mjr 17:ab3cec0c8bf4 2627 newState = 4;
mjr 17:ab3cec0c8bf4 2628 break;
mjr 17:ab3cec0c8bf4 2629
mjr 17:ab3cec0c8bf4 2630 case 4:
mjr 17:ab3cec0c8bf4 2631 // Launch rest state. If the plunger is pushed forward
mjr 17:ab3cec0c8bf4 2632 // again, switch back to launch state. If not, and we've
mjr 17:ab3cec0c8bf4 2633 // been in this state for at least 200ms, return to the
mjr 17:ab3cec0c8bf4 2634 // default state.
mjr 18:5e890ebd0023 2635 if (znew <= pushThreshold)
mjr 17:ab3cec0c8bf4 2636 newState = 3;
mjr 17:ab3cec0c8bf4 2637 else if (lbTimer.read_ms() > 200)
mjr 17:ab3cec0c8bf4 2638 newState = 0;
mjr 17:ab3cec0c8bf4 2639 break;
mjr 21:5048e16cc9ef 2640
mjr 21:5048e16cc9ef 2641 case 5:
mjr 21:5048e16cc9ef 2642 // Press-and-Hold state. If the plunger is no longer pushed
mjr 21:5048e16cc9ef 2643 // forward, AND it's been at least 50ms since we generated
mjr 21:5048e16cc9ef 2644 // the simulated Launch Ball button press, return to the base
mjr 21:5048e16cc9ef 2645 // state. The minimum time is to ensure that VP has a chance
mjr 21:5048e16cc9ef 2646 // to see the button press and to avoid transient key bounce
mjr 21:5048e16cc9ef 2647 // effects when the plunger position is right on the threshold.
mjr 21:5048e16cc9ef 2648 if (znew > pushThreshold && lbTimer.read_ms() > 50)
mjr 21:5048e16cc9ef 2649 newState = 0;
mjr 21:5048e16cc9ef 2650 break;
mjr 17:ab3cec0c8bf4 2651 }
mjr 17:ab3cec0c8bf4 2652
mjr 17:ab3cec0c8bf4 2653 // change states if desired
mjr 17:ab3cec0c8bf4 2654 if (newState != lbState)
mjr 17:ab3cec0c8bf4 2655 {
mjr 21:5048e16cc9ef 2656 // If we're entering Launch state OR we're entering the
mjr 21:5048e16cc9ef 2657 // Press-and-Hold state, AND the ZB Launch Ball LedWiz signal
mjr 21:5048e16cc9ef 2658 // is turned on, simulate a Launch Ball button press.
mjr 21:5048e16cc9ef 2659 if (((newState == 3 && lbState != 4) || newState == 5)
mjr 35:e959ffba78fd 2660 && wizOn[cfg.plunger.zbLaunchBall.port-1])
mjr 18:5e890ebd0023 2661 {
mjr 18:5e890ebd0023 2662 lbBtnTimer.reset();
mjr 18:5e890ebd0023 2663 lbBtnTimer.start();
mjr 18:5e890ebd0023 2664 simButtons |= lbButtonBit;
mjr 18:5e890ebd0023 2665 }
mjr 21:5048e16cc9ef 2666
mjr 17:ab3cec0c8bf4 2667 // if we're switching to state 0, release the button
mjr 17:ab3cec0c8bf4 2668 if (newState == 0)
mjr 35:e959ffba78fd 2669 simButtons &= ~(1 << (cfg.plunger.zbLaunchBall.btn - 1));
mjr 17:ab3cec0c8bf4 2670
mjr 17:ab3cec0c8bf4 2671 // switch to the new state
mjr 17:ab3cec0c8bf4 2672 lbState = newState;
mjr 17:ab3cec0c8bf4 2673
mjr 17:ab3cec0c8bf4 2674 // start timing in the new state
mjr 17:ab3cec0c8bf4 2675 lbTimer.reset();
mjr 17:ab3cec0c8bf4 2676 }
mjr 21:5048e16cc9ef 2677
mjr 21:5048e16cc9ef 2678 // If the Launch Ball button press is in effect, but the
mjr 21:5048e16cc9ef 2679 // ZB Launch Ball LedWiz signal is no longer turned on, turn
mjr 21:5048e16cc9ef 2680 // off the button.
mjr 21:5048e16cc9ef 2681 //
mjr 21:5048e16cc9ef 2682 // If we're in one of the Launch states (state #3 or #4),
mjr 21:5048e16cc9ef 2683 // and the button has been on for long enough, turn it off.
mjr 21:5048e16cc9ef 2684 // The Launch mode is triggered by a pull-and-release gesture.
mjr 21:5048e16cc9ef 2685 // From the user's perspective, this is just a single gesture
mjr 21:5048e16cc9ef 2686 // that should trigger just one momentary press on the Launch
mjr 21:5048e16cc9ef 2687 // Ball button. Physically, though, the plunger usually
mjr 21:5048e16cc9ef 2688 // bounces back and forth for 500ms or so before coming to
mjr 21:5048e16cc9ef 2689 // rest after this gesture. That's what the whole state
mjr 21:5048e16cc9ef 2690 // #3-#4 business is all about - we stay in this pair of
mjr 21:5048e16cc9ef 2691 // states until the plunger comes to rest. As long as we're
mjr 21:5048e16cc9ef 2692 // in these states, we won't send duplicate button presses.
mjr 21:5048e16cc9ef 2693 // But we also don't want the one button press to continue
mjr 21:5048e16cc9ef 2694 // the whole time, so we'll time it out now.
mjr 21:5048e16cc9ef 2695 //
mjr 21:5048e16cc9ef 2696 // (This could be written as one big 'if' condition, but
mjr 21:5048e16cc9ef 2697 // I'm breaking it out verbosely like this to make it easier
mjr 21:5048e16cc9ef 2698 // for human readers such as myself to comprehend the logic.)
mjr 21:5048e16cc9ef 2699 if ((simButtons & lbButtonBit) != 0)
mjr 18:5e890ebd0023 2700 {
mjr 21:5048e16cc9ef 2701 int turnOff = false;
mjr 21:5048e16cc9ef 2702
mjr 21:5048e16cc9ef 2703 // turn it off if the ZB Launch Ball signal is off
mjr 35:e959ffba78fd 2704 if (!wizOn[cfg.plunger.zbLaunchBall.port-1])
mjr 21:5048e16cc9ef 2705 turnOff = true;
mjr 21:5048e16cc9ef 2706
mjr 21:5048e16cc9ef 2707 // also turn it off if we're in state 3 or 4 ("Launch"),
mjr 21:5048e16cc9ef 2708 // and the button has been on long enough
mjr 21:5048e16cc9ef 2709 if ((lbState == 3 || lbState == 4) && lbBtnTimer.read_ms() > 250)
mjr 21:5048e16cc9ef 2710 turnOff = true;
mjr 21:5048e16cc9ef 2711
mjr 21:5048e16cc9ef 2712 // if we decided to turn off the button, do so
mjr 21:5048e16cc9ef 2713 if (turnOff)
mjr 21:5048e16cc9ef 2714 {
mjr 21:5048e16cc9ef 2715 lbBtnTimer.stop();
mjr 21:5048e16cc9ef 2716 simButtons &= ~lbButtonBit;
mjr 21:5048e16cc9ef 2717 }
mjr 18:5e890ebd0023 2718 }
mjr 17:ab3cec0c8bf4 2719 }
mjr 17:ab3cec0c8bf4 2720
mjr 17:ab3cec0c8bf4 2721 // If a firing event is in progress, generate synthetic reports to
mjr 17:ab3cec0c8bf4 2722 // describe an idealized version of the plunger motion to VP rather
mjr 17:ab3cec0c8bf4 2723 // than reporting the actual physical plunger position.
mjr 6:cc35eb643e8f 2724 //
mjr 17:ab3cec0c8bf4 2725 // We use the synthetic reports during a release event because the
mjr 17:ab3cec0c8bf4 2726 // physical plunger motion when released is too fast for VP to track.
mjr 17:ab3cec0c8bf4 2727 // VP only syncs its internal physics model with the outside world
mjr 17:ab3cec0c8bf4 2728 // about every 10ms. In that amount of time, the plunger moves
mjr 17:ab3cec0c8bf4 2729 // fast enough when released that it can shoot all the way forward,
mjr 17:ab3cec0c8bf4 2730 // bounce off of the barrel spring, and rebound part of the way
mjr 17:ab3cec0c8bf4 2731 // back. The result is the classic analog-to-digital problem of
mjr 17:ab3cec0c8bf4 2732 // sample aliasing. If we happen to time our sample during the
mjr 17:ab3cec0c8bf4 2733 // release motion so that we catch the plunger at the peak of a
mjr 17:ab3cec0c8bf4 2734 // bounce, the digital signal incorrectly looks like the plunger
mjr 17:ab3cec0c8bf4 2735 // is moving slowly forward - VP thinks we went from fully
mjr 17:ab3cec0c8bf4 2736 // retracted to half retracted in the sample interval, whereas
mjr 17:ab3cec0c8bf4 2737 // we actually traveled all the way forward and half way back,
mjr 17:ab3cec0c8bf4 2738 // so the speed VP infers is about 1/3 of the actual speed.
mjr 9:fd65b0a94720 2739 //
mjr 17:ab3cec0c8bf4 2740 // To correct this, we take advantage of our ability to sample
mjr 17:ab3cec0c8bf4 2741 // the CCD image several times in the course of a VP report. If
mjr 17:ab3cec0c8bf4 2742 // we catch the plunger near the origin after we've seen it
mjr 17:ab3cec0c8bf4 2743 // retracted, we go into Release Event mode. During this mode,
mjr 17:ab3cec0c8bf4 2744 // we stop reporting the true physical plunger position, and
mjr 17:ab3cec0c8bf4 2745 // instead report an idealized pattern: we report the plunger
mjr 17:ab3cec0c8bf4 2746 // immediately shooting forward to a position in front of the
mjr 17:ab3cec0c8bf4 2747 // park position that's in proportion to how far back the plunger
mjr 17:ab3cec0c8bf4 2748 // was just before the release, and we then report it stationary
mjr 17:ab3cec0c8bf4 2749 // at the park position. We continue to report the stationary
mjr 17:ab3cec0c8bf4 2750 // park position until the actual physical plunger motion has
mjr 17:ab3cec0c8bf4 2751 // stabilized on a new position. We then exit Release Event
mjr 17:ab3cec0c8bf4 2752 // mode and return to reporting the true physical position.
mjr 17:ab3cec0c8bf4 2753 if (firing)
mjr 6:cc35eb643e8f 2754 {
mjr 17:ab3cec0c8bf4 2755 // Firing in progress. Keep reporting the park position
mjr 17:ab3cec0c8bf4 2756 // until the physical plunger position comes to rest.
mjr 17:ab3cec0c8bf4 2757 const int restTol = JOYMAX/24;
mjr 17:ab3cec0c8bf4 2758 if (firing == 1)
mjr 6:cc35eb643e8f 2759 {
mjr 17:ab3cec0c8bf4 2760 // For the first couple of frames, show the plunger shooting
mjr 17:ab3cec0c8bf4 2761 // forward past the zero point, to simulate the momentum carrying
mjr 17:ab3cec0c8bf4 2762 // it forward to bounce off of the barrel spring. Show the
mjr 17:ab3cec0c8bf4 2763 // bounce as proportional to the distance it was retracted
mjr 17:ab3cec0c8bf4 2764 // in the prior report.
mjr 17:ab3cec0c8bf4 2765 z = zBounce = -z0/6;
mjr 17:ab3cec0c8bf4 2766 ++firing;
mjr 6:cc35eb643e8f 2767 }
mjr 17:ab3cec0c8bf4 2768 else if (firing == 2)
mjr 9:fd65b0a94720 2769 {
mjr 17:ab3cec0c8bf4 2770 // second frame - keep the bounce a little longer
mjr 17:ab3cec0c8bf4 2771 z = zBounce;
mjr 17:ab3cec0c8bf4 2772 ++firing;
mjr 17:ab3cec0c8bf4 2773 }
mjr 17:ab3cec0c8bf4 2774 else if (firing > 4
mjr 17:ab3cec0c8bf4 2775 && abs(znew - z0) < restTol
mjr 17:ab3cec0c8bf4 2776 && abs(znew - z1) < restTol
mjr 17:ab3cec0c8bf4 2777 && abs(znew - z2) < restTol)
mjr 17:ab3cec0c8bf4 2778 {
mjr 17:ab3cec0c8bf4 2779 // The physical plunger has come to rest. Exit firing
mjr 17:ab3cec0c8bf4 2780 // mode and resume reporting the actual position.
mjr 17:ab3cec0c8bf4 2781 firing = false;
mjr 17:ab3cec0c8bf4 2782 z = znew;
mjr 9:fd65b0a94720 2783 }
mjr 9:fd65b0a94720 2784 else
mjr 9:fd65b0a94720 2785 {
mjr 17:ab3cec0c8bf4 2786 // until the physical plunger comes to rest, simply
mjr 17:ab3cec0c8bf4 2787 // report the park position
mjr 9:fd65b0a94720 2788 z = 0;
mjr 17:ab3cec0c8bf4 2789 ++firing;
mjr 9:fd65b0a94720 2790 }
mjr 6:cc35eb643e8f 2791 }
mjr 6:cc35eb643e8f 2792 else
mjr 6:cc35eb643e8f 2793 {
mjr 17:ab3cec0c8bf4 2794 // not in firing mode - report the true physical position
mjr 17:ab3cec0c8bf4 2795 z = znew;
mjr 6:cc35eb643e8f 2796 }
mjr 17:ab3cec0c8bf4 2797
mjr 17:ab3cec0c8bf4 2798 // shift the new reading into the recent history buffer
mjr 6:cc35eb643e8f 2799 z2 = z1;
mjr 6:cc35eb643e8f 2800 z1 = z0;
mjr 6:cc35eb643e8f 2801 z0 = znew;
mjr 2:c174f9ee414a 2802 }
mjr 6:cc35eb643e8f 2803
mjr 11:bd9da7088e6e 2804 // update the buttons
mjr 35:e959ffba78fd 2805 readButtons(cfg);
mjr 17:ab3cec0c8bf4 2806
mjr 17:ab3cec0c8bf4 2807 // If it's been long enough since our last USB status report,
mjr 17:ab3cec0c8bf4 2808 // send the new report. We throttle the report rate because
mjr 17:ab3cec0c8bf4 2809 // it can overwhelm the PC side if we report too frequently.
mjr 17:ab3cec0c8bf4 2810 // VP only wants to sync with the real world in 10ms intervals,
mjr 35:e959ffba78fd 2811 // so reporting more frequently creates I/O overhead without
mjr 35:e959ffba78fd 2812 // doing anything to improve the simulation.
mjr 35:e959ffba78fd 2813 if (cfg.joystickEnabled && reportTimer.read_ms() > 15)
mjr 17:ab3cec0c8bf4 2814 {
mjr 17:ab3cec0c8bf4 2815 // read the accelerometer
mjr 17:ab3cec0c8bf4 2816 int xa, ya;
mjr 17:ab3cec0c8bf4 2817 accel.get(xa, ya);
mjr 17:ab3cec0c8bf4 2818
mjr 17:ab3cec0c8bf4 2819 // confine the results to our joystick axis range
mjr 17:ab3cec0c8bf4 2820 if (xa < -JOYMAX) xa = -JOYMAX;
mjr 17:ab3cec0c8bf4 2821 if (xa > JOYMAX) xa = JOYMAX;
mjr 17:ab3cec0c8bf4 2822 if (ya < -JOYMAX) ya = -JOYMAX;
mjr 17:ab3cec0c8bf4 2823 if (ya > JOYMAX) ya = JOYMAX;
mjr 17:ab3cec0c8bf4 2824
mjr 17:ab3cec0c8bf4 2825 // store the updated accelerometer coordinates
mjr 17:ab3cec0c8bf4 2826 x = xa;
mjr 17:ab3cec0c8bf4 2827 y = ya;
mjr 17:ab3cec0c8bf4 2828
mjr 21:5048e16cc9ef 2829 // Report the current plunger position UNLESS the ZB Launch Ball
mjr 21:5048e16cc9ef 2830 // signal is on, in which case just report a constant 0 value.
mjr 21:5048e16cc9ef 2831 // ZB Launch Ball turns off the plunger position because it
mjr 21:5048e16cc9ef 2832 // tells us that the table has a Launch Ball button instead of
mjr 21:5048e16cc9ef 2833 // a traditional plunger.
mjr 35:e959ffba78fd 2834 int zrep = (cfg.plunger.zbLaunchBall.port != 0 && wizOn[cfg.plunger.zbLaunchBall.port-1] ? 0 : z);
mjr 35:e959ffba78fd 2835
mjr 35:e959ffba78fd 2836 // rotate X and Y according to the device orientation in the cabinet
mjr 35:e959ffba78fd 2837 accelRotate(x, y);
mjr 35:e959ffba78fd 2838
mjr 35:e959ffba78fd 2839 // send the joystick report
mjr 35:e959ffba78fd 2840 js.update(x, y, zrep, jsButtons | simButtons, statusFlags);
mjr 21:5048e16cc9ef 2841
mjr 35:e959ffba78fd 2842 // send the keyboard report(s), if applicable
mjr 35:e959ffba78fd 2843 bool waitBeforeMedia = false;
mjr 35:e959ffba78fd 2844 if (kbState.changed)
mjr 35:e959ffba78fd 2845 {
mjr 35:e959ffba78fd 2846 js.kbUpdate(kbState.data);
mjr 35:e959ffba78fd 2847 kbState.changed = false;
mjr 35:e959ffba78fd 2848 waitBeforeMedia = true;
mjr 35:e959ffba78fd 2849 }
mjr 35:e959ffba78fd 2850 if (mediaState.changed)
mjr 35:e959ffba78fd 2851 {
mjr 35:e959ffba78fd 2852 // just sent a key report - give the channel a moment to clear before
mjr 35:e959ffba78fd 2853 // sending another report on its heels, to avoid clogging the pipe
mjr 35:e959ffba78fd 2854 if (waitBeforeMedia)
mjr 35:e959ffba78fd 2855 wait_us(1);
mjr 35:e959ffba78fd 2856
mjr 35:e959ffba78fd 2857 // send the media key report
mjr 35:e959ffba78fd 2858 js.mediaUpdate(mediaState.data);
mjr 35:e959ffba78fd 2859 mediaState.changed = false;
mjr 35:e959ffba78fd 2860 }
mjr 17:ab3cec0c8bf4 2861
mjr 17:ab3cec0c8bf4 2862 // we've just started a new report interval, so reset the timer
mjr 17:ab3cec0c8bf4 2863 reportTimer.reset();
mjr 17:ab3cec0c8bf4 2864 }
mjr 21:5048e16cc9ef 2865
mjr 10:976666ffa4ef 2866 // If we're in pixel dump mode, report all pixel exposure values
mjr 10:976666ffa4ef 2867 if (reportPix)
mjr 10:976666ffa4ef 2868 {
mjr 17:ab3cec0c8bf4 2869 // send the report
mjr 35:e959ffba78fd 2870 plungerSensor->sendExposureReport(js);
mjr 17:ab3cec0c8bf4 2871
mjr 10:976666ffa4ef 2872 // we have satisfied this request
mjr 10:976666ffa4ef 2873 reportPix = false;
mjr 10:976666ffa4ef 2874 }
mjr 10:976666ffa4ef 2875
mjr 35:e959ffba78fd 2876 // If joystick reports are turned off, send a generic status report
mjr 35:e959ffba78fd 2877 // periodically for the sake of the Windows config tool.
mjr 35:e959ffba78fd 2878 if (!cfg.joystickEnabled && reportTimer.read_ms() > 200)
mjr 21:5048e16cc9ef 2879 {
mjr 21:5048e16cc9ef 2880 js.updateStatus(0);
mjr 21:5048e16cc9ef 2881 }
mjr 21:5048e16cc9ef 2882
mjr 6:cc35eb643e8f 2883 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 2884 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 2885 printf("%d,%d\r\n", x, y);
mjr 6:cc35eb643e8f 2886 #endif
mjr 6:cc35eb643e8f 2887
mjr 33:d832bcab089e 2888 // check for connection status changes
mjr 33:d832bcab089e 2889 int newConnected = js.isConnected() && !js.isSuspended();
mjr 33:d832bcab089e 2890 if (newConnected != connected)
mjr 33:d832bcab089e 2891 {
mjr 33:d832bcab089e 2892 // give it a few seconds to stabilize
mjr 33:d832bcab089e 2893 time_t tc = time(0);
mjr 33:d832bcab089e 2894 if (tc - connectChangeTime > 3)
mjr 33:d832bcab089e 2895 {
mjr 33:d832bcab089e 2896 // note the new status
mjr 33:d832bcab089e 2897 connected = newConnected;
mjr 33:d832bcab089e 2898 connectChangeTime = tc;
mjr 33:d832bcab089e 2899
mjr 33:d832bcab089e 2900 // if we're no longer connected, turn off all outputs
mjr 33:d832bcab089e 2901 if (!connected)
mjr 33:d832bcab089e 2902 allOutputsOff();
mjr 33:d832bcab089e 2903 }
mjr 33:d832bcab089e 2904 }
mjr 33:d832bcab089e 2905
mjr 6:cc35eb643e8f 2906 // provide a visual status indication on the on-board LED
mjr 5:a70c0bce770d 2907 if (calBtnState < 2 && hbTimer.read_ms() > 1000)
mjr 1:d913e0afb2ac 2908 {
mjr 33:d832bcab089e 2909 if (!newConnected)
mjr 2:c174f9ee414a 2910 {
mjr 5:a70c0bce770d 2911 // suspended - turn off the LED
mjr 4:02c7cd7b2183 2912 ledR = 1;
mjr 4:02c7cd7b2183 2913 ledG = 1;
mjr 4:02c7cd7b2183 2914 ledB = 1;
mjr 5:a70c0bce770d 2915
mjr 5:a70c0bce770d 2916 // show a status flash every so often
mjr 5:a70c0bce770d 2917 if (hbcnt % 3 == 0)
mjr 5:a70c0bce770d 2918 {
mjr 6:cc35eb643e8f 2919 // disconnected = red/red flash; suspended = red
mjr 5:a70c0bce770d 2920 for (int n = js.isConnected() ? 1 : 2 ; n > 0 ; --n)
mjr 5:a70c0bce770d 2921 {
mjr 5:a70c0bce770d 2922 ledR = 0;
mjr 5:a70c0bce770d 2923 wait(0.05);
mjr 5:a70c0bce770d 2924 ledR = 1;
mjr 5:a70c0bce770d 2925 wait(0.25);
mjr 5:a70c0bce770d 2926 }
mjr 5:a70c0bce770d 2927 }
mjr 2:c174f9ee414a 2928 }
mjr 35:e959ffba78fd 2929 else if (cfg.plunger.enabled && !cfg.plunger.cal.calibrated)
mjr 6:cc35eb643e8f 2930 {
mjr 6:cc35eb643e8f 2931 // connected, plunger calibration needed - flash yellow/green
mjr 6:cc35eb643e8f 2932 hb = !hb;
mjr 6:cc35eb643e8f 2933 ledR = (hb ? 0 : 1);
mjr 6:cc35eb643e8f 2934 ledG = 0;
mjr 6:cc35eb643e8f 2935 ledB = 1;
mjr 6:cc35eb643e8f 2936 }
mjr 6:cc35eb643e8f 2937 else
mjr 6:cc35eb643e8f 2938 {
mjr 6:cc35eb643e8f 2939 // connected - flash blue/green
mjr 2:c174f9ee414a 2940 hb = !hb;
mjr 4:02c7cd7b2183 2941 ledR = 1;
mjr 4:02c7cd7b2183 2942 ledG = (hb ? 0 : 1);
mjr 4:02c7cd7b2183 2943 ledB = (hb ? 1 : 0);
mjr 2:c174f9ee414a 2944 }
mjr 1:d913e0afb2ac 2945
mjr 1:d913e0afb2ac 2946 // reset the heartbeat timer
mjr 1:d913e0afb2ac 2947 hbTimer.reset();
mjr 5:a70c0bce770d 2948 ++hbcnt;
mjr 1:d913e0afb2ac 2949 }
mjr 1:d913e0afb2ac 2950 }
mjr 0:5acbbe3f4cf4 2951 }