Pinscape Controller version 1 fork. This is a fork to allow for ongoing bug fixes to the original controller version, from before the major changes for the expansion board project.

Dependencies:   FastIO FastPWM SimpleDMA mbed

Fork of Pinscape_Controller by Mike R

Committer:
mjr
Date:
Fri Sep 25 18:49:53 2015 +0000
Revision:
29:582472d0bc57
Parent:
26:cb71c4af2912
Child:
30:6e9902f06f48
Test of direct bit writes instead of SPI.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mjr 5:a70c0bce770d 1 /* Copyright 2014 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 5:a70c0bce770d 20 // Pinscape Controller
mjr 5:a70c0bce770d 21 //
mjr 17:ab3cec0c8bf4 22 // "Pinscape" is the name of my custom-built virtual pinball cabinet, so I call this
mjr 17:ab3cec0c8bf4 23 // software the Pinscape Controller. I wrote it to handle several tasks that I needed
mjr 17:ab3cec0c8bf4 24 // for my cabinet. It runs on a Freescale KL25Z microcontroller, which is a small and
mjr 17:ab3cec0c8bf4 25 // inexpensive device that attaches to the cabinet PC via a USB cable, and can attach
mjr 17:ab3cec0c8bf4 26 // via custom wiring to sensors, buttons, and other devices in the cabinet.
mjr 5:a70c0bce770d 27 //
mjr 17:ab3cec0c8bf4 28 // I designed the software and hardware in this project especially for my own
mjr 17:ab3cec0c8bf4 29 // cabinet, but it uses standard interfaces in Windows and Visual Pinball, so it should
mjr 17:ab3cec0c8bf4 30 // work in any VP-based cabinet, as long as you're using the usual VP software suite.
mjr 17:ab3cec0c8bf4 31 // I've tried to document the hardware in enough detail for anyone else to duplicate
mjr 17:ab3cec0c8bf4 32 // the entire project, and the full software is open source.
mjr 5:a70c0bce770d 33 //
mjr 17:ab3cec0c8bf4 34 // The Freescale board appears to the host PC as a standard USB joystick. This works
mjr 17:ab3cec0c8bf4 35 // with the built-in Windows joystick device drivers, so there's no need to install any
mjr 17:ab3cec0c8bf4 36 // new drivers or other software on the PC. Windows should recognize the Freescale
mjr 17:ab3cec0c8bf4 37 // as a joystick when you plug it into the USB port, and Windows shouldn't ask you to
mjr 17:ab3cec0c8bf4 38 // install any drivers. If you bring up the Windows control panel for USB Game
mjr 17:ab3cec0c8bf4 39 // Controllers, this device will appear as "Pinscape Controller". *Don't* do any
mjr 17:ab3cec0c8bf4 40 // calibration with the Windows control panel or third-part calibration tools. The
mjr 17:ab3cec0c8bf4 41 // software calibrates the accelerometer portion automatically, and has its own special
mjr 17:ab3cec0c8bf4 42 // calibration procedure for the plunger sensor, if you're using that (see below).
mjr 5:a70c0bce770d 43 //
mjr 17:ab3cec0c8bf4 44 // This software provides a whole bunch of separate features. You can use any of these
mjr 17:ab3cec0c8bf4 45 // features individually or all together. If you're not using a particular feature, you
mjr 17:ab3cec0c8bf4 46 // can simply omit the extra wiring and/or hardware for that feature. You can use
mjr 17:ab3cec0c8bf4 47 // the nudging feature by itself without any extra hardware attached, since the
mjr 17:ab3cec0c8bf4 48 // accelerometer is built in to the KL25Z board.
mjr 5:a70c0bce770d 49 //
mjr 17:ab3cec0c8bf4 50 // - Nudge sensing via the KL25Z's on-board accelerometer. Nudging the cabinet
mjr 17:ab3cec0c8bf4 51 // causes small accelerations that the accelerometer can detect; these are sent to
mjr 17:ab3cec0c8bf4 52 // Visual Pinball via the joystick interface so that VP can simulate the effect
mjr 17:ab3cec0c8bf4 53 // of the real physical nudges on its simulated ball. VP has native handling for
mjr 17:ab3cec0c8bf4 54 // this type of input, so all you have to do is set some preferences in VP to tell
mjr 17:ab3cec0c8bf4 55 // it that an accelerometer is attached.
mjr 5:a70c0bce770d 56 //
mjr 5:a70c0bce770d 57 // - Plunger position sensing via an attached TAOS TSL 1410R CCD linear array sensor.
mjr 17:ab3cec0c8bf4 58 // To use this feature, you need to buy the TAOS device (it's not built in to the
mjr 17:ab3cec0c8bf4 59 // KL25Z, obviously), wire it to the KL25Z (5 wire connections between the two
mjr 17:ab3cec0c8bf4 60 // devices are required), and mount the TAOS sensor in your cabinet so that it's
mjr 17:ab3cec0c8bf4 61 // positioned properly to capture images of the physical plunger shooter rod.
mjr 17:ab3cec0c8bf4 62 //
mjr 17:ab3cec0c8bf4 63 // The physical mounting and wiring details are desribed in the project
mjr 17:ab3cec0c8bf4 64 // documentation.
mjr 17:ab3cec0c8bf4 65 //
mjr 17:ab3cec0c8bf4 66 // If the CCD is attached, the software constantly captures images from the CCD
mjr 17:ab3cec0c8bf4 67 // and analyzes them to determine how far back the plunger is pulled. It reports
mjr 17:ab3cec0c8bf4 68 // this to Visual Pinball via the joystick interface. This allows VP to make the
mjr 17:ab3cec0c8bf4 69 // simulated on-screen plunger track the motion of the physical plunger in real
mjr 17:ab3cec0c8bf4 70 // time. As with the nudge data, VP has native handling for the plunger input,
mjr 17:ab3cec0c8bf4 71 // so you just need to set the VP preferences to tell it that an analog plunger
mjr 17:ab3cec0c8bf4 72 // device is attached. One caveat, though: although VP itself has built-in
mjr 17:ab3cec0c8bf4 73 // support for an analog plunger, not all existing tables take advantage of it.
mjr 17:ab3cec0c8bf4 74 // Many existing tables have their own custom plunger scripting that doesn't
mjr 17:ab3cec0c8bf4 75 // cooperate with the VP plunger input. All tables *can* be made to work with
mjr 17:ab3cec0c8bf4 76 // the plunger, and in most cases it only requires some simple script editing,
mjr 17:ab3cec0c8bf4 77 // but in some cases it requires some more extensive surgery.
mjr 5:a70c0bce770d 78 //
mjr 6:cc35eb643e8f 79 // For best results, the plunger sensor should be calibrated. The calibration
mjr 6:cc35eb643e8f 80 // is stored in non-volatile memory on board the KL25Z, so it's only necessary
mjr 6:cc35eb643e8f 81 // to do the calibration once, when you first install everything. (You might
mjr 6:cc35eb643e8f 82 // also want to re-calibrate if you physically remove and reinstall the CCD
mjr 17:ab3cec0c8bf4 83 // sensor or the mechanical plunger, since their alignment shift change slightly
mjr 17:ab3cec0c8bf4 84 // when you put everything back together.) You can optionally install a
mjr 17:ab3cec0c8bf4 85 // dedicated momentary switch or pushbutton to activate the calibration mode;
mjr 17:ab3cec0c8bf4 86 // this is describe in the project documentation. If you don't want to bother
mjr 17:ab3cec0c8bf4 87 // with the extra button, you can also trigger calibration using the Windows
mjr 17:ab3cec0c8bf4 88 // setup software, which you can find on the Pinscape project page.
mjr 6:cc35eb643e8f 89 //
mjr 17:ab3cec0c8bf4 90 // The calibration procedure is described in the project documentation. Briefly,
mjr 17:ab3cec0c8bf4 91 // when you trigger calibration mode, the software will scan the CCD for about
mjr 17:ab3cec0c8bf4 92 // 15 seconds, during which you should simply pull the physical plunger back
mjr 17:ab3cec0c8bf4 93 // all the way, hold it for a moment, and then slowly return it to the rest
mjr 17:ab3cec0c8bf4 94 // position. (DON'T just release it from the retracted position, since that
mjr 17:ab3cec0c8bf4 95 // let it shoot forward too far. We want to measure the range from the park
mjr 17:ab3cec0c8bf4 96 // position to the fully retracted position only.)
mjr 5:a70c0bce770d 97 //
mjr 13:72dda449c3c0 98 // - Button input wiring. 24 of the KL25Z's GPIO ports are mapped as digital inputs
mjr 13:72dda449c3c0 99 // for buttons and switches. The software reports these as joystick buttons when
mjr 13:72dda449c3c0 100 // it sends reports to the PC. These can be used to wire physical pinball-style
mjr 13:72dda449c3c0 101 // buttons in the cabinet (e.g., flipper buttons, the Start button) and miscellaneous
mjr 13:72dda449c3c0 102 // switches (such as a tilt bob) to the PC. Visual Pinball can use joystick buttons
mjr 13:72dda449c3c0 103 // for input - you just have to assign a VP function to each button using VP's
mjr 13:72dda449c3c0 104 // keyboard options dialog. To wire a button physically, connect one terminal of
mjr 13:72dda449c3c0 105 // the button switch to the KL25Z ground, and connect the other terminal to the
mjr 13:72dda449c3c0 106 // the GPIO port you wish to assign to the button. See the buttonMap[] array
mjr 13:72dda449c3c0 107 // below for the available GPIO ports and their assigned joystick button numbers.
mjr 13:72dda449c3c0 108 // If you're not using a GPIO port, you can just leave it unconnected - the digital
mjr 13:72dda449c3c0 109 // inputs have built-in pull-up resistors, so an unconnected port is the same as
mjr 13:72dda449c3c0 110 // an open switch (an "off" state for the button).
mjr 13:72dda449c3c0 111 //
mjr 5:a70c0bce770d 112 // - LedWiz emulation. The KL25Z can appear to the PC as an LedWiz device, and will
mjr 5:a70c0bce770d 113 // accept and process LedWiz commands from the host. The software can turn digital
mjr 5:a70c0bce770d 114 // output ports on and off, and can set varying PWM intensitiy levels on a subset
mjr 5:a70c0bce770d 115 // of ports. (The KL25Z can only provide 6 PWM ports. Intensity level settings on
mjr 5:a70c0bce770d 116 // other ports is ignored, so non-PWM ports can only be used for simple on/off
mjr 5:a70c0bce770d 117 // devices such as contactors and solenoids.) The KL25Z can only supply 4mA on its
mjr 5:a70c0bce770d 118 // output ports, so external hardware is required to take advantage of the LedWiz
mjr 5:a70c0bce770d 119 // emulation. Many different hardware designs are possible, but there's a simple
mjr 5:a70c0bce770d 120 // reference design in the documentation that uses a Darlington array IC to
mjr 5:a70c0bce770d 121 // increase the output from each port to 500mA (the same level as the LedWiz),
mjr 5:a70c0bce770d 122 // plus an extended design that adds an optocoupler and MOSFET to provide very
mjr 5:a70c0bce770d 123 // high power handling, up to about 45A or 150W, with voltages up to 100V.
mjr 5:a70c0bce770d 124 // That will handle just about any DC device directly (wtihout relays or other
mjr 5:a70c0bce770d 125 // amplifiers), and switches fast enough to support PWM devices.
mjr 5:a70c0bce770d 126 //
mjr 5:a70c0bce770d 127 // The device can report any desired LedWiz unit number to the host, which makes
mjr 5:a70c0bce770d 128 // it possible to use the LedWiz emulation on a machine that also has one or more
mjr 5:a70c0bce770d 129 // actual LedWiz devices intalled. The LedWiz design allows for up to 16 units
mjr 5:a70c0bce770d 130 // to be installed in one machine - each one is invidually addressable by its
mjr 5:a70c0bce770d 131 // distinct unit number.
mjr 5:a70c0bce770d 132 //
mjr 5:a70c0bce770d 133 // The LedWiz emulation features are of course optional. There's no need to
mjr 5:a70c0bce770d 134 // build any of the external port hardware (or attach anything to the output
mjr 5:a70c0bce770d 135 // ports at all) if the LedWiz features aren't needed. Most people won't have
mjr 5:a70c0bce770d 136 // any use for the LedWiz features. I built them mostly as a learning exercise,
mjr 5:a70c0bce770d 137 // but with a slight practical need for a handful of extra ports (I'm using the
mjr 5:a70c0bce770d 138 // cutting-edge 10-contactor setup, so my real LedWiz is full!).
mjr 6:cc35eb643e8f 139 //
mjr 26:cb71c4af2912 140 // - Enhanced LedWiz emulation with TLC5940 PWM controller chips. You can attach
mjr 26:cb71c4af2912 141 // external PWM controller chips for controlling device outputs, instead of using
mjr 26:cb71c4af2912 142 // the limited LedWiz emulation through the on-board GPIO ports as described above.
mjr 26:cb71c4af2912 143 // The software can control a set of daisy-chained TLC5940 chips, which provide
mjr 26:cb71c4af2912 144 // 16 PWM outputs per chip. Two of these chips give you the full complement
mjr 26:cb71c4af2912 145 // of 32 output ports of an actual LedWiz, and four give you 64 ports, which
mjr 26:cb71c4af2912 146 // should be plenty for nearly any virtual pinball project.
mjr 26:cb71c4af2912 147 //
mjr 26:cb71c4af2912 148 //
mjr 6:cc35eb643e8f 149 // The on-board LED on the KL25Z flashes to indicate the current device status:
mjr 6:cc35eb643e8f 150 //
mjr 6:cc35eb643e8f 151 // two short red flashes = the device is powered but hasn't successfully
mjr 6:cc35eb643e8f 152 // connected to the host via USB (either it's not physically connected
mjr 6:cc35eb643e8f 153 // to the USB port, or there was a problem with the software handshake
mjr 6:cc35eb643e8f 154 // with the USB device driver on the computer)
mjr 6:cc35eb643e8f 155 //
mjr 6:cc35eb643e8f 156 // short red flash = the host computer is in sleep/suspend mode
mjr 6:cc35eb643e8f 157 //
mjr 6:cc35eb643e8f 158 // long red/green = the LedWiz unti number has been changed, so a reset
mjr 6:cc35eb643e8f 159 // is needed. You can simply unplug the device and plug it back in,
mjr 6:cc35eb643e8f 160 // or presss and hold the reset button on the device for a few seconds.
mjr 6:cc35eb643e8f 161 //
mjr 6:cc35eb643e8f 162 // long yellow/green = everything's working, but the plunger hasn't
mjr 6:cc35eb643e8f 163 // been calibrated; follow the calibration procedure described above.
mjr 6:cc35eb643e8f 164 // This flash mode won't appear if the CCD has been disabled. Note
mjr 18:5e890ebd0023 165 // that the device can't tell whether a CCD is physically attached;
mjr 18:5e890ebd0023 166 // if you don't have a CCD attached, you can set the appropriate option
mjr 18:5e890ebd0023 167 // in config.h or use the Windows config tool to disable the CCD
mjr 18:5e890ebd0023 168 // software features.
mjr 6:cc35eb643e8f 169 //
mjr 6:cc35eb643e8f 170 // alternating blue/green = everything's working
mjr 6:cc35eb643e8f 171 //
mjr 6:cc35eb643e8f 172 // Software configuration: you can change option settings by sending special
mjr 6:cc35eb643e8f 173 // USB commands from the PC. I've provided a Windows program for this purpose;
mjr 6:cc35eb643e8f 174 // refer to the documentation for details. For reference, here's the format
mjr 6:cc35eb643e8f 175 // of the USB command for option changes:
mjr 6:cc35eb643e8f 176 //
mjr 6:cc35eb643e8f 177 // length of report = 8 bytes
mjr 6:cc35eb643e8f 178 // byte 0 = 65 (0x41)
mjr 6:cc35eb643e8f 179 // byte 1 = 1 (0x01)
mjr 6:cc35eb643e8f 180 // byte 2 = new LedWiz unit number, 0x01 to 0x0f
mjr 6:cc35eb643e8f 181 // byte 3 = feature enable bit mask:
mjr 6:cc35eb643e8f 182 // 0x01 = enable CCD (default = on)
mjr 9:fd65b0a94720 183 //
mjr 9:fd65b0a94720 184 // Plunger calibration mode: the host can activate plunger calibration mode
mjr 9:fd65b0a94720 185 // by sending this packet. This has the same effect as pressing and holding
mjr 9:fd65b0a94720 186 // the plunger calibration button for two seconds, to allow activating this
mjr 9:fd65b0a94720 187 // mode without attaching a physical button.
mjr 9:fd65b0a94720 188 //
mjr 9:fd65b0a94720 189 // length = 8 bytes
mjr 9:fd65b0a94720 190 // byte 0 = 65 (0x41)
mjr 9:fd65b0a94720 191 // byte 1 = 2 (0x02)
mjr 9:fd65b0a94720 192 //
mjr 10:976666ffa4ef 193 // Exposure reports: the host can request a report of the full set of pixel
mjr 10:976666ffa4ef 194 // values for the next frame by sending this special packet:
mjr 10:976666ffa4ef 195 //
mjr 10:976666ffa4ef 196 // length = 8 bytes
mjr 10:976666ffa4ef 197 // byte 0 = 65 (0x41)
mjr 10:976666ffa4ef 198 // byte 1 = 3 (0x03)
mjr 10:976666ffa4ef 199 //
mjr 10:976666ffa4ef 200 // We'll respond with a series of special reports giving the exposure status.
mjr 10:976666ffa4ef 201 // Each report has the following structure:
mjr 10:976666ffa4ef 202 //
mjr 10:976666ffa4ef 203 // bytes 0:1 = 11-bit index, with high 5 bits set to 10000. For
mjr 10:976666ffa4ef 204 // example, 0x04 0x80 indicates index 4. This is the
mjr 10:976666ffa4ef 205 // starting pixel number in the report. The first report
mjr 10:976666ffa4ef 206 // will be 0x00 0x80 to indicate pixel #0.
mjr 10:976666ffa4ef 207 // bytes 2:3 = 16-bit unsigned int brightness level of pixel at index
mjr 10:976666ffa4ef 208 // bytes 4:5 = brightness of pixel at index+1
mjr 10:976666ffa4ef 209 // etc for the rest of the packet
mjr 10:976666ffa4ef 210 //
mjr 10:976666ffa4ef 211 // This still has the form of a joystick packet at the USB level, but
mjr 10:976666ffa4ef 212 // can be differentiated by the host via the status bits. It would have
mjr 10:976666ffa4ef 213 // been cleaner to use a different Report ID at the USB level, but this
mjr 10:976666ffa4ef 214 // would have necessitated a different container structure in the report
mjr 10:976666ffa4ef 215 // descriptor, which would have broken LedWiz compatibility. Given that
mjr 10:976666ffa4ef 216 // constraint, we have to re-use the joystick report type, making for
mjr 10:976666ffa4ef 217 // this somewhat kludgey approach.
mjr 6:cc35eb643e8f 218
mjr 0:5acbbe3f4cf4 219 #include "mbed.h"
mjr 6:cc35eb643e8f 220 #include "math.h"
mjr 0:5acbbe3f4cf4 221 #include "USBJoystick.h"
mjr 0:5acbbe3f4cf4 222 #include "MMA8451Q.h"
mjr 1:d913e0afb2ac 223 #include "tsl1410r.h"
mjr 1:d913e0afb2ac 224 #include "FreescaleIAP.h"
mjr 2:c174f9ee414a 225 #include "crc32.h"
mjr 26:cb71c4af2912 226 #include "TLC5940.h"
mjr 2:c174f9ee414a 227
mjr 17:ab3cec0c8bf4 228 // our local configuration file
mjr 21:5048e16cc9ef 229 #define DECL_EXTERNS
mjr 17:ab3cec0c8bf4 230 #include "config.h"
mjr 17:ab3cec0c8bf4 231
mjr 5:a70c0bce770d 232
mjr 5:a70c0bce770d 233 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 234 // utilities
mjr 17:ab3cec0c8bf4 235
mjr 17:ab3cec0c8bf4 236 // number of elements in an array
mjr 17:ab3cec0c8bf4 237 #define countof(x) (sizeof(x)/sizeof((x)[0]))
mjr 17:ab3cec0c8bf4 238
mjr 26:cb71c4af2912 239 // floating point square of a number
mjr 26:cb71c4af2912 240 inline float square(float x) { return x*x; }
mjr 26:cb71c4af2912 241
mjr 26:cb71c4af2912 242 // floating point rounding
mjr 26:cb71c4af2912 243 inline float round(float x) { return x > 0 ? floor(x + 0.5) : ceil(x - 0.5); }
mjr 26:cb71c4af2912 244
mjr 17:ab3cec0c8bf4 245
mjr 17:ab3cec0c8bf4 246 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 247 // USB device vendor ID, product ID, and version.
mjr 5:a70c0bce770d 248 //
mjr 5:a70c0bce770d 249 // We use the vendor ID for the LedWiz, so that the PC-side software can
mjr 5:a70c0bce770d 250 // identify us as capable of performing LedWiz commands. The LedWiz uses
mjr 5:a70c0bce770d 251 // a product ID value from 0xF0 to 0xFF; the last four bits identify the
mjr 5:a70c0bce770d 252 // unit number (e.g., product ID 0xF7 means unit #7). This allows multiple
mjr 5:a70c0bce770d 253 // LedWiz units to be installed in a single PC; the software on the PC side
mjr 5:a70c0bce770d 254 // uses the unit number to route commands to the devices attached to each
mjr 5:a70c0bce770d 255 // unit. On the real LedWiz, the unit number must be set in the firmware
mjr 5:a70c0bce770d 256 // at the factory; it's not configurable by the end user. Most LedWiz's
mjr 5:a70c0bce770d 257 // ship with the unit number set to 0, but the vendor will set different
mjr 5:a70c0bce770d 258 // unit numbers if requested at the time of purchase. So if you have a
mjr 5:a70c0bce770d 259 // single LedWiz already installed in your cabinet, and you didn't ask for
mjr 5:a70c0bce770d 260 // a non-default unit number, your existing LedWiz will be unit 0.
mjr 5:a70c0bce770d 261 //
mjr 6:cc35eb643e8f 262 // Note that the USB_PRODUCT_ID value set here omits the unit number. We
mjr 6:cc35eb643e8f 263 // take the unit number from the saved configuration. We provide a
mjr 6:cc35eb643e8f 264 // configuration command that can be sent via the USB connection to change
mjr 6:cc35eb643e8f 265 // the unit number, so that users can select the unit number without having
mjr 6:cc35eb643e8f 266 // to install a different version of the software. We'll combine the base
mjr 6:cc35eb643e8f 267 // product ID here with the unit number to get the actual product ID that
mjr 6:cc35eb643e8f 268 // we send to the USB controller.
mjr 5:a70c0bce770d 269 const uint16_t USB_VENDOR_ID = 0xFAFA;
mjr 6:cc35eb643e8f 270 const uint16_t USB_PRODUCT_ID = 0x00F0;
mjr 6:cc35eb643e8f 271 const uint16_t USB_VERSION_NO = 0x0006;
mjr 0:5acbbe3f4cf4 272
mjr 5:a70c0bce770d 273
mjr 6:cc35eb643e8f 274 // Joystick axis report range - we report from -JOYMAX to +JOYMAX
mjr 6:cc35eb643e8f 275 #define JOYMAX 4096
mjr 6:cc35eb643e8f 276
mjr 25:e22b88bd783a 277 // --------------------------------------------------------------------------
mjr 25:e22b88bd783a 278 //
mjr 25:e22b88bd783a 279 // Set up mappings for the joystick X and Y reports based on the mounting
mjr 25:e22b88bd783a 280 // orientation of the KL25Z in the cabinet. Visual Pinball and other
mjr 25:e22b88bd783a 281 // pinball software effectively use video coordinates to define the axes:
mjr 25:e22b88bd783a 282 // positive X is to the right of the table, negative Y to the left, positive
mjr 25:e22b88bd783a 283 // Y toward the front of the table, negative Y toward the back. The KL25Z
mjr 25:e22b88bd783a 284 // accelerometer is mounted on the board with positive Y toward the USB
mjr 25:e22b88bd783a 285 // ports and positive X toward the right side of the board with the USB
mjr 25:e22b88bd783a 286 // ports pointing up. It's a simple matter to remap the KL25Z coordinate
mjr 25:e22b88bd783a 287 // system to match VP's coordinate system for mounting orientations at
mjr 25:e22b88bd783a 288 // 90-degree increments...
mjr 25:e22b88bd783a 289 //
mjr 25:e22b88bd783a 290 #if defined(ORIENTATION_PORTS_AT_FRONT)
mjr 25:e22b88bd783a 291 # define JOY_X(x, y) (y)
mjr 25:e22b88bd783a 292 # define JOY_Y(x, y) (x)
mjr 25:e22b88bd783a 293 #elif defined(ORIENTATION_PORTS_AT_LEFT)
mjr 25:e22b88bd783a 294 # define JOY_X(x, y) (-(x))
mjr 25:e22b88bd783a 295 # define JOY_Y(x, y) (y)
mjr 25:e22b88bd783a 296 #elif defined(ORIENTATION_PORTS_AT_RIGHT)
mjr 25:e22b88bd783a 297 # define JOY_X(x, y) (x)
mjr 25:e22b88bd783a 298 # define JOY_Y(x, y) (-(y))
mjr 25:e22b88bd783a 299 #elif defined(ORIENTATION_PORTS_AT_REAR)
mjr 25:e22b88bd783a 300 # define JOY_X(x, y) (-(y))
mjr 25:e22b88bd783a 301 # define JOY_Y(x, y) (-(x))
mjr 25:e22b88bd783a 302 #else
mjr 25:e22b88bd783a 303 # error Please define one of the ORIENTATION_PORTS_AT_xxx macros to establish the accelerometer orientation in your cabinet
mjr 25:e22b88bd783a 304 #endif
mjr 25:e22b88bd783a 305
mjr 25:e22b88bd783a 306
mjr 5:a70c0bce770d 307
mjr 17:ab3cec0c8bf4 308 // --------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 309 //
mjr 21:5048e16cc9ef 310 // Define a symbol to tell us whether any sort of plunger sensor code
mjr 21:5048e16cc9ef 311 // is enabled in this build. Note that this doesn't tell us that a
mjr 21:5048e16cc9ef 312 // plunger device is actually attached or *currently* enabled; it just
mjr 21:5048e16cc9ef 313 // tells us whether or not the code for plunger sensing is enabled in
mjr 21:5048e16cc9ef 314 // the software build. This lets us leave out some unnecessary code
mjr 21:5048e16cc9ef 315 // on installations where no physical plunger is attached.
mjr 17:ab3cec0c8bf4 316 //
mjr 21:5048e16cc9ef 317 const int PLUNGER_CODE_ENABLED =
mjr 21:5048e16cc9ef 318 #if defined(ENABLE_CCD_SENSOR) || defined(ENABLE_POT_SENSOR)
mjr 21:5048e16cc9ef 319 1;
mjr 17:ab3cec0c8bf4 320 #else
mjr 21:5048e16cc9ef 321 0;
mjr 17:ab3cec0c8bf4 322 #endif
mjr 9:fd65b0a94720 323
mjr 17:ab3cec0c8bf4 324 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 325 //
mjr 17:ab3cec0c8bf4 326 // On-board RGB LED elements - we use these for diagnostic displays.
mjr 17:ab3cec0c8bf4 327 //
mjr 26:cb71c4af2912 328 // Note that LED3 (the blue segment) is hard-wired on the KL25Z to PTD1,
mjr 26:cb71c4af2912 329 // so PTD1 shouldn't be used for any other purpose (e.g., as a keyboard
mjr 26:cb71c4af2912 330 // input or a device output). (This is kind of unfortunate in that it's
mjr 26:cb71c4af2912 331 // one of only two ports exposed on the jumper pins that can be muxed to
mjr 26:cb71c4af2912 332 // SPI0 SCLK. This effectively limits us to PTC5 if we want to use the
mjr 26:cb71c4af2912 333 // SPI capability.)
mjr 26:cb71c4af2912 334 //
mjr 17:ab3cec0c8bf4 335 DigitalOut ledR(LED1), ledG(LED2), ledB(LED3);
mjr 17:ab3cec0c8bf4 336
mjr 9:fd65b0a94720 337
mjr 9:fd65b0a94720 338 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 339 //
mjr 29:582472d0bc57 340 // LedWiz emulation, and enhanced TLC5940 output controller
mjr 5:a70c0bce770d 341 //
mjr 26:cb71c4af2912 342 // There are two modes for this feature. The default mode uses the on-board
mjr 26:cb71c4af2912 343 // GPIO ports to implement device outputs - each LedWiz software port is
mjr 26:cb71c4af2912 344 // connected to a physical GPIO pin on the KL25Z. The KL25Z only has 10
mjr 26:cb71c4af2912 345 // PWM channels, so in this mode only 10 LedWiz ports will be dimmable; the
mjr 26:cb71c4af2912 346 // rest are strictly on/off. The KL25Z also has a limited number of GPIO
mjr 26:cb71c4af2912 347 // ports overall - not enough for the full complement of 32 LedWiz ports
mjr 26:cb71c4af2912 348 // and 24 VP joystick inputs, so it's necessary to trade one against the
mjr 26:cb71c4af2912 349 // other if both features are to be used.
mjr 26:cb71c4af2912 350 //
mjr 26:cb71c4af2912 351 // The alternative, enhanced mode uses external TLC5940 PWM controller
mjr 26:cb71c4af2912 352 // chips to control device outputs. In this mode, each LedWiz software
mjr 26:cb71c4af2912 353 // port is mapped to an output on one of the external TLC5940 chips.
mjr 26:cb71c4af2912 354 // Two 5940s is enough for the full set of 32 LedWiz ports, and we can
mjr 26:cb71c4af2912 355 // support even more chips for even more outputs (although doing so requires
mjr 26:cb71c4af2912 356 // breaking LedWiz compatibility, since the LedWiz USB protocol is hardwired
mjr 26:cb71c4af2912 357 // for 32 outputs). Every port in this mode has full PWM support.
mjr 26:cb71c4af2912 358 //
mjr 5:a70c0bce770d 359
mjr 29:582472d0bc57 360 // Figure the number of outputs. If we're in the default LedWiz mode,
mjr 29:582472d0bc57 361 // we have a fixed set of 32 outputs. If we're in TLC5940 enhanced mode,
mjr 29:582472d0bc57 362 // we have 16 outputs per chip. To simplify the LedWiz compatibility code,
mjr 29:582472d0bc57 363 // always use a minimum of 32 outputs even if we have fewer than two of the
mjr 29:582472d0bc57 364 // TLC5940 chips.
mjr 29:582472d0bc57 365 #if !defined(ENABLE_TLC5940) || (TLC_NCHIPS) < 2
mjr 29:582472d0bc57 366 # define NUM_OUTPUTS 32
mjr 29:582472d0bc57 367 #else
mjr 29:582472d0bc57 368 # define NUM_OUTPUTS ((TLC5940_NCHIPS)*16)
mjr 29:582472d0bc57 369 #endif
mjr 29:582472d0bc57 370
mjr 26:cb71c4af2912 371 // Current starting output index for "PBA" messages from the PC (using
mjr 26:cb71c4af2912 372 // the LedWiz USB protocol). Each PBA message implicitly uses the
mjr 26:cb71c4af2912 373 // current index as the starting point for the ports referenced in
mjr 26:cb71c4af2912 374 // the message, and increases it (by 8) for the next call.
mjr 0:5acbbe3f4cf4 375 static int pbaIdx = 0;
mjr 0:5acbbe3f4cf4 376
mjr 26:cb71c4af2912 377 // Generic LedWiz output port interface. We create a cover class to
mjr 26:cb71c4af2912 378 // virtualize digital vs PWM outputs, and on-board KL25Z GPIO vs external
mjr 26:cb71c4af2912 379 // TLC5940 outputs, and give them all a common interface.
mjr 6:cc35eb643e8f 380 class LwOut
mjr 6:cc35eb643e8f 381 {
mjr 6:cc35eb643e8f 382 public:
mjr 26:cb71c4af2912 383 // Set the output intensity. 'val' is 0.0 for fully off, 1.0 for
mjr 26:cb71c4af2912 384 // fully on, and fractional values for intermediate intensities.
mjr 6:cc35eb643e8f 385 virtual void set(float val) = 0;
mjr 6:cc35eb643e8f 386 };
mjr 26:cb71c4af2912 387
mjr 26:cb71c4af2912 388
mjr 26:cb71c4af2912 389 #ifdef ENABLE_TLC5940
mjr 26:cb71c4af2912 390
mjr 26:cb71c4af2912 391 // The TLC5940 interface object.
mjr 26:cb71c4af2912 392 TLC5940 tlc5940(TLC5940_SCLK, TLC5940_SIN, TLC5940_GSCLK, TLC5940_BLANK,
mjr 26:cb71c4af2912 393 TLC5940_XLAT, TLC5940_NCHIPS);
mjr 26:cb71c4af2912 394
mjr 26:cb71c4af2912 395 // LwOut class for TLC5940 outputs. These are fully PWM capable.
mjr 26:cb71c4af2912 396 // The 'idx' value in the constructor is the output index in the
mjr 26:cb71c4af2912 397 // daisy-chained TLC5940 array. 0 is output #0 on the first chip,
mjr 26:cb71c4af2912 398 // 1 is #1 on the first chip, 15 is #15 on the first chip, 16 is
mjr 26:cb71c4af2912 399 // #0 on the second chip, 32 is #0 on the third chip, etc.
mjr 26:cb71c4af2912 400 class Lw5940Out: public LwOut
mjr 26:cb71c4af2912 401 {
mjr 26:cb71c4af2912 402 public:
mjr 26:cb71c4af2912 403 Lw5940Out(int idx) : idx(idx) { prv = -1; }
mjr 26:cb71c4af2912 404 virtual void set(float val)
mjr 26:cb71c4af2912 405 {
mjr 26:cb71c4af2912 406 if (val != prv)
mjr 29:582472d0bc57 407 tlc5940.set(idx, (int)(val * 4095));
mjr 26:cb71c4af2912 408 }
mjr 26:cb71c4af2912 409 int idx;
mjr 26:cb71c4af2912 410 float prv;
mjr 26:cb71c4af2912 411 };
mjr 26:cb71c4af2912 412
mjr 26:cb71c4af2912 413 #else // ENABLE_TLC5940
mjr 26:cb71c4af2912 414
mjr 26:cb71c4af2912 415 //
mjr 26:cb71c4af2912 416 // Default LedWiz mode - using on-board GPIO ports. In this mode, we
mjr 26:cb71c4af2912 417 // assign a KL25Z GPIO port to each LedWiz output. We have to use a
mjr 26:cb71c4af2912 418 // mix of PWM-capable and Digital-Only ports in this configuration,
mjr 26:cb71c4af2912 419 // since the KL25Z hardware only has 10 PWM channels, which isn't
mjr 26:cb71c4af2912 420 // enough to fill out the full complement of 32 LedWiz outputs.
mjr 26:cb71c4af2912 421 //
mjr 26:cb71c4af2912 422
mjr 26:cb71c4af2912 423 // LwOut class for a PWM-capable GPIO port
mjr 6:cc35eb643e8f 424 class LwPwmOut: public LwOut
mjr 6:cc35eb643e8f 425 {
mjr 6:cc35eb643e8f 426 public:
mjr 13:72dda449c3c0 427 LwPwmOut(PinName pin) : p(pin) { prv = -1; }
mjr 13:72dda449c3c0 428 virtual void set(float val)
mjr 13:72dda449c3c0 429 {
mjr 13:72dda449c3c0 430 if (val != prv)
mjr 13:72dda449c3c0 431 p.write(prv = val);
mjr 13:72dda449c3c0 432 }
mjr 6:cc35eb643e8f 433 PwmOut p;
mjr 13:72dda449c3c0 434 float prv;
mjr 6:cc35eb643e8f 435 };
mjr 26:cb71c4af2912 436
mjr 26:cb71c4af2912 437 // LwOut class for a Digital-Only (Non-PWM) GPIO port
mjr 6:cc35eb643e8f 438 class LwDigOut: public LwOut
mjr 6:cc35eb643e8f 439 {
mjr 6:cc35eb643e8f 440 public:
mjr 13:72dda449c3c0 441 LwDigOut(PinName pin) : p(pin) { prv = -1; }
mjr 13:72dda449c3c0 442 virtual void set(float val)
mjr 13:72dda449c3c0 443 {
mjr 13:72dda449c3c0 444 if (val != prv)
mjr 13:72dda449c3c0 445 p.write((prv = val) == 0.0 ? 0 : 1);
mjr 13:72dda449c3c0 446 }
mjr 6:cc35eb643e8f 447 DigitalOut p;
mjr 13:72dda449c3c0 448 float prv;
mjr 6:cc35eb643e8f 449 };
mjr 26:cb71c4af2912 450
mjr 26:cb71c4af2912 451 #endif // ENABLE_TLC5940
mjr 26:cb71c4af2912 452
mjr 26:cb71c4af2912 453 // LwOut class for unmapped ports. The LedWiz protocol is hardwired
mjr 26:cb71c4af2912 454 // for 32 ports, but we might not want to assign all 32 software ports
mjr 26:cb71c4af2912 455 // to physical output pins - the KL25Z has a limited number of GPIO
mjr 26:cb71c4af2912 456 // ports, so we might not have enough available GPIOs to fill out the
mjr 26:cb71c4af2912 457 // full LedWiz complement after assigning GPIOs for other functions.
mjr 26:cb71c4af2912 458 // This class is used to populate the LedWiz mapping array for ports
mjr 26:cb71c4af2912 459 // that aren't connected to physical outputs; it simply ignores value
mjr 26:cb71c4af2912 460 // changes.
mjr 11:bd9da7088e6e 461 class LwUnusedOut: public LwOut
mjr 11:bd9da7088e6e 462 {
mjr 11:bd9da7088e6e 463 public:
mjr 11:bd9da7088e6e 464 LwUnusedOut() { }
mjr 11:bd9da7088e6e 465 virtual void set(float val) { }
mjr 11:bd9da7088e6e 466 };
mjr 6:cc35eb643e8f 467
mjr 29:582472d0bc57 468 // Array of output physical pin assignments. This array is indexed
mjr 29:582472d0bc57 469 // by LedWiz logical port number - lwPin[n] is the maping for LedWiz
mjr 29:582472d0bc57 470 // port n (0-based). If we're using GPIO ports to implement outputs,
mjr 29:582472d0bc57 471 // we initialize the array at start-up to map each logical port to the
mjr 29:582472d0bc57 472 // physical GPIO pin for the port specified in the ledWizPortMap[]
mjr 29:582472d0bc57 473 // array in config.h. If we're using TLC5940 chips for the outputs,
mjr 29:582472d0bc57 474 // we map each logical port to the corresponding TLC5940 output.
mjr 29:582472d0bc57 475 static LwOut *lwPin[NUM_OUTPUTS];
mjr 6:cc35eb643e8f 476
mjr 6:cc35eb643e8f 477 // initialize the output pin array
mjr 6:cc35eb643e8f 478 void initLwOut()
mjr 6:cc35eb643e8f 479 {
mjr 9:fd65b0a94720 480 for (int i = 0 ; i < countof(lwPin) ; ++i)
mjr 6:cc35eb643e8f 481 {
mjr 26:cb71c4af2912 482 #ifdef ENABLE_TLC5940
mjr 26:cb71c4af2912 483 // Set up a TLC5940 output. If the output is within range of
mjr 26:cb71c4af2912 484 // the connected number of chips (16 outputs per chip), assign it
mjr 26:cb71c4af2912 485 // to the current index, otherwise leave it unattached.
mjr 29:582472d0bc57 486 if (i < (TLC5940_NCHIPS)*16)
mjr 26:cb71c4af2912 487 lwPin[i] = new Lw5940Out(i);
mjr 26:cb71c4af2912 488 else
mjr 26:cb71c4af2912 489 lwPin[i] = new LwUnusedOut();
mjr 26:cb71c4af2912 490
mjr 26:cb71c4af2912 491 #else // ENABLE_TLC5940
mjr 29:582472d0bc57 492 // Set up the GPIO pin. If the pin is not connected ("NC" in the
mjr 29:582472d0bc57 493 // pin map), set up a dummy "unused" output for it. If it's a
mjr 29:582472d0bc57 494 // real pin, set up a PWM-capable or Digital-Only output handler
mjr 29:582472d0bc57 495 // object, according to the pin type in the map.
mjr 11:bd9da7088e6e 496 PinName p = (i < countof(ledWizPortMap) ? ledWizPortMap[i].pin : NC);
mjr 11:bd9da7088e6e 497 if (p == NC)
mjr 11:bd9da7088e6e 498 lwPin[i] = new LwUnusedOut();
mjr 11:bd9da7088e6e 499 else if (ledWizPortMap[i].isPWM)
mjr 11:bd9da7088e6e 500 lwPin[i] = new LwPwmOut(p);
mjr 11:bd9da7088e6e 501 else
mjr 11:bd9da7088e6e 502 lwPin[i] = new LwDigOut(p);
mjr 26:cb71c4af2912 503
mjr 26:cb71c4af2912 504 #endif // ENABLE_TLC5940
mjr 26:cb71c4af2912 505
mjr 6:cc35eb643e8f 506 }
mjr 6:cc35eb643e8f 507 }
mjr 6:cc35eb643e8f 508
mjr 29:582472d0bc57 509 // Current absolute brightness level for an output. This is a float
mjr 29:582472d0bc57 510 // value from 0.0 for fully off to 1.0 for fully on. This is the final
mjr 29:582472d0bc57 511 // derived value for the port. For outputs set by LedWiz messages,
mjr 29:582472d0bc57 512 // this is derived from te LedWiz state, and is updated on each pulse
mjr 29:582472d0bc57 513 // timer interrupt for lights in flashing states. For outputs set by
mjr 29:582472d0bc57 514 // extended protocol messages, this is simply the brightness last set.
mjr 29:582472d0bc57 515 static float outLevel[NUM_OUTPUTS];
mjr 29:582472d0bc57 516
mjr 29:582472d0bc57 517 // LedWiz output states.
mjr 29:582472d0bc57 518 //
mjr 29:582472d0bc57 519 // The LedWiz protocol has two separate control axes for each output.
mjr 29:582472d0bc57 520 // One axis is its on/off state; the other is its "profile" state, which
mjr 29:582472d0bc57 521 // is either a fixed brightness or a blinking pattern for the light.
mjr 29:582472d0bc57 522 // The two axes are independent.
mjr 29:582472d0bc57 523 //
mjr 29:582472d0bc57 524 // Note that the LedWiz protocol can only address 32 outputs, so the
mjr 29:582472d0bc57 525 // wizOn and wizVal arrays have fixed sizes of 32 elements no matter
mjr 29:582472d0bc57 526 // how many physical outputs we're using.
mjr 29:582472d0bc57 527
mjr 0:5acbbe3f4cf4 528 // on/off state for each LedWiz output
mjr 1:d913e0afb2ac 529 static uint8_t wizOn[32];
mjr 0:5acbbe3f4cf4 530
mjr 29:582472d0bc57 531 // Profile (brightness/blink) state for each LedWiz output. If the
mjr 29:582472d0bc57 532 // output was last updated through an LedWiz protocol message, it
mjr 29:582472d0bc57 533 // will have one of these values:
mjr 29:582472d0bc57 534 //
mjr 29:582472d0bc57 535 // 0-48 = fixed brightness 0% to 100%
mjr 29:582472d0bc57 536 // 129 = ramp up / ramp down
mjr 29:582472d0bc57 537 // 130 = flash on / off
mjr 29:582472d0bc57 538 // 131 = on / ramp down
mjr 29:582472d0bc57 539 // 132 = ramp up / on
mjr 29:582472d0bc57 540 //
mjr 29:582472d0bc57 541 // Special value 255: If the output was updated through the
mjr 29:582472d0bc57 542 // extended protocol, we'll set the wizVal entry to 255, which has
mjr 29:582472d0bc57 543 // no meaning in the LedWiz protocol. This tells us that the value
mjr 29:582472d0bc57 544 // in outLevel[] was set directly from the extended protocol, so it
mjr 29:582472d0bc57 545 // shouldn't be derived from wizVal[].
mjr 29:582472d0bc57 546 //
mjr 1:d913e0afb2ac 547 static uint8_t wizVal[32] = {
mjr 13:72dda449c3c0 548 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 549 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 550 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 551 48, 48, 48, 48, 48, 48, 48, 48
mjr 0:5acbbe3f4cf4 552 };
mjr 0:5acbbe3f4cf4 553
mjr 29:582472d0bc57 554 // LedWiz flash speed. This is a value from 1 to 7 giving the pulse
mjr 29:582472d0bc57 555 // rate for lights in blinking states.
mjr 29:582472d0bc57 556 static uint8_t wizSpeed = 2;
mjr 29:582472d0bc57 557
mjr 29:582472d0bc57 558 // Current LedWiz flash cycle counter.
mjr 29:582472d0bc57 559 static uint8_t wizFlashCounter = 0;
mjr 29:582472d0bc57 560
mjr 29:582472d0bc57 561 // Get the current brightness level for an LedWiz output.
mjr 1:d913e0afb2ac 562 static float wizState(int idx)
mjr 0:5acbbe3f4cf4 563 {
mjr 29:582472d0bc57 564 // if the output was last set with an extended protocol message,
mjr 29:582472d0bc57 565 // use the value set there, ignoring the output's LedWiz state
mjr 29:582472d0bc57 566 if (wizVal[idx] == 255)
mjr 29:582472d0bc57 567 return outLevel[idx];
mjr 29:582472d0bc57 568
mjr 29:582472d0bc57 569 // if it's off, show at zero intensity
mjr 29:582472d0bc57 570 if (!wizOn[idx])
mjr 29:582472d0bc57 571 return 0;
mjr 29:582472d0bc57 572
mjr 29:582472d0bc57 573 // check the state
mjr 29:582472d0bc57 574 uint8_t val = wizVal[idx];
mjr 29:582472d0bc57 575 if (val <= 48)
mjr 29:582472d0bc57 576 {
mjr 29:582472d0bc57 577 // PWM brightness/intensity level. Rescale from the LedWiz
mjr 29:582472d0bc57 578 // 0..48 integer range to our internal PwmOut 0..1 float range.
mjr 29:582472d0bc57 579 // Note that on the actual LedWiz, level 48 is actually about
mjr 29:582472d0bc57 580 // 98% on - contrary to the LedWiz documentation, level 49 is
mjr 29:582472d0bc57 581 // the true 100% level. (In the documentation, level 49 is
mjr 29:582472d0bc57 582 // simply not a valid setting.) Even so, we treat level 48 as
mjr 29:582472d0bc57 583 // 100% on to match the documentation. This won't be perfectly
mjr 29:582472d0bc57 584 // ocmpatible with the actual LedWiz, but it makes for such a
mjr 29:582472d0bc57 585 // small difference in brightness (if the output device is an
mjr 29:582472d0bc57 586 // LED, say) that no one should notice. It seems better to
mjr 29:582472d0bc57 587 // err in this direction, because while the difference in
mjr 29:582472d0bc57 588 // brightness when attached to an LED won't be noticeable, the
mjr 29:582472d0bc57 589 // difference in duty cycle when attached to something like a
mjr 29:582472d0bc57 590 // contactor *can* be noticeable - anything less than 100%
mjr 29:582472d0bc57 591 // can cause a contactor or relay to chatter. There's almost
mjr 29:582472d0bc57 592 // never a situation where you'd want values other than 0% and
mjr 29:582472d0bc57 593 // 100% for a contactor or relay, so treating level 48 as 100%
mjr 29:582472d0bc57 594 // makes us work properly with software that's expecting the
mjr 29:582472d0bc57 595 // documented LedWiz behavior and therefore uses level 48 to
mjr 29:582472d0bc57 596 // turn a contactor or relay fully on.
mjr 29:582472d0bc57 597 return val/48.0;
mjr 29:582472d0bc57 598 }
mjr 29:582472d0bc57 599 else if (val == 49)
mjr 13:72dda449c3c0 600 {
mjr 29:582472d0bc57 601 // 49 is undefined in the LedWiz documentation, but actually
mjr 29:582472d0bc57 602 // means 100% on. The documentation says that levels 1-48 are
mjr 29:582472d0bc57 603 // the full PWM range, but empirically it appears that the real
mjr 29:582472d0bc57 604 // range implemented in the firmware is 1-49. Some software on
mjr 29:582472d0bc57 605 // the PC side (notably DOF) is aware of this and uses level 49
mjr 29:582472d0bc57 606 // to mean "100% on". To ensure compatibility with existing
mjr 29:582472d0bc57 607 // PC-side software, we need to recognize level 49.
mjr 29:582472d0bc57 608 return 1.0;
mjr 29:582472d0bc57 609 }
mjr 29:582472d0bc57 610 else if (val == 129)
mjr 29:582472d0bc57 611 {
mjr 29:582472d0bc57 612 // 129 = ramp up / ramp down
mjr 29:582472d0bc57 613 if (wizFlashCounter < 128)
mjr 29:582472d0bc57 614 return wizFlashCounter/127.0;
mjr 0:5acbbe3f4cf4 615 else
mjr 29:582472d0bc57 616 return (255 - wizFlashCounter)/127.0;
mjr 29:582472d0bc57 617 }
mjr 29:582472d0bc57 618 else if (val == 130)
mjr 29:582472d0bc57 619 {
mjr 29:582472d0bc57 620 // 130 = flash on / off
mjr 29:582472d0bc57 621 return (wizFlashCounter < 128 ? 1.0 : 0.0);
mjr 29:582472d0bc57 622 }
mjr 29:582472d0bc57 623 else if (val == 131)
mjr 29:582472d0bc57 624 {
mjr 29:582472d0bc57 625 // 131 = on / ramp down
mjr 29:582472d0bc57 626 return (255 - wizFlashCounter)/255.0;
mjr 0:5acbbe3f4cf4 627 }
mjr 29:582472d0bc57 628 else if (val == 132)
mjr 29:582472d0bc57 629 {
mjr 29:582472d0bc57 630 // 132 = ramp up / on
mjr 29:582472d0bc57 631 return wizFlashCounter/255.0;
mjr 29:582472d0bc57 632 }
mjr 29:582472d0bc57 633 else
mjr 13:72dda449c3c0 634 {
mjr 29:582472d0bc57 635 // Other values are undefined in the LedWiz documentation. Hosts
mjr 29:582472d0bc57 636 // *should* never send undefined values, since whatever behavior an
mjr 29:582472d0bc57 637 // LedWiz unit exhibits in response is accidental and could change
mjr 29:582472d0bc57 638 // in a future version. We'll treat all undefined values as equivalent
mjr 29:582472d0bc57 639 // to 48 (fully on).
mjr 29:582472d0bc57 640 return 1.0;
mjr 0:5acbbe3f4cf4 641 }
mjr 0:5acbbe3f4cf4 642 }
mjr 0:5acbbe3f4cf4 643
mjr 29:582472d0bc57 644 // LedWiz flash timer pulse. This fires periodically to update
mjr 29:582472d0bc57 645 // LedWiz flashing outputs. At the slowest pulse speed set via
mjr 29:582472d0bc57 646 // the SBA command, each waveform cycle has 256 steps, so we
mjr 29:582472d0bc57 647 // choose the pulse time base so that the slowest cycle completes
mjr 29:582472d0bc57 648 // in 2 seconds. This seems to roughly match the real LedWiz
mjr 29:582472d0bc57 649 // behavior. We run the pulse timer at the same rate regardless
mjr 29:582472d0bc57 650 // of the pulse speed; at higher pulse speeds, we simply use
mjr 29:582472d0bc57 651 // larger steps through the cycle on each interrupt. Running
mjr 29:582472d0bc57 652 // every 1/127 of a second = 8ms seems to be a pretty light load.
mjr 29:582472d0bc57 653 Timeout wizPulseTimer;
mjr 29:582472d0bc57 654 #define WIZ_PULSE_TIME_BASE (1.0/127.0)
mjr 29:582472d0bc57 655 static void wizPulse()
mjr 29:582472d0bc57 656 {
mjr 29:582472d0bc57 657 // increase the counter by the speed increment, and wrap at 256
mjr 29:582472d0bc57 658 wizFlashCounter += wizSpeed;
mjr 29:582472d0bc57 659 wizFlashCounter &= 0xff;
mjr 29:582472d0bc57 660
mjr 29:582472d0bc57 661 // if we have any flashing lights, update them
mjr 29:582472d0bc57 662 int ena = false;
mjr 29:582472d0bc57 663 for (int i = 0 ; i < 32 ; ++i)
mjr 29:582472d0bc57 664 {
mjr 29:582472d0bc57 665 if (wizOn[i])
mjr 29:582472d0bc57 666 {
mjr 29:582472d0bc57 667 uint8_t s = wizVal[i];
mjr 29:582472d0bc57 668 if (s >= 129 && s <= 132)
mjr 29:582472d0bc57 669 {
mjr 29:582472d0bc57 670 lwPin[i]->set(wizState(i));
mjr 29:582472d0bc57 671 ena = true;
mjr 29:582472d0bc57 672 }
mjr 29:582472d0bc57 673 }
mjr 29:582472d0bc57 674 }
mjr 29:582472d0bc57 675
mjr 29:582472d0bc57 676 // Set up the next timer pulse only if we found anything flashing.
mjr 29:582472d0bc57 677 // To minimize overhead from this feature, we only enable the interrupt
mjr 29:582472d0bc57 678 // when we need it. This eliminates any performance penalty to other
mjr 29:582472d0bc57 679 // features when the host software doesn't care about the flashing
mjr 29:582472d0bc57 680 // modes. For example, DOF never uses these modes, so there's no
mjr 29:582472d0bc57 681 // need for them when running Visual Pinball.
mjr 29:582472d0bc57 682 if (ena)
mjr 29:582472d0bc57 683 wizPulseTimer.attach(wizPulse, WIZ_PULSE_TIME_BASE);
mjr 29:582472d0bc57 684 }
mjr 29:582472d0bc57 685
mjr 29:582472d0bc57 686 // Update the physical outputs connected to the LedWiz ports. This is
mjr 29:582472d0bc57 687 // called after any update from an LedWiz protocol message.
mjr 1:d913e0afb2ac 688 static void updateWizOuts()
mjr 1:d913e0afb2ac 689 {
mjr 29:582472d0bc57 690 // update each output
mjr 29:582472d0bc57 691 int pulse = false;
mjr 6:cc35eb643e8f 692 for (int i = 0 ; i < 32 ; ++i)
mjr 29:582472d0bc57 693 {
mjr 29:582472d0bc57 694 pulse |= (wizVal[i] >= 129 && wizVal[i] <= 132);
mjr 6:cc35eb643e8f 695 lwPin[i]->set(wizState(i));
mjr 29:582472d0bc57 696 }
mjr 29:582472d0bc57 697
mjr 29:582472d0bc57 698 // if any outputs are set to flashing mode, and the pulse timer
mjr 29:582472d0bc57 699 // isn't running, turn it on
mjr 29:582472d0bc57 700 if (pulse)
mjr 29:582472d0bc57 701 wizPulseTimer.attach(wizPulse, WIZ_PULSE_TIME_BASE);
mjr 1:d913e0afb2ac 702 }
mjr 1:d913e0afb2ac 703
mjr 11:bd9da7088e6e 704 // ---------------------------------------------------------------------------
mjr 11:bd9da7088e6e 705 //
mjr 11:bd9da7088e6e 706 // Button input
mjr 11:bd9da7088e6e 707 //
mjr 11:bd9da7088e6e 708
mjr 11:bd9da7088e6e 709 // button input map array
mjr 11:bd9da7088e6e 710 DigitalIn *buttonDigIn[32];
mjr 11:bd9da7088e6e 711
mjr 18:5e890ebd0023 712 // button state
mjr 18:5e890ebd0023 713 struct ButtonState
mjr 18:5e890ebd0023 714 {
mjr 18:5e890ebd0023 715 // current on/off state
mjr 18:5e890ebd0023 716 int pressed;
mjr 18:5e890ebd0023 717
mjr 18:5e890ebd0023 718 // Sticky time remaining for current state. When a
mjr 18:5e890ebd0023 719 // state transition occurs, we set this to a debounce
mjr 18:5e890ebd0023 720 // period. Future state transitions will be ignored
mjr 18:5e890ebd0023 721 // until the debounce time elapses.
mjr 18:5e890ebd0023 722 int t;
mjr 18:5e890ebd0023 723 } buttonState[32];
mjr 18:5e890ebd0023 724
mjr 12:669df364a565 725 // timer for button reports
mjr 12:669df364a565 726 static Timer buttonTimer;
mjr 12:669df364a565 727
mjr 11:bd9da7088e6e 728 // initialize the button inputs
mjr 11:bd9da7088e6e 729 void initButtons()
mjr 11:bd9da7088e6e 730 {
mjr 11:bd9da7088e6e 731 // create the digital inputs
mjr 11:bd9da7088e6e 732 for (int i = 0 ; i < countof(buttonDigIn) ; ++i)
mjr 11:bd9da7088e6e 733 {
mjr 11:bd9da7088e6e 734 if (i < countof(buttonMap) && buttonMap[i] != NC)
mjr 11:bd9da7088e6e 735 buttonDigIn[i] = new DigitalIn(buttonMap[i]);
mjr 11:bd9da7088e6e 736 else
mjr 11:bd9da7088e6e 737 buttonDigIn[i] = 0;
mjr 11:bd9da7088e6e 738 }
mjr 12:669df364a565 739
mjr 12:669df364a565 740 // start the button timer
mjr 12:669df364a565 741 buttonTimer.start();
mjr 11:bd9da7088e6e 742 }
mjr 11:bd9da7088e6e 743
mjr 11:bd9da7088e6e 744
mjr 18:5e890ebd0023 745 // read the button input state
mjr 18:5e890ebd0023 746 uint32_t readButtons()
mjr 11:bd9da7088e6e 747 {
mjr 11:bd9da7088e6e 748 // start with all buttons off
mjr 11:bd9da7088e6e 749 uint32_t buttons = 0;
mjr 11:bd9da7088e6e 750
mjr 18:5e890ebd0023 751 // figure the time elapsed since the last scan
mjr 18:5e890ebd0023 752 int dt = buttonTimer.read_ms();
mjr 18:5e890ebd0023 753
mjr 18:5e890ebd0023 754 // reset the timef for the next scan
mjr 18:5e890ebd0023 755 buttonTimer.reset();
mjr 18:5e890ebd0023 756
mjr 11:bd9da7088e6e 757 // scan the button list
mjr 11:bd9da7088e6e 758 uint32_t bit = 1;
mjr 18:5e890ebd0023 759 DigitalIn **di = buttonDigIn;
mjr 18:5e890ebd0023 760 ButtonState *bs = buttonState;
mjr 18:5e890ebd0023 761 for (int i = 0 ; i < countof(buttonDigIn) ; ++i, ++di, ++bs, bit <<= 1)
mjr 11:bd9da7088e6e 762 {
mjr 18:5e890ebd0023 763 // read this button
mjr 18:5e890ebd0023 764 if (*di != 0)
mjr 18:5e890ebd0023 765 {
mjr 18:5e890ebd0023 766 // deduct the elapsed time since the last update
mjr 18:5e890ebd0023 767 // from the button's remaining sticky time
mjr 18:5e890ebd0023 768 bs->t -= dt;
mjr 18:5e890ebd0023 769 if (bs->t < 0)
mjr 18:5e890ebd0023 770 bs->t = 0;
mjr 18:5e890ebd0023 771
mjr 18:5e890ebd0023 772 // If the sticky time has elapsed, note the new physical
mjr 18:5e890ebd0023 773 // state of the button. If we still have sticky time
mjr 18:5e890ebd0023 774 // remaining, ignore the physical state; the last state
mjr 18:5e890ebd0023 775 // change persists until the sticky time elapses so that
mjr 18:5e890ebd0023 776 // we smooth out any "bounce" (electrical transients that
mjr 18:5e890ebd0023 777 // occur when the switch contact is opened or closed).
mjr 18:5e890ebd0023 778 if (bs->t == 0)
mjr 18:5e890ebd0023 779 {
mjr 18:5e890ebd0023 780 // get the new physical state
mjr 18:5e890ebd0023 781 int pressed = !(*di)->read();
mjr 18:5e890ebd0023 782
mjr 18:5e890ebd0023 783 // update the button's logical state if this is a change
mjr 18:5e890ebd0023 784 if (pressed != bs->pressed)
mjr 18:5e890ebd0023 785 {
mjr 18:5e890ebd0023 786 // store the new state
mjr 18:5e890ebd0023 787 bs->pressed = pressed;
mjr 18:5e890ebd0023 788
mjr 18:5e890ebd0023 789 // start a new sticky period for debouncing this
mjr 18:5e890ebd0023 790 // state change
mjr 19:054f8af32fce 791 bs->t = 25;
mjr 18:5e890ebd0023 792 }
mjr 18:5e890ebd0023 793 }
mjr 18:5e890ebd0023 794
mjr 18:5e890ebd0023 795 // if it's pressed, OR its bit into the state
mjr 18:5e890ebd0023 796 if (bs->pressed)
mjr 18:5e890ebd0023 797 buttons |= bit;
mjr 18:5e890ebd0023 798 }
mjr 11:bd9da7088e6e 799 }
mjr 11:bd9da7088e6e 800
mjr 18:5e890ebd0023 801 // return the new button list
mjr 11:bd9da7088e6e 802 return buttons;
mjr 11:bd9da7088e6e 803 }
mjr 11:bd9da7088e6e 804
mjr 5:a70c0bce770d 805 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 806 //
mjr 5:a70c0bce770d 807 // Customization joystick subbclass
mjr 5:a70c0bce770d 808 //
mjr 5:a70c0bce770d 809
mjr 5:a70c0bce770d 810 class MyUSBJoystick: public USBJoystick
mjr 5:a70c0bce770d 811 {
mjr 5:a70c0bce770d 812 public:
mjr 5:a70c0bce770d 813 MyUSBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release)
mjr 5:a70c0bce770d 814 : USBJoystick(vendor_id, product_id, product_release, true)
mjr 5:a70c0bce770d 815 {
mjr 5:a70c0bce770d 816 suspended_ = false;
mjr 5:a70c0bce770d 817 }
mjr 5:a70c0bce770d 818
mjr 5:a70c0bce770d 819 // are we connected?
mjr 5:a70c0bce770d 820 int isConnected() { return configured(); }
mjr 5:a70c0bce770d 821
mjr 5:a70c0bce770d 822 // Are we in suspend mode?
mjr 5:a70c0bce770d 823 int isSuspended() const { return suspended_; }
mjr 5:a70c0bce770d 824
mjr 5:a70c0bce770d 825 protected:
mjr 5:a70c0bce770d 826 virtual void suspendStateChanged(unsigned int suspended)
mjr 5:a70c0bce770d 827 { suspended_ = suspended; }
mjr 5:a70c0bce770d 828
mjr 5:a70c0bce770d 829 // are we suspended?
mjr 5:a70c0bce770d 830 int suspended_;
mjr 5:a70c0bce770d 831 };
mjr 5:a70c0bce770d 832
mjr 5:a70c0bce770d 833 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 834 //
mjr 5:a70c0bce770d 835 // Accelerometer (MMA8451Q)
mjr 5:a70c0bce770d 836 //
mjr 5:a70c0bce770d 837
mjr 5:a70c0bce770d 838 // The MMA8451Q is the KL25Z's on-board 3-axis accelerometer.
mjr 5:a70c0bce770d 839 //
mjr 5:a70c0bce770d 840 // This is a custom wrapper for the library code to interface to the
mjr 6:cc35eb643e8f 841 // MMA8451Q. This class encapsulates an interrupt handler and
mjr 6:cc35eb643e8f 842 // automatic calibration.
mjr 5:a70c0bce770d 843 //
mjr 5:a70c0bce770d 844 // We install an interrupt handler on the accelerometer "data ready"
mjr 6:cc35eb643e8f 845 // interrupt to ensure that we fetch each sample immediately when it
mjr 6:cc35eb643e8f 846 // becomes available. The accelerometer data rate is fiarly high
mjr 6:cc35eb643e8f 847 // (800 Hz), so it's not practical to keep up with it by polling.
mjr 6:cc35eb643e8f 848 // Using an interrupt handler lets us respond quickly and read
mjr 6:cc35eb643e8f 849 // every sample.
mjr 5:a70c0bce770d 850 //
mjr 6:cc35eb643e8f 851 // We automatically calibrate the accelerometer so that it's not
mjr 6:cc35eb643e8f 852 // necessary to get it exactly level when installing it, and so
mjr 6:cc35eb643e8f 853 // that it's also not necessary to calibrate it manually. There's
mjr 6:cc35eb643e8f 854 // lots of experience that tells us that manual calibration is a
mjr 6:cc35eb643e8f 855 // terrible solution, mostly because cabinets tend to shift slightly
mjr 6:cc35eb643e8f 856 // during use, requiring frequent recalibration. Instead, we
mjr 6:cc35eb643e8f 857 // calibrate automatically. We continuously monitor the acceleration
mjr 6:cc35eb643e8f 858 // data, watching for periods of constant (or nearly constant) values.
mjr 6:cc35eb643e8f 859 // Any time it appears that the machine has been at rest for a while
mjr 6:cc35eb643e8f 860 // (about 5 seconds), we'll average the readings during that rest
mjr 6:cc35eb643e8f 861 // period and use the result as the level rest position. This is
mjr 6:cc35eb643e8f 862 // is ongoing, so we'll quickly find the center point again if the
mjr 6:cc35eb643e8f 863 // machine is moved during play (by an especially aggressive bout
mjr 6:cc35eb643e8f 864 // of nudging, say).
mjr 5:a70c0bce770d 865 //
mjr 5:a70c0bce770d 866
mjr 17:ab3cec0c8bf4 867 // I2C address of the accelerometer (this is a constant of the KL25Z)
mjr 17:ab3cec0c8bf4 868 const int MMA8451_I2C_ADDRESS = (0x1d<<1);
mjr 17:ab3cec0c8bf4 869
mjr 17:ab3cec0c8bf4 870 // SCL and SDA pins for the accelerometer (constant for the KL25Z)
mjr 17:ab3cec0c8bf4 871 #define MMA8451_SCL_PIN PTE25
mjr 17:ab3cec0c8bf4 872 #define MMA8451_SDA_PIN PTE24
mjr 17:ab3cec0c8bf4 873
mjr 17:ab3cec0c8bf4 874 // Digital in pin to use for the accelerometer interrupt. For the KL25Z,
mjr 17:ab3cec0c8bf4 875 // this can be either PTA14 or PTA15, since those are the pins physically
mjr 17:ab3cec0c8bf4 876 // wired on this board to the MMA8451 interrupt controller.
mjr 17:ab3cec0c8bf4 877 #define MMA8451_INT_PIN PTA15
mjr 17:ab3cec0c8bf4 878
mjr 17:ab3cec0c8bf4 879
mjr 6:cc35eb643e8f 880 // accelerometer input history item, for gathering calibration data
mjr 6:cc35eb643e8f 881 struct AccHist
mjr 5:a70c0bce770d 882 {
mjr 6:cc35eb643e8f 883 AccHist() { x = y = d = 0.0; xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 884 void set(float x, float y, AccHist *prv)
mjr 6:cc35eb643e8f 885 {
mjr 6:cc35eb643e8f 886 // save the raw position
mjr 6:cc35eb643e8f 887 this->x = x;
mjr 6:cc35eb643e8f 888 this->y = y;
mjr 6:cc35eb643e8f 889 this->d = distance(prv);
mjr 6:cc35eb643e8f 890 }
mjr 6:cc35eb643e8f 891
mjr 6:cc35eb643e8f 892 // reading for this entry
mjr 5:a70c0bce770d 893 float x, y;
mjr 5:a70c0bce770d 894
mjr 6:cc35eb643e8f 895 // distance from previous entry
mjr 6:cc35eb643e8f 896 float d;
mjr 5:a70c0bce770d 897
mjr 6:cc35eb643e8f 898 // total and count of samples averaged over this period
mjr 6:cc35eb643e8f 899 float xtot, ytot;
mjr 6:cc35eb643e8f 900 int cnt;
mjr 6:cc35eb643e8f 901
mjr 6:cc35eb643e8f 902 void clearAvg() { xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 903 void addAvg(float x, float y) { xtot += x; ytot += y; ++cnt; }
mjr 6:cc35eb643e8f 904 float xAvg() const { return xtot/cnt; }
mjr 6:cc35eb643e8f 905 float yAvg() const { return ytot/cnt; }
mjr 5:a70c0bce770d 906
mjr 6:cc35eb643e8f 907 float distance(AccHist *p)
mjr 6:cc35eb643e8f 908 { return sqrt(square(p->x - x) + square(p->y - y)); }
mjr 5:a70c0bce770d 909 };
mjr 5:a70c0bce770d 910
mjr 5:a70c0bce770d 911 // accelerometer wrapper class
mjr 3:3514575d4f86 912 class Accel
mjr 3:3514575d4f86 913 {
mjr 3:3514575d4f86 914 public:
mjr 3:3514575d4f86 915 Accel(PinName sda, PinName scl, int i2cAddr, PinName irqPin)
mjr 3:3514575d4f86 916 : mma_(sda, scl, i2cAddr), intIn_(irqPin)
mjr 3:3514575d4f86 917 {
mjr 5:a70c0bce770d 918 // remember the interrupt pin assignment
mjr 5:a70c0bce770d 919 irqPin_ = irqPin;
mjr 5:a70c0bce770d 920
mjr 5:a70c0bce770d 921 // reset and initialize
mjr 5:a70c0bce770d 922 reset();
mjr 5:a70c0bce770d 923 }
mjr 5:a70c0bce770d 924
mjr 5:a70c0bce770d 925 void reset()
mjr 5:a70c0bce770d 926 {
mjr 6:cc35eb643e8f 927 // clear the center point
mjr 6:cc35eb643e8f 928 cx_ = cy_ = 0.0;
mjr 6:cc35eb643e8f 929
mjr 6:cc35eb643e8f 930 // start the calibration timer
mjr 5:a70c0bce770d 931 tCenter_.start();
mjr 5:a70c0bce770d 932 iAccPrv_ = nAccPrv_ = 0;
mjr 6:cc35eb643e8f 933
mjr 5:a70c0bce770d 934 // reset and initialize the MMA8451Q
mjr 5:a70c0bce770d 935 mma_.init();
mjr 6:cc35eb643e8f 936
mjr 6:cc35eb643e8f 937 // set the initial integrated velocity reading to zero
mjr 6:cc35eb643e8f 938 vx_ = vy_ = 0;
mjr 3:3514575d4f86 939
mjr 6:cc35eb643e8f 940 // set up our accelerometer interrupt handling
mjr 6:cc35eb643e8f 941 intIn_.rise(this, &Accel::isr);
mjr 5:a70c0bce770d 942 mma_.setInterruptMode(irqPin_ == PTA14 ? 1 : 2);
mjr 3:3514575d4f86 943
mjr 3:3514575d4f86 944 // read the current registers to clear the data ready flag
mjr 6:cc35eb643e8f 945 mma_.getAccXYZ(ax_, ay_, az_);
mjr 3:3514575d4f86 946
mjr 3:3514575d4f86 947 // start our timers
mjr 3:3514575d4f86 948 tGet_.start();
mjr 3:3514575d4f86 949 tInt_.start();
mjr 3:3514575d4f86 950 }
mjr 3:3514575d4f86 951
mjr 9:fd65b0a94720 952 void get(int &x, int &y)
mjr 3:3514575d4f86 953 {
mjr 3:3514575d4f86 954 // disable interrupts while manipulating the shared data
mjr 3:3514575d4f86 955 __disable_irq();
mjr 3:3514575d4f86 956
mjr 3:3514575d4f86 957 // read the shared data and store locally for calculations
mjr 6:cc35eb643e8f 958 float ax = ax_, ay = ay_;
mjr 6:cc35eb643e8f 959 float vx = vx_, vy = vy_;
mjr 5:a70c0bce770d 960
mjr 6:cc35eb643e8f 961 // reset the velocity sum for the next run
mjr 6:cc35eb643e8f 962 vx_ = vy_ = 0;
mjr 3:3514575d4f86 963
mjr 3:3514575d4f86 964 // get the time since the last get() sample
mjr 3:3514575d4f86 965 float dt = tGet_.read_us()/1.0e6;
mjr 3:3514575d4f86 966 tGet_.reset();
mjr 3:3514575d4f86 967
mjr 3:3514575d4f86 968 // done manipulating the shared data
mjr 3:3514575d4f86 969 __enable_irq();
mjr 3:3514575d4f86 970
mjr 6:cc35eb643e8f 971 // adjust the readings for the integration time
mjr 6:cc35eb643e8f 972 vx /= dt;
mjr 6:cc35eb643e8f 973 vy /= dt;
mjr 6:cc35eb643e8f 974
mjr 6:cc35eb643e8f 975 // add this sample to the current calibration interval's running total
mjr 6:cc35eb643e8f 976 AccHist *p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 977 p->addAvg(ax, ay);
mjr 6:cc35eb643e8f 978
mjr 5:a70c0bce770d 979 // check for auto-centering every so often
mjr 5:a70c0bce770d 980 if (tCenter_.read_ms() > 1000)
mjr 5:a70c0bce770d 981 {
mjr 5:a70c0bce770d 982 // add the latest raw sample to the history list
mjr 6:cc35eb643e8f 983 AccHist *prv = p;
mjr 5:a70c0bce770d 984 iAccPrv_ = (iAccPrv_ + 1) % maxAccPrv;
mjr 6:cc35eb643e8f 985 p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 986 p->set(ax, ay, prv);
mjr 5:a70c0bce770d 987
mjr 5:a70c0bce770d 988 // if we have a full complement, check for stability
mjr 5:a70c0bce770d 989 if (nAccPrv_ >= maxAccPrv)
mjr 5:a70c0bce770d 990 {
mjr 5:a70c0bce770d 991 // check if we've been stable for all recent samples
mjr 6:cc35eb643e8f 992 static const float accTol = .01;
mjr 6:cc35eb643e8f 993 AccHist *p0 = accPrv_;
mjr 6:cc35eb643e8f 994 if (p0[0].d < accTol
mjr 6:cc35eb643e8f 995 && p0[1].d < accTol
mjr 6:cc35eb643e8f 996 && p0[2].d < accTol
mjr 6:cc35eb643e8f 997 && p0[3].d < accTol
mjr 6:cc35eb643e8f 998 && p0[4].d < accTol)
mjr 5:a70c0bce770d 999 {
mjr 6:cc35eb643e8f 1000 // Figure the new calibration point as the average of
mjr 6:cc35eb643e8f 1001 // the samples over the rest period
mjr 6:cc35eb643e8f 1002 cx_ = (p0[0].xAvg() + p0[1].xAvg() + p0[2].xAvg() + p0[3].xAvg() + p0[4].xAvg())/5.0;
mjr 6:cc35eb643e8f 1003 cy_ = (p0[0].yAvg() + p0[1].yAvg() + p0[2].yAvg() + p0[3].yAvg() + p0[4].yAvg())/5.0;
mjr 5:a70c0bce770d 1004 }
mjr 5:a70c0bce770d 1005 }
mjr 5:a70c0bce770d 1006 else
mjr 5:a70c0bce770d 1007 {
mjr 5:a70c0bce770d 1008 // not enough samples yet; just up the count
mjr 5:a70c0bce770d 1009 ++nAccPrv_;
mjr 5:a70c0bce770d 1010 }
mjr 6:cc35eb643e8f 1011
mjr 6:cc35eb643e8f 1012 // clear the new item's running totals
mjr 6:cc35eb643e8f 1013 p->clearAvg();
mjr 5:a70c0bce770d 1014
mjr 5:a70c0bce770d 1015 // reset the timer
mjr 5:a70c0bce770d 1016 tCenter_.reset();
mjr 5:a70c0bce770d 1017 }
mjr 5:a70c0bce770d 1018
mjr 6:cc35eb643e8f 1019 // report our integrated velocity reading in x,y
mjr 6:cc35eb643e8f 1020 x = rawToReport(vx);
mjr 6:cc35eb643e8f 1021 y = rawToReport(vy);
mjr 5:a70c0bce770d 1022
mjr 6:cc35eb643e8f 1023 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 1024 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 1025 printf("%f %f %d %d %f\r\n", vx, vy, x, y, dt);
mjr 6:cc35eb643e8f 1026 #endif
mjr 3:3514575d4f86 1027 }
mjr 29:582472d0bc57 1028
mjr 3:3514575d4f86 1029 private:
mjr 6:cc35eb643e8f 1030 // adjust a raw acceleration figure to a usb report value
mjr 6:cc35eb643e8f 1031 int rawToReport(float v)
mjr 5:a70c0bce770d 1032 {
mjr 6:cc35eb643e8f 1033 // scale to the joystick report range and round to integer
mjr 6:cc35eb643e8f 1034 int i = int(round(v*JOYMAX));
mjr 5:a70c0bce770d 1035
mjr 6:cc35eb643e8f 1036 // if it's near the center, scale it roughly as 20*(i/20)^2,
mjr 6:cc35eb643e8f 1037 // to suppress noise near the rest position
mjr 6:cc35eb643e8f 1038 static const int filter[] = {
mjr 6:cc35eb643e8f 1039 -18, -16, -14, -13, -11, -10, -8, -7, -6, -5, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0,
mjr 6:cc35eb643e8f 1040 0,
mjr 6:cc35eb643e8f 1041 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 16, 18
mjr 6:cc35eb643e8f 1042 };
mjr 6:cc35eb643e8f 1043 return (i > 20 || i < -20 ? i : filter[i+20]);
mjr 5:a70c0bce770d 1044 }
mjr 5:a70c0bce770d 1045
mjr 3:3514575d4f86 1046 // interrupt handler
mjr 3:3514575d4f86 1047 void isr()
mjr 3:3514575d4f86 1048 {
mjr 3:3514575d4f86 1049 // Read the axes. Note that we have to read all three axes
mjr 3:3514575d4f86 1050 // (even though we only really use x and y) in order to clear
mjr 3:3514575d4f86 1051 // the "data ready" status bit in the accelerometer. The
mjr 3:3514575d4f86 1052 // interrupt only occurs when the "ready" bit transitions from
mjr 3:3514575d4f86 1053 // off to on, so we have to make sure it's off.
mjr 5:a70c0bce770d 1054 float x, y, z;
mjr 5:a70c0bce770d 1055 mma_.getAccXYZ(x, y, z);
mjr 3:3514575d4f86 1056
mjr 3:3514575d4f86 1057 // calculate the time since the last interrupt
mjr 3:3514575d4f86 1058 float dt = tInt_.read_us()/1.0e6;
mjr 3:3514575d4f86 1059 tInt_.reset();
mjr 6:cc35eb643e8f 1060
mjr 6:cc35eb643e8f 1061 // integrate the time slice from the previous reading to this reading
mjr 6:cc35eb643e8f 1062 vx_ += (x + ax_ - 2*cx_)*dt/2;
mjr 6:cc35eb643e8f 1063 vy_ += (y + ay_ - 2*cy_)*dt/2;
mjr 3:3514575d4f86 1064
mjr 6:cc35eb643e8f 1065 // store the updates
mjr 6:cc35eb643e8f 1066 ax_ = x;
mjr 6:cc35eb643e8f 1067 ay_ = y;
mjr 6:cc35eb643e8f 1068 az_ = z;
mjr 3:3514575d4f86 1069 }
mjr 3:3514575d4f86 1070
mjr 3:3514575d4f86 1071 // underlying accelerometer object
mjr 3:3514575d4f86 1072 MMA8451Q mma_;
mjr 3:3514575d4f86 1073
mjr 5:a70c0bce770d 1074 // last raw acceleration readings
mjr 6:cc35eb643e8f 1075 float ax_, ay_, az_;
mjr 5:a70c0bce770d 1076
mjr 6:cc35eb643e8f 1077 // integrated velocity reading since last get()
mjr 6:cc35eb643e8f 1078 float vx_, vy_;
mjr 6:cc35eb643e8f 1079
mjr 3:3514575d4f86 1080 // timer for measuring time between get() samples
mjr 3:3514575d4f86 1081 Timer tGet_;
mjr 3:3514575d4f86 1082
mjr 3:3514575d4f86 1083 // timer for measuring time between interrupts
mjr 3:3514575d4f86 1084 Timer tInt_;
mjr 5:a70c0bce770d 1085
mjr 6:cc35eb643e8f 1086 // Calibration reference point for accelerometer. This is the
mjr 6:cc35eb643e8f 1087 // average reading on the accelerometer when in the neutral position
mjr 6:cc35eb643e8f 1088 // at rest.
mjr 6:cc35eb643e8f 1089 float cx_, cy_;
mjr 5:a70c0bce770d 1090
mjr 5:a70c0bce770d 1091 // timer for atuo-centering
mjr 5:a70c0bce770d 1092 Timer tCenter_;
mjr 6:cc35eb643e8f 1093
mjr 6:cc35eb643e8f 1094 // Auto-centering history. This is a separate history list that
mjr 6:cc35eb643e8f 1095 // records results spaced out sparesely over time, so that we can
mjr 6:cc35eb643e8f 1096 // watch for long-lasting periods of rest. When we observe nearly
mjr 6:cc35eb643e8f 1097 // no motion for an extended period (on the order of 5 seconds), we
mjr 6:cc35eb643e8f 1098 // take this to mean that the cabinet is at rest in its neutral
mjr 6:cc35eb643e8f 1099 // position, so we take this as the calibration zero point for the
mjr 6:cc35eb643e8f 1100 // accelerometer. We update this history continuously, which allows
mjr 6:cc35eb643e8f 1101 // us to continuously re-calibrate the accelerometer. This ensures
mjr 6:cc35eb643e8f 1102 // that we'll automatically adjust to any actual changes in the
mjr 6:cc35eb643e8f 1103 // cabinet's orientation (e.g., if it gets moved slightly by an
mjr 6:cc35eb643e8f 1104 // especially strong nudge) as well as any systematic drift in the
mjr 6:cc35eb643e8f 1105 // accelerometer measurement bias (e.g., from temperature changes).
mjr 5:a70c0bce770d 1106 int iAccPrv_, nAccPrv_;
mjr 5:a70c0bce770d 1107 static const int maxAccPrv = 5;
mjr 6:cc35eb643e8f 1108 AccHist accPrv_[maxAccPrv];
mjr 6:cc35eb643e8f 1109
mjr 5:a70c0bce770d 1110 // interurupt pin name
mjr 5:a70c0bce770d 1111 PinName irqPin_;
mjr 5:a70c0bce770d 1112
mjr 5:a70c0bce770d 1113 // interrupt router
mjr 5:a70c0bce770d 1114 InterruptIn intIn_;
mjr 3:3514575d4f86 1115 };
mjr 3:3514575d4f86 1116
mjr 5:a70c0bce770d 1117
mjr 5:a70c0bce770d 1118 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 1119 //
mjr 14:df700b22ca08 1120 // Clear the I2C bus for the MMA8451Q. This seems necessary some of the time
mjr 5:a70c0bce770d 1121 // for reasons that aren't clear to me. Doing a hard power cycle has the same
mjr 5:a70c0bce770d 1122 // effect, but when we do a soft reset, the hardware sometimes seems to leave
mjr 5:a70c0bce770d 1123 // the MMA's SDA line stuck low. Forcing a series of 9 clock pulses through
mjr 14:df700b22ca08 1124 // the SCL line is supposed to clear this condition. I'm not convinced this
mjr 14:df700b22ca08 1125 // actually works with the way this component is wired on the KL25Z, but it
mjr 14:df700b22ca08 1126 // seems harmless, so we'll do it on reset in case it does some good. What
mjr 14:df700b22ca08 1127 // we really seem to need is a way to power cycle the MMA8451Q if it ever
mjr 14:df700b22ca08 1128 // gets stuck, but this is simply not possible in software on the KL25Z.
mjr 14:df700b22ca08 1129 //
mjr 14:df700b22ca08 1130 // If the accelerometer does get stuck, and a software reboot doesn't reset
mjr 14:df700b22ca08 1131 // it, the only workaround is to manually power cycle the whole KL25Z by
mjr 14:df700b22ca08 1132 // unplugging both of its USB connections.
mjr 5:a70c0bce770d 1133 //
mjr 5:a70c0bce770d 1134 void clear_i2c()
mjr 5:a70c0bce770d 1135 {
mjr 5:a70c0bce770d 1136 // assume a general-purpose output pin to the I2C clock
mjr 5:a70c0bce770d 1137 DigitalOut scl(MMA8451_SCL_PIN);
mjr 5:a70c0bce770d 1138 DigitalIn sda(MMA8451_SDA_PIN);
mjr 5:a70c0bce770d 1139
mjr 5:a70c0bce770d 1140 // clock the SCL 9 times
mjr 5:a70c0bce770d 1141 for (int i = 0 ; i < 9 ; ++i)
mjr 5:a70c0bce770d 1142 {
mjr 5:a70c0bce770d 1143 scl = 1;
mjr 5:a70c0bce770d 1144 wait_us(20);
mjr 5:a70c0bce770d 1145 scl = 0;
mjr 5:a70c0bce770d 1146 wait_us(20);
mjr 5:a70c0bce770d 1147 }
mjr 5:a70c0bce770d 1148 }
mjr 14:df700b22ca08 1149
mjr 14:df700b22ca08 1150 // ---------------------------------------------------------------------------
mjr 14:df700b22ca08 1151 //
mjr 17:ab3cec0c8bf4 1152 // Include the appropriate plunger sensor definition. This will define a
mjr 17:ab3cec0c8bf4 1153 // class called PlungerSensor, with a standard interface that we use in
mjr 17:ab3cec0c8bf4 1154 // the main loop below. This is *kind of* like a virtual class interface,
mjr 17:ab3cec0c8bf4 1155 // but it actually defines the methods statically, which is a little more
mjr 17:ab3cec0c8bf4 1156 // efficient at run-time. There's no need for a true virtual interface
mjr 17:ab3cec0c8bf4 1157 // because we don't need to be able to change sensor types on the fly.
mjr 17:ab3cec0c8bf4 1158 //
mjr 17:ab3cec0c8bf4 1159
mjr 22:71422c359f2a 1160 #if defined(ENABLE_CCD_SENSOR)
mjr 17:ab3cec0c8bf4 1161 #include "ccdSensor.h"
mjr 22:71422c359f2a 1162 #elif defined(ENABLE_POT_SENSOR)
mjr 17:ab3cec0c8bf4 1163 #include "potSensor.h"
mjr 17:ab3cec0c8bf4 1164 #else
mjr 17:ab3cec0c8bf4 1165 #include "nullSensor.h"
mjr 17:ab3cec0c8bf4 1166 #endif
mjr 17:ab3cec0c8bf4 1167
mjr 17:ab3cec0c8bf4 1168
mjr 17:ab3cec0c8bf4 1169 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 1170 //
mjr 17:ab3cec0c8bf4 1171 // Non-volatile memory (NVM)
mjr 17:ab3cec0c8bf4 1172 //
mjr 17:ab3cec0c8bf4 1173
mjr 17:ab3cec0c8bf4 1174 // Structure defining our NVM storage layout. We store a small
mjr 17:ab3cec0c8bf4 1175 // amount of persistent data in flash memory to retain calibration
mjr 17:ab3cec0c8bf4 1176 // data when powered off.
mjr 17:ab3cec0c8bf4 1177 struct NVM
mjr 17:ab3cec0c8bf4 1178 {
mjr 17:ab3cec0c8bf4 1179 // checksum - we use this to determine if the flash record
mjr 17:ab3cec0c8bf4 1180 // has been properly initialized
mjr 17:ab3cec0c8bf4 1181 uint32_t checksum;
mjr 17:ab3cec0c8bf4 1182
mjr 17:ab3cec0c8bf4 1183 // signature value
mjr 17:ab3cec0c8bf4 1184 static const uint32_t SIGNATURE = 0x4D4A522A;
mjr 17:ab3cec0c8bf4 1185 static const uint16_t VERSION = 0x0003;
mjr 17:ab3cec0c8bf4 1186
mjr 17:ab3cec0c8bf4 1187 // Is the data structure valid? We test the signature and
mjr 17:ab3cec0c8bf4 1188 // checksum to determine if we've been properly stored.
mjr 17:ab3cec0c8bf4 1189 int valid() const
mjr 17:ab3cec0c8bf4 1190 {
mjr 17:ab3cec0c8bf4 1191 return (d.sig == SIGNATURE
mjr 17:ab3cec0c8bf4 1192 && d.vsn == VERSION
mjr 17:ab3cec0c8bf4 1193 && d.sz == sizeof(NVM)
mjr 17:ab3cec0c8bf4 1194 && checksum == CRC32(&d, sizeof(d)));
mjr 17:ab3cec0c8bf4 1195 }
mjr 17:ab3cec0c8bf4 1196
mjr 17:ab3cec0c8bf4 1197 // save to non-volatile memory
mjr 17:ab3cec0c8bf4 1198 void save(FreescaleIAP &iap, int addr)
mjr 17:ab3cec0c8bf4 1199 {
mjr 17:ab3cec0c8bf4 1200 // update the checksum and structure size
mjr 17:ab3cec0c8bf4 1201 checksum = CRC32(&d, sizeof(d));
mjr 17:ab3cec0c8bf4 1202 d.sz = sizeof(NVM);
mjr 17:ab3cec0c8bf4 1203
mjr 17:ab3cec0c8bf4 1204 // erase the sector
mjr 17:ab3cec0c8bf4 1205 iap.erase_sector(addr);
mjr 17:ab3cec0c8bf4 1206
mjr 17:ab3cec0c8bf4 1207 // save the data
mjr 17:ab3cec0c8bf4 1208 iap.program_flash(addr, this, sizeof(*this));
mjr 17:ab3cec0c8bf4 1209 }
mjr 17:ab3cec0c8bf4 1210
mjr 17:ab3cec0c8bf4 1211 // reset calibration data for calibration mode
mjr 17:ab3cec0c8bf4 1212 void resetPlunger()
mjr 17:ab3cec0c8bf4 1213 {
mjr 17:ab3cec0c8bf4 1214 // set extremes for the calibration data
mjr 17:ab3cec0c8bf4 1215 d.plungerMax = 0;
mjr 17:ab3cec0c8bf4 1216 d.plungerZero = npix;
mjr 17:ab3cec0c8bf4 1217 d.plungerMin = npix;
mjr 17:ab3cec0c8bf4 1218 }
mjr 17:ab3cec0c8bf4 1219
mjr 17:ab3cec0c8bf4 1220 // stored data (excluding the checksum)
mjr 17:ab3cec0c8bf4 1221 struct
mjr 17:ab3cec0c8bf4 1222 {
mjr 17:ab3cec0c8bf4 1223 // Signature, structure version, and structure size - further verification
mjr 17:ab3cec0c8bf4 1224 // that we have valid initialized data. The size is a simple proxy for a
mjr 17:ab3cec0c8bf4 1225 // structure version, as the most common type of change to the structure as
mjr 17:ab3cec0c8bf4 1226 // the software evolves will be the addition of new elements. We also
mjr 17:ab3cec0c8bf4 1227 // provide an explicit version number that we can update manually if we
mjr 17:ab3cec0c8bf4 1228 // make any changes that don't affect the structure size but would affect
mjr 17:ab3cec0c8bf4 1229 // compatibility with a saved record (e.g., swapping two existing elements).
mjr 17:ab3cec0c8bf4 1230 uint32_t sig;
mjr 17:ab3cec0c8bf4 1231 uint16_t vsn;
mjr 17:ab3cec0c8bf4 1232 int sz;
mjr 17:ab3cec0c8bf4 1233
mjr 17:ab3cec0c8bf4 1234 // has the plunger been manually calibrated?
mjr 17:ab3cec0c8bf4 1235 int plungerCal;
mjr 17:ab3cec0c8bf4 1236
mjr 17:ab3cec0c8bf4 1237 // Plunger calibration min, zero, and max. The zero point is the
mjr 17:ab3cec0c8bf4 1238 // rest position (aka park position), where it's in equilibrium between
mjr 17:ab3cec0c8bf4 1239 // the main spring and the barrel spring. It can travel a small distance
mjr 17:ab3cec0c8bf4 1240 // forward of the rest position, because the barrel spring can be
mjr 17:ab3cec0c8bf4 1241 // compressed by the user pushing on the plunger or by the momentum
mjr 17:ab3cec0c8bf4 1242 // of a release motion. The minimum is the maximum forward point where
mjr 17:ab3cec0c8bf4 1243 // the barrel spring can't be compressed any further.
mjr 17:ab3cec0c8bf4 1244 int plungerMin;
mjr 17:ab3cec0c8bf4 1245 int plungerZero;
mjr 17:ab3cec0c8bf4 1246 int plungerMax;
mjr 17:ab3cec0c8bf4 1247
mjr 17:ab3cec0c8bf4 1248 // is the plunger sensor enabled?
mjr 17:ab3cec0c8bf4 1249 int plungerEnabled;
mjr 17:ab3cec0c8bf4 1250
mjr 17:ab3cec0c8bf4 1251 // LedWiz unit number
mjr 17:ab3cec0c8bf4 1252 uint8_t ledWizUnitNo;
mjr 17:ab3cec0c8bf4 1253 } d;
mjr 17:ab3cec0c8bf4 1254 };
mjr 17:ab3cec0c8bf4 1255
mjr 17:ab3cec0c8bf4 1256
mjr 17:ab3cec0c8bf4 1257 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 1258 //
mjr 5:a70c0bce770d 1259 // Main program loop. This is invoked on startup and runs forever. Our
mjr 5:a70c0bce770d 1260 // main work is to read our devices (the accelerometer and the CCD), process
mjr 5:a70c0bce770d 1261 // the readings into nudge and plunger position data, and send the results
mjr 5:a70c0bce770d 1262 // to the host computer via the USB joystick interface. We also monitor
mjr 5:a70c0bce770d 1263 // the USB connection for incoming LedWiz commands and process those into
mjr 5:a70c0bce770d 1264 // port outputs.
mjr 5:a70c0bce770d 1265 //
mjr 0:5acbbe3f4cf4 1266 int main(void)
mjr 0:5acbbe3f4cf4 1267 {
mjr 1:d913e0afb2ac 1268 // turn off our on-board indicator LED
mjr 4:02c7cd7b2183 1269 ledR = 1;
mjr 4:02c7cd7b2183 1270 ledG = 1;
mjr 4:02c7cd7b2183 1271 ledB = 1;
mjr 1:d913e0afb2ac 1272
mjr 6:cc35eb643e8f 1273 // initialize the LedWiz ports
mjr 6:cc35eb643e8f 1274 initLwOut();
mjr 6:cc35eb643e8f 1275
mjr 11:bd9da7088e6e 1276 // initialize the button input ports
mjr 11:bd9da7088e6e 1277 initButtons();
mjr 11:bd9da7088e6e 1278
mjr 6:cc35eb643e8f 1279 // we don't need a reset yet
mjr 6:cc35eb643e8f 1280 bool needReset = false;
mjr 6:cc35eb643e8f 1281
mjr 5:a70c0bce770d 1282 // clear the I2C bus for the accelerometer
mjr 5:a70c0bce770d 1283 clear_i2c();
mjr 5:a70c0bce770d 1284
mjr 2:c174f9ee414a 1285 // set up a flash memory controller
mjr 2:c174f9ee414a 1286 FreescaleIAP iap;
mjr 2:c174f9ee414a 1287
mjr 2:c174f9ee414a 1288 // use the last sector of flash for our non-volatile memory structure
mjr 2:c174f9ee414a 1289 int flash_addr = (iap.flash_size() - SECTOR_SIZE);
mjr 2:c174f9ee414a 1290 NVM *flash = (NVM *)flash_addr;
mjr 2:c174f9ee414a 1291 NVM cfg;
mjr 2:c174f9ee414a 1292
mjr 2:c174f9ee414a 1293 // check for valid flash
mjr 6:cc35eb643e8f 1294 bool flash_valid = flash->valid();
mjr 2:c174f9ee414a 1295
mjr 2:c174f9ee414a 1296 // if the flash is valid, load it; otherwise initialize to defaults
mjr 2:c174f9ee414a 1297 if (flash_valid) {
mjr 2:c174f9ee414a 1298 memcpy(&cfg, flash, sizeof(cfg));
mjr 6:cc35eb643e8f 1299 printf("Flash restored: plunger cal=%d, min=%d, zero=%d, max=%d\r\n",
mjr 6:cc35eb643e8f 1300 cfg.d.plungerCal, cfg.d.plungerMin, cfg.d.plungerZero, cfg.d.plungerMax);
mjr 2:c174f9ee414a 1301 }
mjr 2:c174f9ee414a 1302 else {
mjr 2:c174f9ee414a 1303 printf("Factory reset\r\n");
mjr 2:c174f9ee414a 1304 cfg.d.sig = cfg.SIGNATURE;
mjr 2:c174f9ee414a 1305 cfg.d.vsn = cfg.VERSION;
mjr 6:cc35eb643e8f 1306 cfg.d.plungerCal = 0;
mjr 17:ab3cec0c8bf4 1307 cfg.d.plungerMin = 0; // assume we can go all the way forward...
mjr 17:ab3cec0c8bf4 1308 cfg.d.plungerMax = npix; // ...and all the way back
mjr 17:ab3cec0c8bf4 1309 cfg.d.plungerZero = npix/6; // the rest position is usually around 1/2" back
mjr 21:5048e16cc9ef 1310 cfg.d.ledWizUnitNo = DEFAULT_LEDWIZ_UNIT_NUMBER - 1; // unit numbering starts from 0 internally
mjr 21:5048e16cc9ef 1311 cfg.d.plungerEnabled = PLUNGER_CODE_ENABLED;
mjr 2:c174f9ee414a 1312 }
mjr 1:d913e0afb2ac 1313
mjr 6:cc35eb643e8f 1314 // Create the joystick USB client. Note that we use the LedWiz unit
mjr 6:cc35eb643e8f 1315 // number from the saved configuration.
mjr 6:cc35eb643e8f 1316 MyUSBJoystick js(
mjr 6:cc35eb643e8f 1317 USB_VENDOR_ID,
mjr 6:cc35eb643e8f 1318 USB_PRODUCT_ID | cfg.d.ledWizUnitNo,
mjr 6:cc35eb643e8f 1319 USB_VERSION_NO);
mjr 17:ab3cec0c8bf4 1320
mjr 17:ab3cec0c8bf4 1321 // last report timer - we use this to throttle reports, since VP
mjr 17:ab3cec0c8bf4 1322 // doesn't want to hear from us more than about every 10ms
mjr 17:ab3cec0c8bf4 1323 Timer reportTimer;
mjr 17:ab3cec0c8bf4 1324 reportTimer.start();
mjr 17:ab3cec0c8bf4 1325
mjr 17:ab3cec0c8bf4 1326 // initialize the calibration buttons, if present
mjr 17:ab3cec0c8bf4 1327 DigitalIn *calBtn = (CAL_BUTTON_PIN == NC ? 0 : new DigitalIn(CAL_BUTTON_PIN));
mjr 17:ab3cec0c8bf4 1328 DigitalOut *calBtnLed = (CAL_BUTTON_LED == NC ? 0 : new DigitalOut(CAL_BUTTON_LED));
mjr 6:cc35eb643e8f 1329
mjr 1:d913e0afb2ac 1330 // plunger calibration button debounce timer
mjr 1:d913e0afb2ac 1331 Timer calBtnTimer;
mjr 1:d913e0afb2ac 1332 calBtnTimer.start();
mjr 1:d913e0afb2ac 1333 int calBtnLit = false;
mjr 1:d913e0afb2ac 1334
mjr 1:d913e0afb2ac 1335 // Calibration button state:
mjr 1:d913e0afb2ac 1336 // 0 = not pushed
mjr 1:d913e0afb2ac 1337 // 1 = pushed, not yet debounced
mjr 1:d913e0afb2ac 1338 // 2 = pushed, debounced, waiting for hold time
mjr 1:d913e0afb2ac 1339 // 3 = pushed, hold time completed - in calibration mode
mjr 1:d913e0afb2ac 1340 int calBtnState = 0;
mjr 1:d913e0afb2ac 1341
mjr 1:d913e0afb2ac 1342 // set up a timer for our heartbeat indicator
mjr 1:d913e0afb2ac 1343 Timer hbTimer;
mjr 1:d913e0afb2ac 1344 hbTimer.start();
mjr 1:d913e0afb2ac 1345 int hb = 0;
mjr 5:a70c0bce770d 1346 uint16_t hbcnt = 0;
mjr 1:d913e0afb2ac 1347
mjr 1:d913e0afb2ac 1348 // set a timer for accelerometer auto-centering
mjr 1:d913e0afb2ac 1349 Timer acTimer;
mjr 1:d913e0afb2ac 1350 acTimer.start();
mjr 1:d913e0afb2ac 1351
mjr 0:5acbbe3f4cf4 1352 // create the accelerometer object
mjr 5:a70c0bce770d 1353 Accel accel(MMA8451_SCL_PIN, MMA8451_SDA_PIN, MMA8451_I2C_ADDRESS, MMA8451_INT_PIN);
mjr 0:5acbbe3f4cf4 1354
mjr 21:5048e16cc9ef 1355 #ifdef ENABLE_JOYSTICK
mjr 17:ab3cec0c8bf4 1356 // last accelerometer report, in joystick units (we report the nudge
mjr 17:ab3cec0c8bf4 1357 // acceleration via the joystick x & y axes, per the VP convention)
mjr 17:ab3cec0c8bf4 1358 int x = 0, y = 0;
mjr 17:ab3cec0c8bf4 1359
mjr 21:5048e16cc9ef 1360 // flag: send a pixel dump after the next read
mjr 21:5048e16cc9ef 1361 bool reportPix = false;
mjr 21:5048e16cc9ef 1362 #endif
mjr 21:5048e16cc9ef 1363
mjr 29:582472d0bc57 1364 #ifdef ENABLE_TLC5940
mjr 29:582472d0bc57 1365 // start the TLC5940 clock
mjr 29:582472d0bc57 1366 tlc5940.start();
mjr 29:582472d0bc57 1367 #endif
mjr 29:582472d0bc57 1368
mjr 17:ab3cec0c8bf4 1369 // create our plunger sensor object
mjr 17:ab3cec0c8bf4 1370 PlungerSensor plungerSensor;
mjr 17:ab3cec0c8bf4 1371
mjr 17:ab3cec0c8bf4 1372 // last plunger report position, in 'npix' normalized pixel units
mjr 17:ab3cec0c8bf4 1373 int pos = 0;
mjr 17:ab3cec0c8bf4 1374
mjr 17:ab3cec0c8bf4 1375 // last plunger report, in joystick units (we report the plunger as the
mjr 17:ab3cec0c8bf4 1376 // "z" axis of the joystick, per the VP convention)
mjr 17:ab3cec0c8bf4 1377 int z = 0;
mjr 17:ab3cec0c8bf4 1378
mjr 17:ab3cec0c8bf4 1379 // most recent prior plunger readings, for tracking release events(z0 is
mjr 17:ab3cec0c8bf4 1380 // reading just before the last one we reported, z1 is the one before that,
mjr 17:ab3cec0c8bf4 1381 // z2 the next before that)
mjr 17:ab3cec0c8bf4 1382 int z0 = 0, z1 = 0, z2 = 0;
mjr 17:ab3cec0c8bf4 1383
mjr 17:ab3cec0c8bf4 1384 // Simulated "bounce" position when firing. We model the bounce off of
mjr 17:ab3cec0c8bf4 1385 // the barrel spring when the plunger is released as proportional to the
mjr 17:ab3cec0c8bf4 1386 // distance it was retracted just before being released.
mjr 17:ab3cec0c8bf4 1387 int zBounce = 0;
mjr 2:c174f9ee414a 1388
mjr 17:ab3cec0c8bf4 1389 // Simulated Launch Ball button state. If a "ZB Launch Ball" port is
mjr 17:ab3cec0c8bf4 1390 // defined for our LedWiz port mapping, any time that port is turned ON,
mjr 17:ab3cec0c8bf4 1391 // we'll simulate pushing the Launch Ball button if the player pulls
mjr 17:ab3cec0c8bf4 1392 // back and releases the plunger, or simply pushes on the plunger from
mjr 17:ab3cec0c8bf4 1393 // the rest position. This allows the plunger to be used in lieu of a
mjr 17:ab3cec0c8bf4 1394 // physical Launch Ball button for tables that don't have plungers.
mjr 17:ab3cec0c8bf4 1395 //
mjr 17:ab3cec0c8bf4 1396 // States:
mjr 17:ab3cec0c8bf4 1397 // 0 = default
mjr 17:ab3cec0c8bf4 1398 // 1 = cocked (plunger has been pulled back about 1" from state 0)
mjr 17:ab3cec0c8bf4 1399 // 2 = uncocked (plunger is pulled back less than 1" from state 1)
mjr 21:5048e16cc9ef 1400 // 3 = launching, plunger is forward beyond park position
mjr 21:5048e16cc9ef 1401 // 4 = launching, plunger is behind park position
mjr 21:5048e16cc9ef 1402 // 5 = pressed and holding (plunger has been pressed forward beyond
mjr 21:5048e16cc9ef 1403 // the park position from state 0)
mjr 17:ab3cec0c8bf4 1404 int lbState = 0;
mjr 6:cc35eb643e8f 1405
mjr 17:ab3cec0c8bf4 1406 // Time since last lbState transition. Some of the states are time-
mjr 17:ab3cec0c8bf4 1407 // sensitive. In the "uncocked" state, we'll return to state 0 if
mjr 17:ab3cec0c8bf4 1408 // we remain in this state for more than a few milliseconds, since
mjr 17:ab3cec0c8bf4 1409 // it indicates that the plunger is being slowly returned to rest
mjr 17:ab3cec0c8bf4 1410 // rather than released. In the "launching" state, we need to release
mjr 17:ab3cec0c8bf4 1411 // the Launch Ball button after a moment, and we need to wait for
mjr 17:ab3cec0c8bf4 1412 // the plunger to come to rest before returning to state 0.
mjr 17:ab3cec0c8bf4 1413 Timer lbTimer;
mjr 17:ab3cec0c8bf4 1414 lbTimer.start();
mjr 17:ab3cec0c8bf4 1415
mjr 18:5e890ebd0023 1416 // Launch Ball simulated push timer. We start this when we simulate
mjr 18:5e890ebd0023 1417 // the button push, and turn off the simulated button when enough time
mjr 18:5e890ebd0023 1418 // has elapsed.
mjr 18:5e890ebd0023 1419 Timer lbBtnTimer;
mjr 18:5e890ebd0023 1420
mjr 17:ab3cec0c8bf4 1421 // Simulated button states. This is a vector of button states
mjr 17:ab3cec0c8bf4 1422 // for the simulated buttons. We combine this with the physical
mjr 17:ab3cec0c8bf4 1423 // button states on each USB joystick report, so we will report
mjr 17:ab3cec0c8bf4 1424 // a button as pressed if either the physical button is being pressed
mjr 17:ab3cec0c8bf4 1425 // or we're simulating a press on the button. This is used for the
mjr 17:ab3cec0c8bf4 1426 // simulated Launch Ball button.
mjr 17:ab3cec0c8bf4 1427 uint32_t simButtons = 0;
mjr 6:cc35eb643e8f 1428
mjr 6:cc35eb643e8f 1429 // Firing in progress: we set this when we detect the start of rapid
mjr 6:cc35eb643e8f 1430 // plunger movement from a retracted position towards the rest position.
mjr 17:ab3cec0c8bf4 1431 //
mjr 17:ab3cec0c8bf4 1432 // When we detect a firing event, we send VP a series of synthetic
mjr 17:ab3cec0c8bf4 1433 // reports simulating the idealized plunger motion. The actual physical
mjr 17:ab3cec0c8bf4 1434 // motion is much too fast to report to VP; in the time between two USB
mjr 17:ab3cec0c8bf4 1435 // reports, the plunger can shoot all the way forward, rebound off of
mjr 17:ab3cec0c8bf4 1436 // the barrel spring, bounce back part way, and bounce forward again,
mjr 17:ab3cec0c8bf4 1437 // or even do all of this more than once. This means that sampling the
mjr 17:ab3cec0c8bf4 1438 // physical motion at the USB report rate would create a misleading
mjr 17:ab3cec0c8bf4 1439 // picture of the plunger motion, since our samples would catch the
mjr 17:ab3cec0c8bf4 1440 // plunger at random points in this oscillating motion. From the
mjr 17:ab3cec0c8bf4 1441 // user's perspective, the physical action that occurred is simply that
mjr 17:ab3cec0c8bf4 1442 // the plunger was released from a particular distance, so it's this
mjr 17:ab3cec0c8bf4 1443 // high-level event that we want to convey to VP. To do this, we
mjr 17:ab3cec0c8bf4 1444 // synthesize a series of reports to convey an idealized version of
mjr 17:ab3cec0c8bf4 1445 // the release motion that's perfectly synchronized to the VP reports.
mjr 17:ab3cec0c8bf4 1446 // Essentially we pretend that our USB position samples are exactly
mjr 17:ab3cec0c8bf4 1447 // aligned in time with (1) the point of retraction just before the
mjr 17:ab3cec0c8bf4 1448 // user released the plunger, (2) the point of maximum forward motion
mjr 17:ab3cec0c8bf4 1449 // just after the user released the plunger (the point of maximum
mjr 17:ab3cec0c8bf4 1450 // compression as the plunger bounces off of the barrel spring), and
mjr 17:ab3cec0c8bf4 1451 // (3) the plunger coming to rest at the park position. This series
mjr 17:ab3cec0c8bf4 1452 // of reports is synthetic in the sense that it's not what we actually
mjr 17:ab3cec0c8bf4 1453 // see on the CCD at the times of these reports - the true plunger
mjr 17:ab3cec0c8bf4 1454 // position is oscillating at high speed during this period. But at
mjr 17:ab3cec0c8bf4 1455 // the same time it conveys a more faithful picture of the true physical
mjr 17:ab3cec0c8bf4 1456 // motion to VP, and allows VP to reproduce the true physical motion
mjr 17:ab3cec0c8bf4 1457 // more faithfully in its simulation model, by correcting for the
mjr 17:ab3cec0c8bf4 1458 // relatively low sampling rate in the communication path between the
mjr 17:ab3cec0c8bf4 1459 // real plunger and VP's model plunger.
mjr 17:ab3cec0c8bf4 1460 //
mjr 17:ab3cec0c8bf4 1461 // If 'firing' is non-zero, it's the index of our current report in
mjr 17:ab3cec0c8bf4 1462 // the synthetic firing report series.
mjr 9:fd65b0a94720 1463 int firing = 0;
mjr 2:c174f9ee414a 1464
mjr 2:c174f9ee414a 1465 // start the first CCD integration cycle
mjr 17:ab3cec0c8bf4 1466 plungerSensor.init();
mjr 9:fd65b0a94720 1467
mjr 9:fd65b0a94720 1468 // Device status. We report this on each update so that the host config
mjr 9:fd65b0a94720 1469 // tool can detect our current settings. This is a bit mask consisting
mjr 9:fd65b0a94720 1470 // of these bits:
mjr 9:fd65b0a94720 1471 // 0x01 -> plunger sensor enabled
mjr 17:ab3cec0c8bf4 1472 uint16_t statusFlags = (cfg.d.plungerEnabled ? 0x01 : 0x00);
mjr 10:976666ffa4ef 1473
mjr 1:d913e0afb2ac 1474 // we're all set up - now just loop, processing sensor reports and
mjr 1:d913e0afb2ac 1475 // host requests
mjr 0:5acbbe3f4cf4 1476 for (;;)
mjr 0:5acbbe3f4cf4 1477 {
mjr 18:5e890ebd0023 1478 // Look for an incoming report. Process a few input reports in
mjr 18:5e890ebd0023 1479 // a row, but stop after a few so that a barrage of inputs won't
mjr 20:4c43877327ab 1480 // starve our output event processing. Also, pause briefly between
mjr 20:4c43877327ab 1481 // reads; allowing reads to occur back-to-back seems to occasionally
mjr 20:4c43877327ab 1482 // stall the USB pipeline (for reasons unknown; I'd fix the underlying
mjr 20:4c43877327ab 1483 // problem if I knew what it was).
mjr 0:5acbbe3f4cf4 1484 HID_REPORT report;
mjr 20:4c43877327ab 1485 for (int rr = 0 ; rr < 4 && js.readNB(&report) ; ++rr, wait_ms(1))
mjr 0:5acbbe3f4cf4 1486 {
mjr 6:cc35eb643e8f 1487 // all Led-Wiz reports are 8 bytes exactly
mjr 6:cc35eb643e8f 1488 if (report.length == 8)
mjr 1:d913e0afb2ac 1489 {
mjr 6:cc35eb643e8f 1490 uint8_t *data = report.data;
mjr 6:cc35eb643e8f 1491 if (data[0] == 64)
mjr 0:5acbbe3f4cf4 1492 {
mjr 6:cc35eb643e8f 1493 // LWZ-SBA - first four bytes are bit-packed on/off flags
mjr 29:582472d0bc57 1494 // for the outputs; 5th byte is the pulse speed (1-7)
mjr 6:cc35eb643e8f 1495 //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n",
mjr 6:cc35eb643e8f 1496 // data[1], data[2], data[3], data[4], data[5]);
mjr 0:5acbbe3f4cf4 1497
mjr 6:cc35eb643e8f 1498 // update all on/off states
mjr 6:cc35eb643e8f 1499 for (int i = 0, bit = 1, ri = 1 ; i < 32 ; ++i, bit <<= 1)
mjr 6:cc35eb643e8f 1500 {
mjr 6:cc35eb643e8f 1501 if (bit == 0x100) {
mjr 6:cc35eb643e8f 1502 bit = 1;
mjr 6:cc35eb643e8f 1503 ++ri;
mjr 6:cc35eb643e8f 1504 }
mjr 6:cc35eb643e8f 1505 wizOn[i] = ((data[ri] & bit) != 0);
mjr 6:cc35eb643e8f 1506 }
mjr 29:582472d0bc57 1507
mjr 29:582472d0bc57 1508 // set the flash speed - enforce the value range 1-7
mjr 29:582472d0bc57 1509 wizSpeed = data[5];
mjr 29:582472d0bc57 1510 if (wizSpeed < 1)
mjr 29:582472d0bc57 1511 wizSpeed = 1;
mjr 29:582472d0bc57 1512 else if (wizSpeed > 7)
mjr 29:582472d0bc57 1513 wizSpeed = 7;
mjr 6:cc35eb643e8f 1514
mjr 6:cc35eb643e8f 1515 // update the physical outputs
mjr 1:d913e0afb2ac 1516 updateWizOuts();
mjr 6:cc35eb643e8f 1517
mjr 6:cc35eb643e8f 1518 // reset the PBA counter
mjr 6:cc35eb643e8f 1519 pbaIdx = 0;
mjr 6:cc35eb643e8f 1520 }
mjr 6:cc35eb643e8f 1521 else if (data[0] == 65)
mjr 6:cc35eb643e8f 1522 {
mjr 6:cc35eb643e8f 1523 // Private control message. This isn't an LedWiz message - it's
mjr 6:cc35eb643e8f 1524 // an extension for this device. 65 is an invalid PBA setting,
mjr 6:cc35eb643e8f 1525 // and isn't used for any other LedWiz message, so we appropriate
mjr 6:cc35eb643e8f 1526 // it for our own private use. The first byte specifies the
mjr 6:cc35eb643e8f 1527 // message type.
mjr 6:cc35eb643e8f 1528 if (data[1] == 1)
mjr 6:cc35eb643e8f 1529 {
mjr 9:fd65b0a94720 1530 // 1 = Set Configuration:
mjr 6:cc35eb643e8f 1531 // data[2] = LedWiz unit number (0x00 to 0x0f)
mjr 6:cc35eb643e8f 1532 // data[3] = feature enable bit mask:
mjr 21:5048e16cc9ef 1533 // 0x01 = enable plunger sensor
mjr 6:cc35eb643e8f 1534
mjr 6:cc35eb643e8f 1535 // we'll need a reset if the LedWiz unit number is changing
mjr 6:cc35eb643e8f 1536 uint8_t newUnitNo = data[2] & 0x0f;
mjr 6:cc35eb643e8f 1537 needReset |= (newUnitNo != cfg.d.ledWizUnitNo);
mjr 6:cc35eb643e8f 1538
mjr 6:cc35eb643e8f 1539 // set the configuration parameters from the message
mjr 6:cc35eb643e8f 1540 cfg.d.ledWizUnitNo = newUnitNo;
mjr 17:ab3cec0c8bf4 1541 cfg.d.plungerEnabled = data[3] & 0x01;
mjr 6:cc35eb643e8f 1542
mjr 9:fd65b0a94720 1543 // update the status flags
mjr 9:fd65b0a94720 1544 statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01);
mjr 9:fd65b0a94720 1545
mjr 9:fd65b0a94720 1546 // if the ccd is no longer enabled, use 0 for z reports
mjr 17:ab3cec0c8bf4 1547 if (!cfg.d.plungerEnabled)
mjr 9:fd65b0a94720 1548 z = 0;
mjr 9:fd65b0a94720 1549
mjr 6:cc35eb643e8f 1550 // save the configuration
mjr 6:cc35eb643e8f 1551 cfg.save(iap, flash_addr);
mjr 6:cc35eb643e8f 1552 }
mjr 21:5048e16cc9ef 1553 #ifdef ENABLE_JOYSTICK
mjr 9:fd65b0a94720 1554 else if (data[1] == 2)
mjr 9:fd65b0a94720 1555 {
mjr 9:fd65b0a94720 1556 // 2 = Calibrate plunger
mjr 9:fd65b0a94720 1557 // (No parameters)
mjr 9:fd65b0a94720 1558
mjr 9:fd65b0a94720 1559 // enter calibration mode
mjr 9:fd65b0a94720 1560 calBtnState = 3;
mjr 9:fd65b0a94720 1561 calBtnTimer.reset();
mjr 9:fd65b0a94720 1562 cfg.resetPlunger();
mjr 9:fd65b0a94720 1563 }
mjr 10:976666ffa4ef 1564 else if (data[1] == 3)
mjr 10:976666ffa4ef 1565 {
mjr 10:976666ffa4ef 1566 // 3 = pixel dump
mjr 10:976666ffa4ef 1567 // (No parameters)
mjr 10:976666ffa4ef 1568 reportPix = true;
mjr 10:976666ffa4ef 1569
mjr 10:976666ffa4ef 1570 // show purple until we finish sending the report
mjr 10:976666ffa4ef 1571 ledR = 0;
mjr 10:976666ffa4ef 1572 ledB = 0;
mjr 10:976666ffa4ef 1573 ledG = 1;
mjr 10:976666ffa4ef 1574 }
mjr 21:5048e16cc9ef 1575 #endif // ENABLE_JOYSTICK
mjr 6:cc35eb643e8f 1576 }
mjr 6:cc35eb643e8f 1577 else
mjr 6:cc35eb643e8f 1578 {
mjr 6:cc35eb643e8f 1579 // LWZ-PBA - full state dump; each byte is one output
mjr 6:cc35eb643e8f 1580 // in the current bank. pbaIdx keeps track of the bank;
mjr 6:cc35eb643e8f 1581 // this is incremented implicitly by each PBA message.
mjr 6:cc35eb643e8f 1582 //printf("LWZ-PBA[%d] %02x %02x %02x %02x %02x %02x %02x %02x\r\n",
mjr 6:cc35eb643e8f 1583 // pbaIdx, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
mjr 6:cc35eb643e8f 1584
mjr 6:cc35eb643e8f 1585 // update all output profile settings
mjr 6:cc35eb643e8f 1586 for (int i = 0 ; i < 8 ; ++i)
mjr 6:cc35eb643e8f 1587 wizVal[pbaIdx + i] = data[i];
mjr 6:cc35eb643e8f 1588
mjr 6:cc35eb643e8f 1589 // update the physical LED state if this is the last bank
mjr 6:cc35eb643e8f 1590 if (pbaIdx == 24)
mjr 13:72dda449c3c0 1591 {
mjr 6:cc35eb643e8f 1592 updateWizOuts();
mjr 13:72dda449c3c0 1593 pbaIdx = 0;
mjr 13:72dda449c3c0 1594 }
mjr 13:72dda449c3c0 1595 else
mjr 13:72dda449c3c0 1596 pbaIdx += 8;
mjr 6:cc35eb643e8f 1597 }
mjr 0:5acbbe3f4cf4 1598 }
mjr 0:5acbbe3f4cf4 1599 }
mjr 1:d913e0afb2ac 1600
mjr 1:d913e0afb2ac 1601 // check for plunger calibration
mjr 17:ab3cec0c8bf4 1602 if (calBtn != 0 && !calBtn->read())
mjr 0:5acbbe3f4cf4 1603 {
mjr 1:d913e0afb2ac 1604 // check the state
mjr 1:d913e0afb2ac 1605 switch (calBtnState)
mjr 0:5acbbe3f4cf4 1606 {
mjr 1:d913e0afb2ac 1607 case 0:
mjr 1:d913e0afb2ac 1608 // button not yet pushed - start debouncing
mjr 1:d913e0afb2ac 1609 calBtnTimer.reset();
mjr 1:d913e0afb2ac 1610 calBtnState = 1;
mjr 1:d913e0afb2ac 1611 break;
mjr 1:d913e0afb2ac 1612
mjr 1:d913e0afb2ac 1613 case 1:
mjr 1:d913e0afb2ac 1614 // pushed, not yet debounced - if the debounce time has
mjr 1:d913e0afb2ac 1615 // passed, start the hold period
mjr 9:fd65b0a94720 1616 if (calBtnTimer.read_ms() > 50)
mjr 1:d913e0afb2ac 1617 calBtnState = 2;
mjr 1:d913e0afb2ac 1618 break;
mjr 1:d913e0afb2ac 1619
mjr 1:d913e0afb2ac 1620 case 2:
mjr 1:d913e0afb2ac 1621 // in the hold period - if the button has been held down
mjr 1:d913e0afb2ac 1622 // for the entire hold period, move to calibration mode
mjr 9:fd65b0a94720 1623 if (calBtnTimer.read_ms() > 2050)
mjr 1:d913e0afb2ac 1624 {
mjr 1:d913e0afb2ac 1625 // enter calibration mode
mjr 1:d913e0afb2ac 1626 calBtnState = 3;
mjr 9:fd65b0a94720 1627 calBtnTimer.reset();
mjr 9:fd65b0a94720 1628 cfg.resetPlunger();
mjr 1:d913e0afb2ac 1629 }
mjr 1:d913e0afb2ac 1630 break;
mjr 2:c174f9ee414a 1631
mjr 2:c174f9ee414a 1632 case 3:
mjr 9:fd65b0a94720 1633 // Already in calibration mode - pushing the button here
mjr 9:fd65b0a94720 1634 // doesn't change the current state, but we won't leave this
mjr 9:fd65b0a94720 1635 // state as long as it's held down. So nothing changes here.
mjr 2:c174f9ee414a 1636 break;
mjr 0:5acbbe3f4cf4 1637 }
mjr 0:5acbbe3f4cf4 1638 }
mjr 1:d913e0afb2ac 1639 else
mjr 1:d913e0afb2ac 1640 {
mjr 2:c174f9ee414a 1641 // Button released. If we're in calibration mode, and
mjr 2:c174f9ee414a 1642 // the calibration time has elapsed, end the calibration
mjr 2:c174f9ee414a 1643 // and save the results to flash.
mjr 2:c174f9ee414a 1644 //
mjr 2:c174f9ee414a 1645 // Otherwise, return to the base state without saving anything.
mjr 2:c174f9ee414a 1646 // If the button is released before we make it to calibration
mjr 2:c174f9ee414a 1647 // mode, it simply cancels the attempt.
mjr 9:fd65b0a94720 1648 if (calBtnState == 3 && calBtnTimer.read_ms() > 15000)
mjr 2:c174f9ee414a 1649 {
mjr 2:c174f9ee414a 1650 // exit calibration mode
mjr 1:d913e0afb2ac 1651 calBtnState = 0;
mjr 2:c174f9ee414a 1652
mjr 6:cc35eb643e8f 1653 // save the updated configuration
mjr 6:cc35eb643e8f 1654 cfg.d.plungerCal = 1;
mjr 6:cc35eb643e8f 1655 cfg.save(iap, flash_addr);
mjr 2:c174f9ee414a 1656
mjr 2:c174f9ee414a 1657 // the flash state is now valid
mjr 2:c174f9ee414a 1658 flash_valid = true;
mjr 2:c174f9ee414a 1659 }
mjr 2:c174f9ee414a 1660 else if (calBtnState != 3)
mjr 2:c174f9ee414a 1661 {
mjr 2:c174f9ee414a 1662 // didn't make it to calibration mode - cancel the operation
mjr 1:d913e0afb2ac 1663 calBtnState = 0;
mjr 2:c174f9ee414a 1664 }
mjr 1:d913e0afb2ac 1665 }
mjr 1:d913e0afb2ac 1666
mjr 1:d913e0afb2ac 1667 // light/flash the calibration button light, if applicable
mjr 1:d913e0afb2ac 1668 int newCalBtnLit = calBtnLit;
mjr 1:d913e0afb2ac 1669 switch (calBtnState)
mjr 0:5acbbe3f4cf4 1670 {
mjr 1:d913e0afb2ac 1671 case 2:
mjr 1:d913e0afb2ac 1672 // in the hold period - flash the light
mjr 9:fd65b0a94720 1673 newCalBtnLit = ((calBtnTimer.read_ms()/250) & 1);
mjr 1:d913e0afb2ac 1674 break;
mjr 1:d913e0afb2ac 1675
mjr 1:d913e0afb2ac 1676 case 3:
mjr 1:d913e0afb2ac 1677 // calibration mode - show steady on
mjr 1:d913e0afb2ac 1678 newCalBtnLit = true;
mjr 1:d913e0afb2ac 1679 break;
mjr 1:d913e0afb2ac 1680
mjr 1:d913e0afb2ac 1681 default:
mjr 1:d913e0afb2ac 1682 // not calibrating/holding - show steady off
mjr 1:d913e0afb2ac 1683 newCalBtnLit = false;
mjr 1:d913e0afb2ac 1684 break;
mjr 1:d913e0afb2ac 1685 }
mjr 3:3514575d4f86 1686
mjr 3:3514575d4f86 1687 // light or flash the external calibration button LED, and
mjr 3:3514575d4f86 1688 // do the same with the on-board blue LED
mjr 1:d913e0afb2ac 1689 if (calBtnLit != newCalBtnLit)
mjr 1:d913e0afb2ac 1690 {
mjr 1:d913e0afb2ac 1691 calBtnLit = newCalBtnLit;
mjr 2:c174f9ee414a 1692 if (calBtnLit) {
mjr 17:ab3cec0c8bf4 1693 if (calBtnLed != 0)
mjr 17:ab3cec0c8bf4 1694 calBtnLed->write(1);
mjr 4:02c7cd7b2183 1695 ledR = 1;
mjr 4:02c7cd7b2183 1696 ledG = 1;
mjr 9:fd65b0a94720 1697 ledB = 0;
mjr 2:c174f9ee414a 1698 }
mjr 2:c174f9ee414a 1699 else {
mjr 17:ab3cec0c8bf4 1700 if (calBtnLed != 0)
mjr 17:ab3cec0c8bf4 1701 calBtnLed->write(0);
mjr 4:02c7cd7b2183 1702 ledR = 1;
mjr 4:02c7cd7b2183 1703 ledG = 1;
mjr 9:fd65b0a94720 1704 ledB = 1;
mjr 2:c174f9ee414a 1705 }
mjr 1:d913e0afb2ac 1706 }
mjr 1:d913e0afb2ac 1707
mjr 17:ab3cec0c8bf4 1708 // If the plunger is enabled, and we're not already in a firing event,
mjr 17:ab3cec0c8bf4 1709 // and the last plunger reading had the plunger pulled back at least
mjr 17:ab3cec0c8bf4 1710 // a bit, watch for plunger release events until it's time for our next
mjr 17:ab3cec0c8bf4 1711 // USB report.
mjr 17:ab3cec0c8bf4 1712 if (!firing && cfg.d.plungerEnabled && z >= JOYMAX/6)
mjr 17:ab3cec0c8bf4 1713 {
mjr 17:ab3cec0c8bf4 1714 // monitor the plunger until it's time for our next report
mjr 17:ab3cec0c8bf4 1715 while (reportTimer.read_ms() < 15)
mjr 17:ab3cec0c8bf4 1716 {
mjr 17:ab3cec0c8bf4 1717 // do a fast low-res scan; if it's at or past the zero point,
mjr 17:ab3cec0c8bf4 1718 // start a firing event
mjr 17:ab3cec0c8bf4 1719 if (plungerSensor.lowResScan() <= cfg.d.plungerZero)
mjr 17:ab3cec0c8bf4 1720 firing = 1;
mjr 17:ab3cec0c8bf4 1721 }
mjr 17:ab3cec0c8bf4 1722 }
mjr 17:ab3cec0c8bf4 1723
mjr 6:cc35eb643e8f 1724 // read the plunger sensor, if it's enabled
mjr 17:ab3cec0c8bf4 1725 if (cfg.d.plungerEnabled)
mjr 6:cc35eb643e8f 1726 {
mjr 6:cc35eb643e8f 1727 // start with the previous reading, in case we don't have a
mjr 6:cc35eb643e8f 1728 // clear result on this frame
mjr 6:cc35eb643e8f 1729 int znew = z;
mjr 17:ab3cec0c8bf4 1730 if (plungerSensor.highResScan(pos))
mjr 6:cc35eb643e8f 1731 {
mjr 17:ab3cec0c8bf4 1732 // We got a new reading. If we're in calibration mode, use it
mjr 17:ab3cec0c8bf4 1733 // to figure the new calibration, otherwise adjust the new reading
mjr 17:ab3cec0c8bf4 1734 // for the established calibration.
mjr 17:ab3cec0c8bf4 1735 if (calBtnState == 3)
mjr 6:cc35eb643e8f 1736 {
mjr 17:ab3cec0c8bf4 1737 // Calibration mode. If this reading is outside of the current
mjr 17:ab3cec0c8bf4 1738 // calibration bounds, expand the bounds.
mjr 17:ab3cec0c8bf4 1739 if (pos < cfg.d.plungerMin)
mjr 17:ab3cec0c8bf4 1740 cfg.d.plungerMin = pos;
mjr 17:ab3cec0c8bf4 1741 if (pos < cfg.d.plungerZero)
mjr 17:ab3cec0c8bf4 1742 cfg.d.plungerZero = pos;
mjr 17:ab3cec0c8bf4 1743 if (pos > cfg.d.plungerMax)
mjr 17:ab3cec0c8bf4 1744 cfg.d.plungerMax = pos;
mjr 6:cc35eb643e8f 1745
mjr 17:ab3cec0c8bf4 1746 // normalize to the full physical range while calibrating
mjr 17:ab3cec0c8bf4 1747 znew = int(round(float(pos)/npix * JOYMAX));
mjr 17:ab3cec0c8bf4 1748 }
mjr 17:ab3cec0c8bf4 1749 else
mjr 17:ab3cec0c8bf4 1750 {
mjr 17:ab3cec0c8bf4 1751 // Not in calibration mode, so normalize the new reading to the
mjr 17:ab3cec0c8bf4 1752 // established calibration range.
mjr 17:ab3cec0c8bf4 1753 //
mjr 17:ab3cec0c8bf4 1754 // Note that negative values are allowed. Zero represents the
mjr 17:ab3cec0c8bf4 1755 // "park" position, where the plunger sits when at rest. A mechanical
mjr 23:14f8c5004cd0 1756 // plunger has a small amount of travel in the "push" direction,
mjr 17:ab3cec0c8bf4 1757 // since the barrel spring can be compressed slightly. Negative
mjr 17:ab3cec0c8bf4 1758 // values represent travel in the push direction.
mjr 17:ab3cec0c8bf4 1759 if (pos > cfg.d.plungerMax)
mjr 17:ab3cec0c8bf4 1760 pos = cfg.d.plungerMax;
mjr 17:ab3cec0c8bf4 1761 znew = int(round(float(pos - cfg.d.plungerZero)
mjr 17:ab3cec0c8bf4 1762 / (cfg.d.plungerMax - cfg.d.plungerZero + 1) * JOYMAX));
mjr 6:cc35eb643e8f 1763 }
mjr 6:cc35eb643e8f 1764 }
mjr 7:100a25f8bf56 1765
mjr 17:ab3cec0c8bf4 1766 // If we're not already in a firing event, check to see if the
mjr 17:ab3cec0c8bf4 1767 // new position is forward of the last report. If it is, a firing
mjr 17:ab3cec0c8bf4 1768 // event might have started during the high-res scan. This might
mjr 17:ab3cec0c8bf4 1769 // seem unlikely given that the scan only takes about 5ms, but that
mjr 17:ab3cec0c8bf4 1770 // 5ms represents about 25-30% of our total time between reports,
mjr 17:ab3cec0c8bf4 1771 // there's about a 1 in 4 chance that a release starts during a
mjr 17:ab3cec0c8bf4 1772 // scan.
mjr 17:ab3cec0c8bf4 1773 if (!firing && z0 > 0 && znew < z0)
mjr 17:ab3cec0c8bf4 1774 {
mjr 17:ab3cec0c8bf4 1775 // The plunger has moved forward since the previous report.
mjr 17:ab3cec0c8bf4 1776 // Watch it for a few more ms to see if we can get a stable
mjr 17:ab3cec0c8bf4 1777 // new position.
mjr 23:14f8c5004cd0 1778 int pos0 = plungerSensor.lowResScan();
mjr 23:14f8c5004cd0 1779 int pos1 = pos0;
mjr 17:ab3cec0c8bf4 1780 Timer tw;
mjr 17:ab3cec0c8bf4 1781 tw.start();
mjr 17:ab3cec0c8bf4 1782 while (tw.read_ms() < 6)
mjr 17:ab3cec0c8bf4 1783 {
mjr 23:14f8c5004cd0 1784 // read the new position
mjr 23:14f8c5004cd0 1785 int pos2 = plungerSensor.lowResScan();
mjr 23:14f8c5004cd0 1786
mjr 23:14f8c5004cd0 1787 // If it's stable over consecutive readings, stop looping.
mjr 23:14f8c5004cd0 1788 // (Count it as stable if the position is within about 1/8".
mjr 23:14f8c5004cd0 1789 // pos1 and pos2 are reported in pixels, so they range from
mjr 23:14f8c5004cd0 1790 // 0 to npix. The overall travel of a standard plunger is
mjr 23:14f8c5004cd0 1791 // about 3.2", so we have (npix/3.2) pixels per inch, hence
mjr 23:14f8c5004cd0 1792 // 1/8" is (npix/3.2)*(1/8) pixels.)
mjr 23:14f8c5004cd0 1793 if (abs(pos2 - pos1) < int(npix/(3.2*8)))
mjr 23:14f8c5004cd0 1794 break;
mjr 23:14f8c5004cd0 1795
mjr 23:14f8c5004cd0 1796 // If we've crossed the rest position, and we've moved by
mjr 23:14f8c5004cd0 1797 // a minimum distance from where we starting this loop, begin
mjr 23:14f8c5004cd0 1798 // a firing event. (We require a minimum distance to prevent
mjr 23:14f8c5004cd0 1799 // spurious firing from random analog noise in the readings
mjr 23:14f8c5004cd0 1800 // when the plunger is actually just sitting still at the
mjr 23:14f8c5004cd0 1801 // rest position. If it's at rest, it's normal to see small
mjr 23:14f8c5004cd0 1802 // random fluctuations in the analog reading +/- 1% or so
mjr 23:14f8c5004cd0 1803 // from the 0 point, especially with a sensor like a
mjr 23:14f8c5004cd0 1804 // potentionemeter that reports the position as a single
mjr 23:14f8c5004cd0 1805 // analog voltage.) Note that we compare the latest reading
mjr 23:14f8c5004cd0 1806 // to the first reading of the loop - we don't require the
mjr 23:14f8c5004cd0 1807 // threshold motion over consecutive readings, but any time
mjr 23:14f8c5004cd0 1808 // over the stability wait loop.
mjr 23:14f8c5004cd0 1809 if (pos1 < cfg.d.plungerZero
mjr 23:14f8c5004cd0 1810 && abs(pos2 - pos0) > int(npix/(3.2*8)))
mjr 17:ab3cec0c8bf4 1811 {
mjr 17:ab3cec0c8bf4 1812 firing = 1;
mjr 17:ab3cec0c8bf4 1813 break;
mjr 17:ab3cec0c8bf4 1814 }
mjr 23:14f8c5004cd0 1815
mjr 17:ab3cec0c8bf4 1816 // the new reading is now the prior reading
mjr 17:ab3cec0c8bf4 1817 pos1 = pos2;
mjr 17:ab3cec0c8bf4 1818 }
mjr 17:ab3cec0c8bf4 1819 }
mjr 17:ab3cec0c8bf4 1820
mjr 17:ab3cec0c8bf4 1821 // Check for a simulated Launch Ball button press, if enabled
mjr 18:5e890ebd0023 1822 if (ZBLaunchBallPort != 0)
mjr 17:ab3cec0c8bf4 1823 {
mjr 18:5e890ebd0023 1824 const int cockThreshold = JOYMAX/3;
mjr 18:5e890ebd0023 1825 const int pushThreshold = int(-JOYMAX/3 * LaunchBallPushDistance);
mjr 17:ab3cec0c8bf4 1826 int newState = lbState;
mjr 17:ab3cec0c8bf4 1827 switch (lbState)
mjr 17:ab3cec0c8bf4 1828 {
mjr 17:ab3cec0c8bf4 1829 case 0:
mjr 17:ab3cec0c8bf4 1830 // Base state. If the plunger is pulled back by an inch
mjr 17:ab3cec0c8bf4 1831 // or more, go to "cocked" state. If the plunger is pushed
mjr 21:5048e16cc9ef 1832 // forward by 1/4" or more, go to "pressed" state.
mjr 18:5e890ebd0023 1833 if (znew >= cockThreshold)
mjr 17:ab3cec0c8bf4 1834 newState = 1;
mjr 18:5e890ebd0023 1835 else if (znew <= pushThreshold)
mjr 21:5048e16cc9ef 1836 newState = 5;
mjr 17:ab3cec0c8bf4 1837 break;
mjr 17:ab3cec0c8bf4 1838
mjr 17:ab3cec0c8bf4 1839 case 1:
mjr 17:ab3cec0c8bf4 1840 // Cocked state. If a firing event is now in progress,
mjr 17:ab3cec0c8bf4 1841 // go to "launch" state. Otherwise, if the plunger is less
mjr 17:ab3cec0c8bf4 1842 // than 1" retracted, go to "uncocked" state - the player
mjr 17:ab3cec0c8bf4 1843 // might be slowly returning the plunger to rest so as not
mjr 17:ab3cec0c8bf4 1844 // to trigger a launch.
mjr 17:ab3cec0c8bf4 1845 if (firing || znew <= 0)
mjr 17:ab3cec0c8bf4 1846 newState = 3;
mjr 18:5e890ebd0023 1847 else if (znew < cockThreshold)
mjr 17:ab3cec0c8bf4 1848 newState = 2;
mjr 17:ab3cec0c8bf4 1849 break;
mjr 17:ab3cec0c8bf4 1850
mjr 17:ab3cec0c8bf4 1851 case 2:
mjr 17:ab3cec0c8bf4 1852 // Uncocked state. If the plunger is more than an inch
mjr 17:ab3cec0c8bf4 1853 // retracted, return to cocked state. If we've been in
mjr 17:ab3cec0c8bf4 1854 // the uncocked state for more than half a second, return
mjr 18:5e890ebd0023 1855 // to the base state. This allows the user to return the
mjr 18:5e890ebd0023 1856 // plunger to rest without triggering a launch, by moving
mjr 18:5e890ebd0023 1857 // it at manual speed to the rest position rather than
mjr 18:5e890ebd0023 1858 // releasing it.
mjr 18:5e890ebd0023 1859 if (znew >= cockThreshold)
mjr 17:ab3cec0c8bf4 1860 newState = 1;
mjr 17:ab3cec0c8bf4 1861 else if (lbTimer.read_ms() > 500)
mjr 17:ab3cec0c8bf4 1862 newState = 0;
mjr 17:ab3cec0c8bf4 1863 break;
mjr 17:ab3cec0c8bf4 1864
mjr 17:ab3cec0c8bf4 1865 case 3:
mjr 17:ab3cec0c8bf4 1866 // Launch state. If the plunger is no longer pushed
mjr 17:ab3cec0c8bf4 1867 // forward, switch to launch rest state.
mjr 18:5e890ebd0023 1868 if (znew >= 0)
mjr 17:ab3cec0c8bf4 1869 newState = 4;
mjr 17:ab3cec0c8bf4 1870 break;
mjr 17:ab3cec0c8bf4 1871
mjr 17:ab3cec0c8bf4 1872 case 4:
mjr 17:ab3cec0c8bf4 1873 // Launch rest state. If the plunger is pushed forward
mjr 17:ab3cec0c8bf4 1874 // again, switch back to launch state. If not, and we've
mjr 17:ab3cec0c8bf4 1875 // been in this state for at least 200ms, return to the
mjr 17:ab3cec0c8bf4 1876 // default state.
mjr 18:5e890ebd0023 1877 if (znew <= pushThreshold)
mjr 17:ab3cec0c8bf4 1878 newState = 3;
mjr 17:ab3cec0c8bf4 1879 else if (lbTimer.read_ms() > 200)
mjr 17:ab3cec0c8bf4 1880 newState = 0;
mjr 17:ab3cec0c8bf4 1881 break;
mjr 21:5048e16cc9ef 1882
mjr 21:5048e16cc9ef 1883 case 5:
mjr 21:5048e16cc9ef 1884 // Press-and-Hold state. If the plunger is no longer pushed
mjr 21:5048e16cc9ef 1885 // forward, AND it's been at least 50ms since we generated
mjr 21:5048e16cc9ef 1886 // the simulated Launch Ball button press, return to the base
mjr 21:5048e16cc9ef 1887 // state. The minimum time is to ensure that VP has a chance
mjr 21:5048e16cc9ef 1888 // to see the button press and to avoid transient key bounce
mjr 21:5048e16cc9ef 1889 // effects when the plunger position is right on the threshold.
mjr 21:5048e16cc9ef 1890 if (znew > pushThreshold && lbTimer.read_ms() > 50)
mjr 21:5048e16cc9ef 1891 newState = 0;
mjr 21:5048e16cc9ef 1892 break;
mjr 17:ab3cec0c8bf4 1893 }
mjr 17:ab3cec0c8bf4 1894
mjr 17:ab3cec0c8bf4 1895 // change states if desired
mjr 18:5e890ebd0023 1896 const uint32_t lbButtonBit = (1 << (LaunchBallButton - 1));
mjr 17:ab3cec0c8bf4 1897 if (newState != lbState)
mjr 17:ab3cec0c8bf4 1898 {
mjr 21:5048e16cc9ef 1899 // If we're entering Launch state OR we're entering the
mjr 21:5048e16cc9ef 1900 // Press-and-Hold state, AND the ZB Launch Ball LedWiz signal
mjr 21:5048e16cc9ef 1901 // is turned on, simulate a Launch Ball button press.
mjr 21:5048e16cc9ef 1902 if (((newState == 3 && lbState != 4) || newState == 5)
mjr 21:5048e16cc9ef 1903 && wizOn[ZBLaunchBallPort-1])
mjr 18:5e890ebd0023 1904 {
mjr 18:5e890ebd0023 1905 lbBtnTimer.reset();
mjr 18:5e890ebd0023 1906 lbBtnTimer.start();
mjr 18:5e890ebd0023 1907 simButtons |= lbButtonBit;
mjr 18:5e890ebd0023 1908 }
mjr 21:5048e16cc9ef 1909
mjr 17:ab3cec0c8bf4 1910 // if we're switching to state 0, release the button
mjr 17:ab3cec0c8bf4 1911 if (newState == 0)
mjr 17:ab3cec0c8bf4 1912 simButtons &= ~(1 << (LaunchBallButton - 1));
mjr 17:ab3cec0c8bf4 1913
mjr 17:ab3cec0c8bf4 1914 // switch to the new state
mjr 17:ab3cec0c8bf4 1915 lbState = newState;
mjr 17:ab3cec0c8bf4 1916
mjr 17:ab3cec0c8bf4 1917 // start timing in the new state
mjr 17:ab3cec0c8bf4 1918 lbTimer.reset();
mjr 17:ab3cec0c8bf4 1919 }
mjr 21:5048e16cc9ef 1920
mjr 21:5048e16cc9ef 1921 // If the Launch Ball button press is in effect, but the
mjr 21:5048e16cc9ef 1922 // ZB Launch Ball LedWiz signal is no longer turned on, turn
mjr 21:5048e16cc9ef 1923 // off the button.
mjr 21:5048e16cc9ef 1924 //
mjr 21:5048e16cc9ef 1925 // If we're in one of the Launch states (state #3 or #4),
mjr 21:5048e16cc9ef 1926 // and the button has been on for long enough, turn it off.
mjr 21:5048e16cc9ef 1927 // The Launch mode is triggered by a pull-and-release gesture.
mjr 21:5048e16cc9ef 1928 // From the user's perspective, this is just a single gesture
mjr 21:5048e16cc9ef 1929 // that should trigger just one momentary press on the Launch
mjr 21:5048e16cc9ef 1930 // Ball button. Physically, though, the plunger usually
mjr 21:5048e16cc9ef 1931 // bounces back and forth for 500ms or so before coming to
mjr 21:5048e16cc9ef 1932 // rest after this gesture. That's what the whole state
mjr 21:5048e16cc9ef 1933 // #3-#4 business is all about - we stay in this pair of
mjr 21:5048e16cc9ef 1934 // states until the plunger comes to rest. As long as we're
mjr 21:5048e16cc9ef 1935 // in these states, we won't send duplicate button presses.
mjr 21:5048e16cc9ef 1936 // But we also don't want the one button press to continue
mjr 21:5048e16cc9ef 1937 // the whole time, so we'll time it out now.
mjr 21:5048e16cc9ef 1938 //
mjr 21:5048e16cc9ef 1939 // (This could be written as one big 'if' condition, but
mjr 21:5048e16cc9ef 1940 // I'm breaking it out verbosely like this to make it easier
mjr 21:5048e16cc9ef 1941 // for human readers such as myself to comprehend the logic.)
mjr 21:5048e16cc9ef 1942 if ((simButtons & lbButtonBit) != 0)
mjr 18:5e890ebd0023 1943 {
mjr 21:5048e16cc9ef 1944 int turnOff = false;
mjr 21:5048e16cc9ef 1945
mjr 21:5048e16cc9ef 1946 // turn it off if the ZB Launch Ball signal is off
mjr 21:5048e16cc9ef 1947 if (!wizOn[ZBLaunchBallPort-1])
mjr 21:5048e16cc9ef 1948 turnOff = true;
mjr 21:5048e16cc9ef 1949
mjr 21:5048e16cc9ef 1950 // also turn it off if we're in state 3 or 4 ("Launch"),
mjr 21:5048e16cc9ef 1951 // and the button has been on long enough
mjr 21:5048e16cc9ef 1952 if ((lbState == 3 || lbState == 4) && lbBtnTimer.read_ms() > 250)
mjr 21:5048e16cc9ef 1953 turnOff = true;
mjr 21:5048e16cc9ef 1954
mjr 21:5048e16cc9ef 1955 // if we decided to turn off the button, do so
mjr 21:5048e16cc9ef 1956 if (turnOff)
mjr 21:5048e16cc9ef 1957 {
mjr 21:5048e16cc9ef 1958 lbBtnTimer.stop();
mjr 21:5048e16cc9ef 1959 simButtons &= ~lbButtonBit;
mjr 21:5048e16cc9ef 1960 }
mjr 18:5e890ebd0023 1961 }
mjr 17:ab3cec0c8bf4 1962 }
mjr 17:ab3cec0c8bf4 1963
mjr 17:ab3cec0c8bf4 1964 // If a firing event is in progress, generate synthetic reports to
mjr 17:ab3cec0c8bf4 1965 // describe an idealized version of the plunger motion to VP rather
mjr 17:ab3cec0c8bf4 1966 // than reporting the actual physical plunger position.
mjr 6:cc35eb643e8f 1967 //
mjr 17:ab3cec0c8bf4 1968 // We use the synthetic reports during a release event because the
mjr 17:ab3cec0c8bf4 1969 // physical plunger motion when released is too fast for VP to track.
mjr 17:ab3cec0c8bf4 1970 // VP only syncs its internal physics model with the outside world
mjr 17:ab3cec0c8bf4 1971 // about every 10ms. In that amount of time, the plunger moves
mjr 17:ab3cec0c8bf4 1972 // fast enough when released that it can shoot all the way forward,
mjr 17:ab3cec0c8bf4 1973 // bounce off of the barrel spring, and rebound part of the way
mjr 17:ab3cec0c8bf4 1974 // back. The result is the classic analog-to-digital problem of
mjr 17:ab3cec0c8bf4 1975 // sample aliasing. If we happen to time our sample during the
mjr 17:ab3cec0c8bf4 1976 // release motion so that we catch the plunger at the peak of a
mjr 17:ab3cec0c8bf4 1977 // bounce, the digital signal incorrectly looks like the plunger
mjr 17:ab3cec0c8bf4 1978 // is moving slowly forward - VP thinks we went from fully
mjr 17:ab3cec0c8bf4 1979 // retracted to half retracted in the sample interval, whereas
mjr 17:ab3cec0c8bf4 1980 // we actually traveled all the way forward and half way back,
mjr 17:ab3cec0c8bf4 1981 // so the speed VP infers is about 1/3 of the actual speed.
mjr 9:fd65b0a94720 1982 //
mjr 17:ab3cec0c8bf4 1983 // To correct this, we take advantage of our ability to sample
mjr 17:ab3cec0c8bf4 1984 // the CCD image several times in the course of a VP report. If
mjr 17:ab3cec0c8bf4 1985 // we catch the plunger near the origin after we've seen it
mjr 17:ab3cec0c8bf4 1986 // retracted, we go into Release Event mode. During this mode,
mjr 17:ab3cec0c8bf4 1987 // we stop reporting the true physical plunger position, and
mjr 17:ab3cec0c8bf4 1988 // instead report an idealized pattern: we report the plunger
mjr 17:ab3cec0c8bf4 1989 // immediately shooting forward to a position in front of the
mjr 17:ab3cec0c8bf4 1990 // park position that's in proportion to how far back the plunger
mjr 17:ab3cec0c8bf4 1991 // was just before the release, and we then report it stationary
mjr 17:ab3cec0c8bf4 1992 // at the park position. We continue to report the stationary
mjr 17:ab3cec0c8bf4 1993 // park position until the actual physical plunger motion has
mjr 17:ab3cec0c8bf4 1994 // stabilized on a new position. We then exit Release Event
mjr 17:ab3cec0c8bf4 1995 // mode and return to reporting the true physical position.
mjr 17:ab3cec0c8bf4 1996 if (firing)
mjr 6:cc35eb643e8f 1997 {
mjr 17:ab3cec0c8bf4 1998 // Firing in progress. Keep reporting the park position
mjr 17:ab3cec0c8bf4 1999 // until the physical plunger position comes to rest.
mjr 17:ab3cec0c8bf4 2000 const int restTol = JOYMAX/24;
mjr 17:ab3cec0c8bf4 2001 if (firing == 1)
mjr 6:cc35eb643e8f 2002 {
mjr 17:ab3cec0c8bf4 2003 // For the first couple of frames, show the plunger shooting
mjr 17:ab3cec0c8bf4 2004 // forward past the zero point, to simulate the momentum carrying
mjr 17:ab3cec0c8bf4 2005 // it forward to bounce off of the barrel spring. Show the
mjr 17:ab3cec0c8bf4 2006 // bounce as proportional to the distance it was retracted
mjr 17:ab3cec0c8bf4 2007 // in the prior report.
mjr 17:ab3cec0c8bf4 2008 z = zBounce = -z0/6;
mjr 17:ab3cec0c8bf4 2009 ++firing;
mjr 6:cc35eb643e8f 2010 }
mjr 17:ab3cec0c8bf4 2011 else if (firing == 2)
mjr 9:fd65b0a94720 2012 {
mjr 17:ab3cec0c8bf4 2013 // second frame - keep the bounce a little longer
mjr 17:ab3cec0c8bf4 2014 z = zBounce;
mjr 17:ab3cec0c8bf4 2015 ++firing;
mjr 17:ab3cec0c8bf4 2016 }
mjr 17:ab3cec0c8bf4 2017 else if (firing > 4
mjr 17:ab3cec0c8bf4 2018 && abs(znew - z0) < restTol
mjr 17:ab3cec0c8bf4 2019 && abs(znew - z1) < restTol
mjr 17:ab3cec0c8bf4 2020 && abs(znew - z2) < restTol)
mjr 17:ab3cec0c8bf4 2021 {
mjr 17:ab3cec0c8bf4 2022 // The physical plunger has come to rest. Exit firing
mjr 17:ab3cec0c8bf4 2023 // mode and resume reporting the actual position.
mjr 17:ab3cec0c8bf4 2024 firing = false;
mjr 17:ab3cec0c8bf4 2025 z = znew;
mjr 9:fd65b0a94720 2026 }
mjr 9:fd65b0a94720 2027 else
mjr 9:fd65b0a94720 2028 {
mjr 17:ab3cec0c8bf4 2029 // until the physical plunger comes to rest, simply
mjr 17:ab3cec0c8bf4 2030 // report the park position
mjr 9:fd65b0a94720 2031 z = 0;
mjr 17:ab3cec0c8bf4 2032 ++firing;
mjr 9:fd65b0a94720 2033 }
mjr 6:cc35eb643e8f 2034 }
mjr 6:cc35eb643e8f 2035 else
mjr 6:cc35eb643e8f 2036 {
mjr 17:ab3cec0c8bf4 2037 // not in firing mode - report the true physical position
mjr 17:ab3cec0c8bf4 2038 z = znew;
mjr 6:cc35eb643e8f 2039 }
mjr 17:ab3cec0c8bf4 2040
mjr 17:ab3cec0c8bf4 2041 // shift the new reading into the recent history buffer
mjr 6:cc35eb643e8f 2042 z2 = z1;
mjr 6:cc35eb643e8f 2043 z1 = z0;
mjr 6:cc35eb643e8f 2044 z0 = znew;
mjr 2:c174f9ee414a 2045 }
mjr 6:cc35eb643e8f 2046
mjr 11:bd9da7088e6e 2047 // update the buttons
mjr 18:5e890ebd0023 2048 uint32_t buttons = readButtons();
mjr 17:ab3cec0c8bf4 2049
mjr 21:5048e16cc9ef 2050 #ifdef ENABLE_JOYSTICK
mjr 17:ab3cec0c8bf4 2051 // If it's been long enough since our last USB status report,
mjr 17:ab3cec0c8bf4 2052 // send the new report. We throttle the report rate because
mjr 17:ab3cec0c8bf4 2053 // it can overwhelm the PC side if we report too frequently.
mjr 17:ab3cec0c8bf4 2054 // VP only wants to sync with the real world in 10ms intervals,
mjr 17:ab3cec0c8bf4 2055 // so reporting more frequently only creates i/o overhead
mjr 17:ab3cec0c8bf4 2056 // without doing anything to improve the simulation.
mjr 17:ab3cec0c8bf4 2057 if (reportTimer.read_ms() > 15)
mjr 17:ab3cec0c8bf4 2058 {
mjr 17:ab3cec0c8bf4 2059 // read the accelerometer
mjr 17:ab3cec0c8bf4 2060 int xa, ya;
mjr 17:ab3cec0c8bf4 2061 accel.get(xa, ya);
mjr 17:ab3cec0c8bf4 2062
mjr 17:ab3cec0c8bf4 2063 // confine the results to our joystick axis range
mjr 17:ab3cec0c8bf4 2064 if (xa < -JOYMAX) xa = -JOYMAX;
mjr 17:ab3cec0c8bf4 2065 if (xa > JOYMAX) xa = JOYMAX;
mjr 17:ab3cec0c8bf4 2066 if (ya < -JOYMAX) ya = -JOYMAX;
mjr 17:ab3cec0c8bf4 2067 if (ya > JOYMAX) ya = JOYMAX;
mjr 17:ab3cec0c8bf4 2068
mjr 17:ab3cec0c8bf4 2069 // store the updated accelerometer coordinates
mjr 17:ab3cec0c8bf4 2070 x = xa;
mjr 17:ab3cec0c8bf4 2071 y = ya;
mjr 17:ab3cec0c8bf4 2072
mjr 21:5048e16cc9ef 2073 // Report the current plunger position UNLESS the ZB Launch Ball
mjr 21:5048e16cc9ef 2074 // signal is on, in which case just report a constant 0 value.
mjr 21:5048e16cc9ef 2075 // ZB Launch Ball turns off the plunger position because it
mjr 21:5048e16cc9ef 2076 // tells us that the table has a Launch Ball button instead of
mjr 21:5048e16cc9ef 2077 // a traditional plunger.
mjr 21:5048e16cc9ef 2078 int zrep = (ZBLaunchBallPort != 0 && wizOn[ZBLaunchBallPort-1] ? 0 : z);
mjr 21:5048e16cc9ef 2079
mjr 25:e22b88bd783a 2080 // Send the status report. Note that we have to map the X and Y
mjr 25:e22b88bd783a 2081 // axes from the accelerometer to match the Windows joystick axes.
mjr 25:e22b88bd783a 2082 // The mapping is determined according to the mounting direction
mjr 25:e22b88bd783a 2083 // set in config.h via the ORIENTATION_xxx macros.
mjr 25:e22b88bd783a 2084 js.update(JOY_X(x,y), JOY_Y(x,y), zrep, buttons | simButtons, statusFlags);
mjr 17:ab3cec0c8bf4 2085
mjr 17:ab3cec0c8bf4 2086 // we've just started a new report interval, so reset the timer
mjr 17:ab3cec0c8bf4 2087 reportTimer.reset();
mjr 17:ab3cec0c8bf4 2088 }
mjr 21:5048e16cc9ef 2089
mjr 10:976666ffa4ef 2090 // If we're in pixel dump mode, report all pixel exposure values
mjr 10:976666ffa4ef 2091 if (reportPix)
mjr 10:976666ffa4ef 2092 {
mjr 17:ab3cec0c8bf4 2093 // send the report
mjr 17:ab3cec0c8bf4 2094 plungerSensor.sendExposureReport(js);
mjr 17:ab3cec0c8bf4 2095
mjr 10:976666ffa4ef 2096 // we have satisfied this request
mjr 10:976666ffa4ef 2097 reportPix = false;
mjr 10:976666ffa4ef 2098 }
mjr 10:976666ffa4ef 2099
mjr 21:5048e16cc9ef 2100 #else // ENABLE_JOYSTICK
mjr 21:5048e16cc9ef 2101 // We're a secondary controller, with no joystick reporting. Send
mjr 21:5048e16cc9ef 2102 // a generic status report to the host periodically for the sake of
mjr 21:5048e16cc9ef 2103 // the Windows config tool.
mjr 21:5048e16cc9ef 2104 if (reportTimer.read_ms() > 200)
mjr 21:5048e16cc9ef 2105 {
mjr 21:5048e16cc9ef 2106 js.updateStatus(0);
mjr 21:5048e16cc9ef 2107 }
mjr 21:5048e16cc9ef 2108
mjr 21:5048e16cc9ef 2109 #endif // ENABLE_JOYSTICK
mjr 21:5048e16cc9ef 2110
mjr 6:cc35eb643e8f 2111 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 2112 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 2113 printf("%d,%d\r\n", x, y);
mjr 6:cc35eb643e8f 2114 #endif
mjr 6:cc35eb643e8f 2115
mjr 6:cc35eb643e8f 2116 // provide a visual status indication on the on-board LED
mjr 5:a70c0bce770d 2117 if (calBtnState < 2 && hbTimer.read_ms() > 1000)
mjr 1:d913e0afb2ac 2118 {
mjr 5:a70c0bce770d 2119 if (js.isSuspended() || !js.isConnected())
mjr 2:c174f9ee414a 2120 {
mjr 5:a70c0bce770d 2121 // suspended - turn off the LED
mjr 4:02c7cd7b2183 2122 ledR = 1;
mjr 4:02c7cd7b2183 2123 ledG = 1;
mjr 4:02c7cd7b2183 2124 ledB = 1;
mjr 5:a70c0bce770d 2125
mjr 5:a70c0bce770d 2126 // show a status flash every so often
mjr 5:a70c0bce770d 2127 if (hbcnt % 3 == 0)
mjr 5:a70c0bce770d 2128 {
mjr 6:cc35eb643e8f 2129 // disconnected = red/red flash; suspended = red
mjr 5:a70c0bce770d 2130 for (int n = js.isConnected() ? 1 : 2 ; n > 0 ; --n)
mjr 5:a70c0bce770d 2131 {
mjr 5:a70c0bce770d 2132 ledR = 0;
mjr 5:a70c0bce770d 2133 wait(0.05);
mjr 5:a70c0bce770d 2134 ledR = 1;
mjr 5:a70c0bce770d 2135 wait(0.25);
mjr 5:a70c0bce770d 2136 }
mjr 5:a70c0bce770d 2137 }
mjr 2:c174f9ee414a 2138 }
mjr 6:cc35eb643e8f 2139 else if (needReset)
mjr 2:c174f9ee414a 2140 {
mjr 6:cc35eb643e8f 2141 // connected, need to reset due to changes in config parameters -
mjr 6:cc35eb643e8f 2142 // flash red/green
mjr 6:cc35eb643e8f 2143 hb = !hb;
mjr 6:cc35eb643e8f 2144 ledR = (hb ? 0 : 1);
mjr 6:cc35eb643e8f 2145 ledG = (hb ? 1 : 0);
mjr 6:cc35eb643e8f 2146 ledB = 0;
mjr 6:cc35eb643e8f 2147 }
mjr 17:ab3cec0c8bf4 2148 else if (cfg.d.plungerEnabled && !cfg.d.plungerCal)
mjr 6:cc35eb643e8f 2149 {
mjr 6:cc35eb643e8f 2150 // connected, plunger calibration needed - flash yellow/green
mjr 6:cc35eb643e8f 2151 hb = !hb;
mjr 6:cc35eb643e8f 2152 ledR = (hb ? 0 : 1);
mjr 6:cc35eb643e8f 2153 ledG = 0;
mjr 6:cc35eb643e8f 2154 ledB = 1;
mjr 6:cc35eb643e8f 2155 }
mjr 6:cc35eb643e8f 2156 else
mjr 6:cc35eb643e8f 2157 {
mjr 6:cc35eb643e8f 2158 // connected - flash blue/green
mjr 2:c174f9ee414a 2159 hb = !hb;
mjr 4:02c7cd7b2183 2160 ledR = 1;
mjr 4:02c7cd7b2183 2161 ledG = (hb ? 0 : 1);
mjr 4:02c7cd7b2183 2162 ledB = (hb ? 1 : 0);
mjr 2:c174f9ee414a 2163 }
mjr 1:d913e0afb2ac 2164
mjr 1:d913e0afb2ac 2165 // reset the heartbeat timer
mjr 1:d913e0afb2ac 2166 hbTimer.reset();
mjr 5:a70c0bce770d 2167 ++hbcnt;
mjr 1:d913e0afb2ac 2168 }
mjr 1:d913e0afb2ac 2169 }
mjr 0:5acbbe3f4cf4 2170 }