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:
Wed Aug 06 23:08:07 2014 +0000
Revision:
6:cc35eb643e8f
Parent:
5:a70c0bce770d
Child:
7:100a25f8bf56
Various testing setups for plunger firing - debouncing, fixed returns, etc

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 5:a70c0bce770d 22 // "Pinscape" is the name of my custom-built virtual pinball cabinet. I wrote this
mjr 5:a70c0bce770d 23 // software to perform a number of tasks that I needed for my cabinet. It runs on a
mjr 5:a70c0bce770d 24 // Freescale KL25Z microcontroller, which is a small and inexpensive device that
mjr 5:a70c0bce770d 25 // attaches to the host PC via USB and can interface with numerous types of external
mjr 5:a70c0bce770d 26 // hardware.
mjr 5:a70c0bce770d 27 //
mjr 5:a70c0bce770d 28 // I designed the software and hardware in this project especially for Pinscape, but
mjr 5:a70c0bce770d 29 // it uses standard interfaces in Windows and Visual Pinball, so it should be
mjr 5:a70c0bce770d 30 // readily usable in anyone else's VP-based cabinet. I've tried to document the
mjr 5:a70c0bce770d 31 // hardware in enough detail for anyone else to duplicate the entire project, and
mjr 5:a70c0bce770d 32 // the full software is open source.
mjr 5:a70c0bce770d 33 //
mjr 6:cc35eb643e8f 34 // The device appears to the host computer as a USB joystick. This works with the
mjr 6:cc35eb643e8f 35 // standard Windows joystick device drivers, so there's no need to install any
mjr 6:cc35eb643e8f 36 // software on the PC - Windows should recognize it as a joystick when you plug
mjr 6:cc35eb643e8f 37 // it in and shouldn't ask you to install anything. If you bring up the control
mjr 6:cc35eb643e8f 38 // panel for USB Game Controllers, this device will appear as "Pinscape Controller".
mjr 6:cc35eb643e8f 39 // *Don't* do any calibration with the Windows control panel or third-part
mjr 6:cc35eb643e8f 40 // calibration tools. The device calibrates itself automatically for the
mjr 6:cc35eb643e8f 41 // accelerometer data, and has its own special calibration procedure for the
mjr 6:cc35eb643e8f 42 // plunger (see below).
mjr 6:cc35eb643e8f 43 //
mjr 5:a70c0bce770d 44 // The controller provides the following functions. It should be possible to use
mjr 5:a70c0bce770d 45 // any subet of the features without using all of them. External hardware for any
mjr 5:a70c0bce770d 46 // particular function can simply be omitted if that feature isn't needed.
mjr 5:a70c0bce770d 47 //
mjr 5:a70c0bce770d 48 // - Nudge sensing via the KL25Z's on-board accelerometer. Nudge accelerations are
mjr 5:a70c0bce770d 49 // processed into a physics model of a rolling ball, and changes to the ball's
mjr 5:a70c0bce770d 50 // motion are sent to the host computer via the joystick interface. This is designed
mjr 5:a70c0bce770d 51 // especially to work with Visuall Pinball's nudge handling to produce realistic
mjr 5:a70c0bce770d 52 // on-screen results in VP. By doing some physics modeling right on the device,
mjr 5:a70c0bce770d 53 // rather than sending raw accelerometer data to VP, we can produce better results
mjr 5:a70c0bce770d 54 // using our awareness of the real physical parameters of a pinball cabinet.
mjr 5:a70c0bce770d 55 // VP's nudge handling has to be more generic, so it can't make the same sorts
mjr 5:a70c0bce770d 56 // of assumptions that we can about the dynamics of a real cabinet.
mjr 5:a70c0bce770d 57 //
mjr 5:a70c0bce770d 58 // The nudge data reports are compatible with the built-in Windows USB joystick
mjr 5:a70c0bce770d 59 // drivers and with VP's own joystick input scheme, so the nudge sensing is almost
mjr 5:a70c0bce770d 60 // plug-and-play. There are no Windiows drivers to install, and the only VP work
mjr 5:a70c0bce770d 61 // needed is to customize a few global preference settings.
mjr 5:a70c0bce770d 62 //
mjr 5:a70c0bce770d 63 // - Plunger position sensing via an attached TAOS TSL 1410R CCD linear array sensor.
mjr 5:a70c0bce770d 64 // The sensor must be wired to a particular set of I/O ports on the KL25Z, and must
mjr 5:a70c0bce770d 65 // be positioned adjacent to the plunger with proper lighting. The physical and
mjr 5:a70c0bce770d 66 // electronic installation details are desribed in the project documentation. We read
mjr 5:a70c0bce770d 67 // the CCD to determine how far back the plunger is pulled, and report this to Visual
mjr 5:a70c0bce770d 68 // Pinball via the joystick interface. As with the nudge data, this is all nearly
mjr 5:a70c0bce770d 69 // plug-and-play, in that it works with the default Windows USB drivers and works
mjr 5:a70c0bce770d 70 // with the existing VP handling for analog plunger input. A few VP settings are
mjr 5:a70c0bce770d 71 // needed to tell VP to allow the plunger.
mjr 5:a70c0bce770d 72 //
mjr 6:cc35eb643e8f 73 // For best results, the plunger sensor should be calibrated. The calibration
mjr 6:cc35eb643e8f 74 // is stored in non-volatile memory on board the KL25Z, so it's only necessary
mjr 6:cc35eb643e8f 75 // to do the calibration once, when you first install everything. (You might
mjr 6:cc35eb643e8f 76 // also want to re-calibrate if you physically remove and reinstall the CCD
mjr 6:cc35eb643e8f 77 // sensor or the mechanical plunger, since their alignment might change slightly
mjr 6:cc35eb643e8f 78 // when you put everything back together.) To calibrate, you have to attach a
mjr 6:cc35eb643e8f 79 // momentary switch (e.g., a push-button switch) between one of the KL25Z ground
mjr 6:cc35eb643e8f 80 // pins (e.g., jumper J9 pin 12) and PTE29 (J10 pin 9). Press and hold the
mjr 6:cc35eb643e8f 81 // button for about two seconds - the LED on the KL25Z wlil flash blue while
mjr 6:cc35eb643e8f 82 // you hold the button, and will turn solid blue when you've held it down long
mjr 6:cc35eb643e8f 83 // enough to enter calibration mode. This mode will last about 15 seconds.
mjr 6:cc35eb643e8f 84 // Simply pull the plunger all the way back, hold it for a few moments, and
mjr 6:cc35eb643e8f 85 // gradually return it to the starting position. *Don't* release it - we want
mjr 6:cc35eb643e8f 86 // to measure the maximum retracted position and the rest position, but NOT
mjr 6:cc35eb643e8f 87 // the maximum forward position when the outer barrel spring is compressed.
mjr 6:cc35eb643e8f 88 // After about 15 seconds, the device will save the new calibration settings
mjr 6:cc35eb643e8f 89 // to its flash memory, and the LED will return to the regular "heartbeat"
mjr 6:cc35eb643e8f 90 // flashes. If this is the first time you calibrated, you should observe the
mjr 6:cc35eb643e8f 91 // color of the flashes change from yellow/green to blue/green to indicate
mjr 6:cc35eb643e8f 92 // that the plunger has been calibrated.
mjr 6:cc35eb643e8f 93 //
mjr 6:cc35eb643e8f 94 // Note that while Visual Pinball itself has good native support for analog
mjr 6:cc35eb643e8f 95 // plungers, most of the VP tables in circulation don't implement the necessary
mjr 6:cc35eb643e8f 96 // scripting features to make this work properly. Therefore, you'll have to do
mjr 6:cc35eb643e8f 97 // a little scripting work for each table you download to add the required code
mjr 6:cc35eb643e8f 98 // to that individual table. The work has to be customized for each table, so
mjr 6:cc35eb643e8f 99 // I haven't been able to automate this process, but I have tried to reduce it
mjr 6:cc35eb643e8f 100 // to a relatively simple recipe that I've documented separately.
mjr 5:a70c0bce770d 101 //
mjr 5:a70c0bce770d 102 // - In addition to the CCD sensor, a button should be attached (also described in
mjr 5:a70c0bce770d 103 // the project documentation) to activate calibration mode for the plunger. When
mjr 5:a70c0bce770d 104 // calibration mode is activated, the software reads the plunger position for about
mjr 5:a70c0bce770d 105 // 10 seconds when to note the limits of travel, and uses these limits to ensure
mjr 5:a70c0bce770d 106 // accurate reports to VP that properly report the actual position of the physical
mjr 5:a70c0bce770d 107 // plunger. The calibration is stored in non-volatile memory on the KL25Z, so it's
mjr 5:a70c0bce770d 108 // only necessary to calibrate once - the calibration will survive power cycling
mjr 5:a70c0bce770d 109 // and reboots of the PC. It's only necessary to recalibrate if the CCD sensor or
mjr 5:a70c0bce770d 110 // the plunger are removed and reinstalled, since the relative alignment of the
mjr 5:a70c0bce770d 111 // parts could cahnge slightly when reinstalling.
mjr 5:a70c0bce770d 112 //
mjr 5:a70c0bce770d 113 // - LedWiz emulation. The KL25Z can appear to the PC as an LedWiz device, and will
mjr 5:a70c0bce770d 114 // accept and process LedWiz commands from the host. The software can turn digital
mjr 5:a70c0bce770d 115 // output ports on and off, and can set varying PWM intensitiy levels on a subset
mjr 5:a70c0bce770d 116 // of ports. (The KL25Z can only provide 6 PWM ports. Intensity level settings on
mjr 5:a70c0bce770d 117 // other ports is ignored, so non-PWM ports can only be used for simple on/off
mjr 5:a70c0bce770d 118 // devices such as contactors and solenoids.) The KL25Z can only supply 4mA on its
mjr 5:a70c0bce770d 119 // output ports, so external hardware is required to take advantage of the LedWiz
mjr 5:a70c0bce770d 120 // emulation. Many different hardware designs are possible, but there's a simple
mjr 5:a70c0bce770d 121 // reference design in the documentation that uses a Darlington array IC to
mjr 5:a70c0bce770d 122 // increase the output from each port to 500mA (the same level as the LedWiz),
mjr 5:a70c0bce770d 123 // plus an extended design that adds an optocoupler and MOSFET to provide very
mjr 5:a70c0bce770d 124 // high power handling, up to about 45A or 150W, with voltages up to 100V.
mjr 5:a70c0bce770d 125 // That will handle just about any DC device directly (wtihout relays or other
mjr 5:a70c0bce770d 126 // amplifiers), and switches fast enough to support PWM devices.
mjr 5:a70c0bce770d 127 //
mjr 5:a70c0bce770d 128 // The device can report any desired LedWiz unit number to the host, which makes
mjr 5:a70c0bce770d 129 // it possible to use the LedWiz emulation on a machine that also has one or more
mjr 5:a70c0bce770d 130 // actual LedWiz devices intalled. The LedWiz design allows for up to 16 units
mjr 5:a70c0bce770d 131 // to be installed in one machine - each one is invidually addressable by its
mjr 5:a70c0bce770d 132 // distinct unit number.
mjr 5:a70c0bce770d 133 //
mjr 5:a70c0bce770d 134 // The LedWiz emulation features are of course optional. There's no need to
mjr 5:a70c0bce770d 135 // build any of the external port hardware (or attach anything to the output
mjr 5:a70c0bce770d 136 // ports at all) if the LedWiz features aren't needed. Most people won't have
mjr 5:a70c0bce770d 137 // any use for the LedWiz features. I built them mostly as a learning exercise,
mjr 5:a70c0bce770d 138 // but with a slight practical need for a handful of extra ports (I'm using the
mjr 5:a70c0bce770d 139 // cutting-edge 10-contactor setup, so my real LedWiz is full!).
mjr 6:cc35eb643e8f 140 //
mjr 6:cc35eb643e8f 141 // The on-board LED on the KL25Z flashes to indicate the current device status:
mjr 6:cc35eb643e8f 142 //
mjr 6:cc35eb643e8f 143 // two short red flashes = the device is powered but hasn't successfully
mjr 6:cc35eb643e8f 144 // connected to the host via USB (either it's not physically connected
mjr 6:cc35eb643e8f 145 // to the USB port, or there was a problem with the software handshake
mjr 6:cc35eb643e8f 146 // with the USB device driver on the computer)
mjr 6:cc35eb643e8f 147 //
mjr 6:cc35eb643e8f 148 // short red flash = the host computer is in sleep/suspend mode
mjr 6:cc35eb643e8f 149 //
mjr 6:cc35eb643e8f 150 // long red/green = the LedWiz unti number has been changed, so a reset
mjr 6:cc35eb643e8f 151 // is needed. You can simply unplug the device and plug it back in,
mjr 6:cc35eb643e8f 152 // or presss and hold the reset button on the device for a few seconds.
mjr 6:cc35eb643e8f 153 //
mjr 6:cc35eb643e8f 154 // long yellow/green = everything's working, but the plunger hasn't
mjr 6:cc35eb643e8f 155 // been calibrated; follow the calibration procedure described above.
mjr 6:cc35eb643e8f 156 // This flash mode won't appear if the CCD has been disabled. Note
mjr 6:cc35eb643e8f 157 // that the device can't tell whether a CCD is physically attached,
mjr 6:cc35eb643e8f 158 // so you should use the config command to disable the CCD software
mjr 6:cc35eb643e8f 159 // features if you won't be attaching a CCD.
mjr 6:cc35eb643e8f 160 //
mjr 6:cc35eb643e8f 161 // alternating blue/green = everything's working
mjr 6:cc35eb643e8f 162 //
mjr 6:cc35eb643e8f 163 // Software configuration: you can change option settings by sending special
mjr 6:cc35eb643e8f 164 // USB commands from the PC. I've provided a Windows program for this purpose;
mjr 6:cc35eb643e8f 165 // refer to the documentation for details. For reference, here's the format
mjr 6:cc35eb643e8f 166 // of the USB command for option changes:
mjr 6:cc35eb643e8f 167 //
mjr 6:cc35eb643e8f 168 // length of report = 8 bytes
mjr 6:cc35eb643e8f 169 // byte 0 = 65 (0x41)
mjr 6:cc35eb643e8f 170 // byte 1 = 1 (0x01)
mjr 6:cc35eb643e8f 171 // byte 2 = new LedWiz unit number, 0x01 to 0x0f
mjr 6:cc35eb643e8f 172 // byte 3 = feature enable bit mask:
mjr 6:cc35eb643e8f 173 // 0x01 = enable CCD (default = on)
mjr 5:a70c0bce770d 174
mjr 6:cc35eb643e8f 175
mjr 0:5acbbe3f4cf4 176 #include "mbed.h"
mjr 6:cc35eb643e8f 177 #include "math.h"
mjr 0:5acbbe3f4cf4 178 #include "USBJoystick.h"
mjr 0:5acbbe3f4cf4 179 #include "MMA8451Q.h"
mjr 1:d913e0afb2ac 180 #include "tsl1410r.h"
mjr 1:d913e0afb2ac 181 #include "FreescaleIAP.h"
mjr 2:c174f9ee414a 182 #include "crc32.h"
mjr 2:c174f9ee414a 183
mjr 5:a70c0bce770d 184
mjr 5:a70c0bce770d 185 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 186 //
mjr 5:a70c0bce770d 187 // Configuration details
mjr 5:a70c0bce770d 188 //
mjr 2:c174f9ee414a 189
mjr 5:a70c0bce770d 190 // Our USB device vendor ID, product ID, and version.
mjr 5:a70c0bce770d 191 // We use the vendor ID for the LedWiz, so that the PC-side software can
mjr 5:a70c0bce770d 192 // identify us as capable of performing LedWiz commands. The LedWiz uses
mjr 5:a70c0bce770d 193 // a product ID value from 0xF0 to 0xFF; the last four bits identify the
mjr 5:a70c0bce770d 194 // unit number (e.g., product ID 0xF7 means unit #7). This allows multiple
mjr 5:a70c0bce770d 195 // LedWiz units to be installed in a single PC; the software on the PC side
mjr 5:a70c0bce770d 196 // uses the unit number to route commands to the devices attached to each
mjr 5:a70c0bce770d 197 // unit. On the real LedWiz, the unit number must be set in the firmware
mjr 5:a70c0bce770d 198 // at the factory; it's not configurable by the end user. Most LedWiz's
mjr 5:a70c0bce770d 199 // ship with the unit number set to 0, but the vendor will set different
mjr 5:a70c0bce770d 200 // unit numbers if requested at the time of purchase. So if you have a
mjr 5:a70c0bce770d 201 // single LedWiz already installed in your cabinet, and you didn't ask for
mjr 5:a70c0bce770d 202 // a non-default unit number, your existing LedWiz will be unit 0.
mjr 5:a70c0bce770d 203 //
mjr 5:a70c0bce770d 204 // We use unit #7 by default. There doesn't seem to be a requirement that
mjr 5:a70c0bce770d 205 // unit numbers be contiguous (DirectOutput Framework and other software
mjr 5:a70c0bce770d 206 // seem happy to have units 0 and 7 installed, without 1-6 existing).
mjr 5:a70c0bce770d 207 // Marking this unit as #7 should work for almost everybody out of the box;
mjr 5:a70c0bce770d 208 // the most common case seems to be to have a single LedWiz installed, and
mjr 5:a70c0bce770d 209 // it's probably extremely rare to more than two.
mjr 6:cc35eb643e8f 210 //
mjr 6:cc35eb643e8f 211 // Note that the USB_PRODUCT_ID value set here omits the unit number. We
mjr 6:cc35eb643e8f 212 // take the unit number from the saved configuration. We provide a
mjr 6:cc35eb643e8f 213 // configuration command that can be sent via the USB connection to change
mjr 6:cc35eb643e8f 214 // the unit number, so that users can select the unit number without having
mjr 6:cc35eb643e8f 215 // to install a different version of the software. We'll combine the base
mjr 6:cc35eb643e8f 216 // product ID here with the unit number to get the actual product ID that
mjr 6:cc35eb643e8f 217 // we send to the USB controller.
mjr 5:a70c0bce770d 218 const uint16_t USB_VENDOR_ID = 0xFAFA;
mjr 6:cc35eb643e8f 219 const uint16_t USB_PRODUCT_ID = 0x00F0;
mjr 6:cc35eb643e8f 220 const uint16_t USB_VERSION_NO = 0x0006;
mjr 6:cc35eb643e8f 221 const uint8_t DEFAULT_LEDWIZ_UNIT_NUMBER = 0x07;
mjr 0:5acbbe3f4cf4 222
mjr 4:02c7cd7b2183 223 // On-board RGB LED elements - we use these for diagnostic displays.
mjr 4:02c7cd7b2183 224 DigitalOut ledR(LED1), ledG(LED2), ledB(LED3);
mjr 0:5acbbe3f4cf4 225
mjr 1:d913e0afb2ac 226 // calibration button - switch input and LED output
mjr 1:d913e0afb2ac 227 DigitalIn calBtn(PTE29);
mjr 1:d913e0afb2ac 228 DigitalOut calBtnLed(PTE23);
mjr 0:5acbbe3f4cf4 229
mjr 6:cc35eb643e8f 230 // LED-Wiz emulation output pin assignments. The LED-Wiz protocol
mjr 6:cc35eb643e8f 231 // can support up to 32 outputs. The KL25Z can physically provide
mjr 6:cc35eb643e8f 232 // about 48 (in addition to the ports we're already using for the
mjr 6:cc35eb643e8f 233 // CCD sensor and the calibration button), but to stay compatible
mjr 6:cc35eb643e8f 234 // with the LED-Wiz protocol we'll stop at 32.
mjr 6:cc35eb643e8f 235 //
mjr 6:cc35eb643e8f 236 // The LED-Wiz protocol allows setting individual intensity levels
mjr 6:cc35eb643e8f 237 // on all outputs, with 48 levels of intensity. This can be used
mjr 6:cc35eb643e8f 238 // to control lamp brightness and motor speeds, among other things.
mjr 6:cc35eb643e8f 239 // Unfortunately, the KL25Z only has 10 PWM channels, so while we
mjr 6:cc35eb643e8f 240 // can support the full complement of 32 outputs, we can only provide
mjr 6:cc35eb643e8f 241 // PWM dimming/speed control on 10 of them. The remaining outputs
mjr 6:cc35eb643e8f 242 // can only be switched fully on and fully off - we can't support
mjr 6:cc35eb643e8f 243 // dimming on these, so they'll ignore any intensity level setting
mjr 6:cc35eb643e8f 244 // requested by the host. Use these for devices that don't have any
mjr 6:cc35eb643e8f 245 // use for intensity settings anyway, such as contactors and knockers.
mjr 6:cc35eb643e8f 246 //
mjr 6:cc35eb643e8f 247 // The mapping between physical output pins on the KL25Z and the
mjr 6:cc35eb643e8f 248 // assigned LED-Wiz port numbers is essentially arbitrary - you can
mjr 6:cc35eb643e8f 249 // customize this by changing the entries in the array below if you
mjr 6:cc35eb643e8f 250 // wish to rearrange the pins for any reason. Be aware that some
mjr 6:cc35eb643e8f 251 // of the physical outputs are already used for other purposes
mjr 6:cc35eb643e8f 252 // (e.g., some of the GPIO pins on header J10 are used for the
mjr 6:cc35eb643e8f 253 // CCD sensor - but you can of course reassign those as well by
mjr 6:cc35eb643e8f 254 // changing the corresponding declarations elsewhere in this module).
mjr 6:cc35eb643e8f 255 // The assignments we make here have two main objectives: first,
mjr 6:cc35eb643e8f 256 // to group the outputs on headers J1 and J2 (to facilitate neater
mjr 6:cc35eb643e8f 257 // wiring by keeping the output pins together physically), and
mjr 6:cc35eb643e8f 258 // second, to make the physical pin layout match the LED-Wiz port
mjr 6:cc35eb643e8f 259 // numbering order to the extent possible. There's one big wrench
mjr 6:cc35eb643e8f 260 // in the works, though, which is the limited number and discontiguous
mjr 6:cc35eb643e8f 261 // placement of the KL25Z PWM-capable output pins. This prevents
mjr 6:cc35eb643e8f 262 // us from doing the most obvious sequential ordering of the pins,
mjr 6:cc35eb643e8f 263 // so we end up with the outputs arranged into several blocks.
mjr 6:cc35eb643e8f 264 // Hopefully this isn't too confusing; for more detailed rationale,
mjr 6:cc35eb643e8f 265 // read on...
mjr 6:cc35eb643e8f 266 //
mjr 6:cc35eb643e8f 267 // With the LED-Wiz, the host software configuration usually
mjr 6:cc35eb643e8f 268 // assumes that each RGB LED is hooked up to three consecutive ports
mjr 6:cc35eb643e8f 269 // (for the red, green, and blue components, which need to be
mjr 6:cc35eb643e8f 270 // physically wired to separate outputs to allow each color to be
mjr 6:cc35eb643e8f 271 // controlled independently). To facilitate this, we arrange the
mjr 6:cc35eb643e8f 272 // PWM-enabled outputs so that they're grouped together in the
mjr 6:cc35eb643e8f 273 // port numbering scheme. Unfortunately, these outputs aren't
mjr 6:cc35eb643e8f 274 // together in a single group in the physical pin layout, so to
mjr 6:cc35eb643e8f 275 // group them logically in the LED-Wiz port numbering scheme, we
mjr 6:cc35eb643e8f 276 // have to break up the overall numbering scheme into several blocks.
mjr 6:cc35eb643e8f 277 // So our port numbering goes sequentially down each column of
mjr 6:cc35eb643e8f 278 // header pins, but there are several break points where we have
mjr 6:cc35eb643e8f 279 // to interrupt the obvious sequence to keep the PWM pins grouped
mjr 6:cc35eb643e8f 280 // logically.
mjr 6:cc35eb643e8f 281 //
mjr 6:cc35eb643e8f 282 // In the list below, "pin J1-2" refers to pin 2 on header J1 on
mjr 6:cc35eb643e8f 283 // the KL25Z, using the standard pin numbering in the KL25Z
mjr 6:cc35eb643e8f 284 // documentation - this is the physical pin that the port controls.
mjr 6:cc35eb643e8f 285 // "LW port 1" means LED-Wiz port 1 - this is the LED-Wiz port
mjr 6:cc35eb643e8f 286 // number that you use on the PC side (in the DirectOutput config
mjr 6:cc35eb643e8f 287 // file, for example) to address the port. PWM-capable ports are
mjr 6:cc35eb643e8f 288 // marked as such - we group the PWM-capable ports into the first
mjr 6:cc35eb643e8f 289 // 10 LED-Wiz port numbers.
mjr 6:cc35eb643e8f 290 //
mjr 6:cc35eb643e8f 291 struct {
mjr 6:cc35eb643e8f 292 PinName pin;
mjr 6:cc35eb643e8f 293 bool isPWM;
mjr 6:cc35eb643e8f 294 } ledWizPortMap[32] = {
mjr 6:cc35eb643e8f 295 { PTA1, true }, // pin J1-2, LW port 1 (PWM capable - TPM 2.0 = channel 9)
mjr 6:cc35eb643e8f 296 { PTA2, true }, // pin J1-4, LW port 2 (PWM capable - TPM 2.1 = channel 10)
mjr 6:cc35eb643e8f 297 { PTD4, true }, // pin J1-6, LW port 3 (PWM capable - TPM 0.4 = channel 5)
mjr 6:cc35eb643e8f 298 { PTA12, true }, // pin J1-8, LW port 4 (PWM capable - TPM 1.0 = channel 7)
mjr 6:cc35eb643e8f 299 { PTA4, true }, // pin J1-10, LW port 5 (PWM capable - TPM 0.1 = channel 2)
mjr 6:cc35eb643e8f 300 { PTA5, true }, // pin J1-12, LW port 6 (PWM capable - TPM 0.2 = channel 3)
mjr 6:cc35eb643e8f 301 { PTA13, true }, // pin J2-2, LW port 7 (PWM capable - TPM 1.1 = channel 13)
mjr 6:cc35eb643e8f 302 { PTD5, true }, // pin J2-4, LW port 8 (PWM capable - TPM 0.5 = channel 6)
mjr 6:cc35eb643e8f 303 { PTD0, true }, // pin J2-6, LW port 9 (PWM capable - TPM 0.0 = channel 1)
mjr 6:cc35eb643e8f 304 { PTD3, true }, // pin J2-10, LW port 10 (PWM capable - TPM 0.3 = channel 4)
mjr 6:cc35eb643e8f 305 { PTC8, false }, // pin J1-14, LW port 11
mjr 6:cc35eb643e8f 306 { PTC9, false }, // pin J1-16, LW port 12
mjr 6:cc35eb643e8f 307 { PTC7, false }, // pin J1-1, LW port 13
mjr 6:cc35eb643e8f 308 { PTC0, false }, // pin J1-3, LW port 14
mjr 6:cc35eb643e8f 309 { PTC3, false }, // pin J1-5, LW port 15
mjr 6:cc35eb643e8f 310 { PTC4, false }, // pin J1-7, LW port 16
mjr 6:cc35eb643e8f 311 { PTC5, false }, // pin J1-9, LW port 17
mjr 6:cc35eb643e8f 312 { PTC6, false }, // pin J1-11, LW port 18
mjr 6:cc35eb643e8f 313 { PTC10, false }, // pin J1-13, LW port 19
mjr 6:cc35eb643e8f 314 { PTC11, false }, // pin J1-15, LW port 20
mjr 6:cc35eb643e8f 315 { PTC12, false }, // pin J2-1, LW port 21
mjr 6:cc35eb643e8f 316 { PTC13, false }, // pin J2-3, LW port 22
mjr 6:cc35eb643e8f 317 { PTC16, false }, // pin J2-5, LW port 23
mjr 6:cc35eb643e8f 318 { PTC17, false }, // pin J2-7, LW port 24
mjr 6:cc35eb643e8f 319 { PTA16, false }, // pin J2-9, LW port 25
mjr 6:cc35eb643e8f 320 { PTA17, false }, // pin J2-11, LW port 26
mjr 6:cc35eb643e8f 321 { PTE31, false }, // pin J2-13, LW port 27
mjr 6:cc35eb643e8f 322 { PTD6, false }, // pin J2-17, LW port 29
mjr 6:cc35eb643e8f 323 { PTD7, false }, // pin J2-19, LW port 30
mjr 6:cc35eb643e8f 324 { PTE0, false }, // pin J2-18, LW port 31
mjr 6:cc35eb643e8f 325 { PTE1, false } // pin J2-20, LW port 32
mjr 6:cc35eb643e8f 326 };
mjr 6:cc35eb643e8f 327
mjr 6:cc35eb643e8f 328
mjr 5:a70c0bce770d 329 // I2C address of the accelerometer (this is a constant of the KL25Z)
mjr 5:a70c0bce770d 330 const int MMA8451_I2C_ADDRESS = (0x1d<<1);
mjr 5:a70c0bce770d 331
mjr 5:a70c0bce770d 332 // SCL and SDA pins for the accelerometer (constant for the KL25Z)
mjr 5:a70c0bce770d 333 #define MMA8451_SCL_PIN PTE25
mjr 5:a70c0bce770d 334 #define MMA8451_SDA_PIN PTE24
mjr 5:a70c0bce770d 335
mjr 5:a70c0bce770d 336 // Digital in pin to use for the accelerometer interrupt. For the KL25Z,
mjr 5:a70c0bce770d 337 // this can be either PTA14 or PTA15, since those are the pins physically
mjr 5:a70c0bce770d 338 // wired on this board to the MMA8451 interrupt controller.
mjr 5:a70c0bce770d 339 #define MMA8451_INT_PIN PTA15
mjr 5:a70c0bce770d 340
mjr 6:cc35eb643e8f 341 // Joystick axis report range - we report from -JOYMAX to +JOYMAX
mjr 6:cc35eb643e8f 342 #define JOYMAX 4096
mjr 6:cc35eb643e8f 343
mjr 5:a70c0bce770d 344
mjr 5:a70c0bce770d 345 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 346 //
mjr 5:a70c0bce770d 347 // LedWiz emulation
mjr 5:a70c0bce770d 348 //
mjr 5:a70c0bce770d 349
mjr 0:5acbbe3f4cf4 350 static int pbaIdx = 0;
mjr 0:5acbbe3f4cf4 351
mjr 6:cc35eb643e8f 352 // LedWiz output pin interface. We create a cover class to virtualize
mjr 6:cc35eb643e8f 353 // digital vs PWM outputs and give them a common interface. The KL25Z
mjr 6:cc35eb643e8f 354 // unfortunately doesn't have enough hardware PWM channels to support
mjr 6:cc35eb643e8f 355 // PWM on all 32 LedWiz outputs, so we provide as many PWM channels as
mjr 6:cc35eb643e8f 356 // we can (10), and fill out the rest of the outputs with plain digital
mjr 6:cc35eb643e8f 357 // outs.
mjr 6:cc35eb643e8f 358 class LwOut
mjr 6:cc35eb643e8f 359 {
mjr 6:cc35eb643e8f 360 public:
mjr 6:cc35eb643e8f 361 virtual void set(float val) = 0;
mjr 6:cc35eb643e8f 362 };
mjr 6:cc35eb643e8f 363 class LwPwmOut: public LwOut
mjr 6:cc35eb643e8f 364 {
mjr 6:cc35eb643e8f 365 public:
mjr 6:cc35eb643e8f 366 LwPwmOut(PinName pin) : p(pin) { }
mjr 6:cc35eb643e8f 367 virtual void set(float val) { p = val; }
mjr 6:cc35eb643e8f 368 PwmOut p;
mjr 6:cc35eb643e8f 369 };
mjr 6:cc35eb643e8f 370 class LwDigOut: public LwOut
mjr 6:cc35eb643e8f 371 {
mjr 6:cc35eb643e8f 372 public:
mjr 6:cc35eb643e8f 373 LwDigOut(PinName pin) : p(pin) { }
mjr 6:cc35eb643e8f 374 virtual void set(float val) { p = val; }
mjr 6:cc35eb643e8f 375 DigitalOut p;
mjr 6:cc35eb643e8f 376 };
mjr 6:cc35eb643e8f 377
mjr 6:cc35eb643e8f 378 // output pin array
mjr 6:cc35eb643e8f 379 static LwOut *lwPin[32];
mjr 6:cc35eb643e8f 380
mjr 6:cc35eb643e8f 381 // initialize the output pin array
mjr 6:cc35eb643e8f 382 void initLwOut()
mjr 6:cc35eb643e8f 383 {
mjr 6:cc35eb643e8f 384 for (int i = 0 ; i < sizeof(lwPin) / sizeof(lwPin[0]) ; ++i)
mjr 6:cc35eb643e8f 385 {
mjr 6:cc35eb643e8f 386 PinName p = ledWizPortMap[i].pin;
mjr 6:cc35eb643e8f 387 lwPin[i] = (ledWizPortMap[i].isPWM
mjr 6:cc35eb643e8f 388 ? (LwOut *)new LwPwmOut(p)
mjr 6:cc35eb643e8f 389 : (LwOut *)new LwDigOut(p));
mjr 6:cc35eb643e8f 390 }
mjr 6:cc35eb643e8f 391 }
mjr 6:cc35eb643e8f 392
mjr 0:5acbbe3f4cf4 393 // on/off state for each LedWiz output
mjr 1:d913e0afb2ac 394 static uint8_t wizOn[32];
mjr 0:5acbbe3f4cf4 395
mjr 0:5acbbe3f4cf4 396 // profile (brightness/blink) state for each LedWiz output
mjr 1:d913e0afb2ac 397 static uint8_t wizVal[32] = {
mjr 0:5acbbe3f4cf4 398 0, 0, 0, 0, 0, 0, 0, 0,
mjr 0:5acbbe3f4cf4 399 0, 0, 0, 0, 0, 0, 0, 0,
mjr 0:5acbbe3f4cf4 400 0, 0, 0, 0, 0, 0, 0, 0,
mjr 0:5acbbe3f4cf4 401 0, 0, 0, 0, 0, 0, 0, 0
mjr 0:5acbbe3f4cf4 402 };
mjr 0:5acbbe3f4cf4 403
mjr 1:d913e0afb2ac 404 static float wizState(int idx)
mjr 0:5acbbe3f4cf4 405 {
mjr 1:d913e0afb2ac 406 if (wizOn[idx]) {
mjr 0:5acbbe3f4cf4 407 // on - map profile brightness state to PWM level
mjr 1:d913e0afb2ac 408 uint8_t val = wizVal[idx];
mjr 0:5acbbe3f4cf4 409 if (val >= 1 && val <= 48)
mjr 0:5acbbe3f4cf4 410 return 1.0 - val/48.0;
mjr 0:5acbbe3f4cf4 411 else if (val >= 129 && val <= 132)
mjr 0:5acbbe3f4cf4 412 return 0.0;
mjr 0:5acbbe3f4cf4 413 else
mjr 0:5acbbe3f4cf4 414 return 1.0;
mjr 0:5acbbe3f4cf4 415 }
mjr 0:5acbbe3f4cf4 416 else {
mjr 0:5acbbe3f4cf4 417 // off
mjr 0:5acbbe3f4cf4 418 return 1.0;
mjr 0:5acbbe3f4cf4 419 }
mjr 0:5acbbe3f4cf4 420 }
mjr 0:5acbbe3f4cf4 421
mjr 1:d913e0afb2ac 422 static void updateWizOuts()
mjr 1:d913e0afb2ac 423 {
mjr 6:cc35eb643e8f 424 for (int i = 0 ; i < 32 ; ++i)
mjr 6:cc35eb643e8f 425 lwPin[i]->set(wizState(i));
mjr 1:d913e0afb2ac 426 }
mjr 1:d913e0afb2ac 427
mjr 5:a70c0bce770d 428 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 429 //
mjr 5:a70c0bce770d 430 // Non-volatile memory (NVM)
mjr 5:a70c0bce770d 431 //
mjr 0:5acbbe3f4cf4 432
mjr 5:a70c0bce770d 433 // Structure defining our NVM storage layout. We store a small
mjr 2:c174f9ee414a 434 // amount of persistent data in flash memory to retain calibration
mjr 5:a70c0bce770d 435 // data when powered off.
mjr 2:c174f9ee414a 436 struct NVM
mjr 2:c174f9ee414a 437 {
mjr 2:c174f9ee414a 438 // checksum - we use this to determine if the flash record
mjr 6:cc35eb643e8f 439 // has been properly initialized
mjr 2:c174f9ee414a 440 uint32_t checksum;
mjr 2:c174f9ee414a 441
mjr 2:c174f9ee414a 442 // signature value
mjr 2:c174f9ee414a 443 static const uint32_t SIGNATURE = 0x4D4A522A;
mjr 6:cc35eb643e8f 444 static const uint16_t VERSION = 0x0003;
mjr 6:cc35eb643e8f 445
mjr 6:cc35eb643e8f 446 // Is the data structure valid? We test the signature and
mjr 6:cc35eb643e8f 447 // checksum to determine if we've been properly stored.
mjr 6:cc35eb643e8f 448 int valid() const
mjr 6:cc35eb643e8f 449 {
mjr 6:cc35eb643e8f 450 return (d.sig == SIGNATURE
mjr 6:cc35eb643e8f 451 && d.vsn == VERSION
mjr 6:cc35eb643e8f 452 && d.sz == sizeof(NVM)
mjr 6:cc35eb643e8f 453 && checksum == CRC32(&d, sizeof(d)));
mjr 6:cc35eb643e8f 454 }
mjr 6:cc35eb643e8f 455
mjr 6:cc35eb643e8f 456 // save to non-volatile memory
mjr 6:cc35eb643e8f 457 void save(FreescaleIAP &iap, int addr)
mjr 6:cc35eb643e8f 458 {
mjr 6:cc35eb643e8f 459 // update the checksum and structure size
mjr 6:cc35eb643e8f 460 checksum = CRC32(&d, sizeof(d));
mjr 6:cc35eb643e8f 461 d.sz = sizeof(NVM);
mjr 6:cc35eb643e8f 462
mjr 6:cc35eb643e8f 463 // erase the sector
mjr 6:cc35eb643e8f 464 iap.erase_sector(addr);
mjr 6:cc35eb643e8f 465
mjr 6:cc35eb643e8f 466 // save the data
mjr 6:cc35eb643e8f 467 iap.program_flash(addr, this, sizeof(*this));
mjr 6:cc35eb643e8f 468 }
mjr 2:c174f9ee414a 469
mjr 2:c174f9ee414a 470 // stored data (excluding the checksum)
mjr 2:c174f9ee414a 471 struct
mjr 2:c174f9ee414a 472 {
mjr 6:cc35eb643e8f 473 // Signature, structure version, and structure size - further verification
mjr 6:cc35eb643e8f 474 // that we have valid initialized data. The size is a simple proxy for a
mjr 6:cc35eb643e8f 475 // structure version, as the most common type of change to the structure as
mjr 6:cc35eb643e8f 476 // the software evolves will be the addition of new elements. We also
mjr 6:cc35eb643e8f 477 // provide an explicit version number that we can update manually if we
mjr 6:cc35eb643e8f 478 // make any changes that don't affect the structure size but would affect
mjr 6:cc35eb643e8f 479 // compatibility with a saved record (e.g., swapping two existing elements).
mjr 2:c174f9ee414a 480 uint32_t sig;
mjr 2:c174f9ee414a 481 uint16_t vsn;
mjr 6:cc35eb643e8f 482 int sz;
mjr 2:c174f9ee414a 483
mjr 6:cc35eb643e8f 484 // has the plunger been manually calibrated?
mjr 6:cc35eb643e8f 485 int plungerCal;
mjr 6:cc35eb643e8f 486
mjr 2:c174f9ee414a 487 // plunger calibration min and max
mjr 2:c174f9ee414a 488 int plungerMin;
mjr 6:cc35eb643e8f 489 int plungerZero;
mjr 2:c174f9ee414a 490 int plungerMax;
mjr 6:cc35eb643e8f 491
mjr 6:cc35eb643e8f 492 // is the CCD enabled?
mjr 6:cc35eb643e8f 493 int ccdEnabled;
mjr 6:cc35eb643e8f 494
mjr 6:cc35eb643e8f 495 // LedWiz unit number
mjr 6:cc35eb643e8f 496 uint8_t ledWizUnitNo;
mjr 2:c174f9ee414a 497 } d;
mjr 2:c174f9ee414a 498 };
mjr 2:c174f9ee414a 499
mjr 5:a70c0bce770d 500
mjr 5:a70c0bce770d 501 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 502 //
mjr 5:a70c0bce770d 503 // Customization joystick subbclass
mjr 5:a70c0bce770d 504 //
mjr 5:a70c0bce770d 505
mjr 5:a70c0bce770d 506 class MyUSBJoystick: public USBJoystick
mjr 5:a70c0bce770d 507 {
mjr 5:a70c0bce770d 508 public:
mjr 5:a70c0bce770d 509 MyUSBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release)
mjr 5:a70c0bce770d 510 : USBJoystick(vendor_id, product_id, product_release, true)
mjr 5:a70c0bce770d 511 {
mjr 5:a70c0bce770d 512 suspended_ = false;
mjr 5:a70c0bce770d 513 }
mjr 5:a70c0bce770d 514
mjr 5:a70c0bce770d 515 // are we connected?
mjr 5:a70c0bce770d 516 int isConnected() { return configured(); }
mjr 5:a70c0bce770d 517
mjr 5:a70c0bce770d 518 // Are we in suspend mode?
mjr 5:a70c0bce770d 519 int isSuspended() const { return suspended_; }
mjr 5:a70c0bce770d 520
mjr 5:a70c0bce770d 521 protected:
mjr 5:a70c0bce770d 522 virtual void suspendStateChanged(unsigned int suspended)
mjr 5:a70c0bce770d 523 { suspended_ = suspended; }
mjr 5:a70c0bce770d 524
mjr 5:a70c0bce770d 525 // are we suspended?
mjr 5:a70c0bce770d 526 int suspended_;
mjr 5:a70c0bce770d 527 };
mjr 5:a70c0bce770d 528
mjr 5:a70c0bce770d 529 // ---------------------------------------------------------------------------
mjr 6:cc35eb643e8f 530 //
mjr 6:cc35eb643e8f 531 // Some simple math service routines
mjr 6:cc35eb643e8f 532 //
mjr 6:cc35eb643e8f 533
mjr 6:cc35eb643e8f 534 inline float square(float x) { return x*x; }
mjr 6:cc35eb643e8f 535 inline float round(float x) { return x > 0 ? floor(x + 0.5) : ceil(x - 0.5); }
mjr 6:cc35eb643e8f 536
mjr 6:cc35eb643e8f 537 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 538 //
mjr 5:a70c0bce770d 539 // Accelerometer (MMA8451Q)
mjr 5:a70c0bce770d 540 //
mjr 5:a70c0bce770d 541
mjr 5:a70c0bce770d 542 // The MMA8451Q is the KL25Z's on-board 3-axis accelerometer.
mjr 5:a70c0bce770d 543 //
mjr 5:a70c0bce770d 544 // This is a custom wrapper for the library code to interface to the
mjr 6:cc35eb643e8f 545 // MMA8451Q. This class encapsulates an interrupt handler and
mjr 6:cc35eb643e8f 546 // automatic calibration.
mjr 5:a70c0bce770d 547 //
mjr 5:a70c0bce770d 548 // We install an interrupt handler on the accelerometer "data ready"
mjr 6:cc35eb643e8f 549 // interrupt to ensure that we fetch each sample immediately when it
mjr 6:cc35eb643e8f 550 // becomes available. The accelerometer data rate is fiarly high
mjr 6:cc35eb643e8f 551 // (800 Hz), so it's not practical to keep up with it by polling.
mjr 6:cc35eb643e8f 552 // Using an interrupt handler lets us respond quickly and read
mjr 6:cc35eb643e8f 553 // every sample.
mjr 5:a70c0bce770d 554 //
mjr 6:cc35eb643e8f 555 // We automatically calibrate the accelerometer so that it's not
mjr 6:cc35eb643e8f 556 // necessary to get it exactly level when installing it, and so
mjr 6:cc35eb643e8f 557 // that it's also not necessary to calibrate it manually. There's
mjr 6:cc35eb643e8f 558 // lots of experience that tells us that manual calibration is a
mjr 6:cc35eb643e8f 559 // terrible solution, mostly because cabinets tend to shift slightly
mjr 6:cc35eb643e8f 560 // during use, requiring frequent recalibration. Instead, we
mjr 6:cc35eb643e8f 561 // calibrate automatically. We continuously monitor the acceleration
mjr 6:cc35eb643e8f 562 // data, watching for periods of constant (or nearly constant) values.
mjr 6:cc35eb643e8f 563 // Any time it appears that the machine has been at rest for a while
mjr 6:cc35eb643e8f 564 // (about 5 seconds), we'll average the readings during that rest
mjr 6:cc35eb643e8f 565 // period and use the result as the level rest position. This is
mjr 6:cc35eb643e8f 566 // is ongoing, so we'll quickly find the center point again if the
mjr 6:cc35eb643e8f 567 // machine is moved during play (by an especially aggressive bout
mjr 6:cc35eb643e8f 568 // of nudging, say).
mjr 5:a70c0bce770d 569 //
mjr 5:a70c0bce770d 570
mjr 6:cc35eb643e8f 571 // accelerometer input history item, for gathering calibration data
mjr 6:cc35eb643e8f 572 struct AccHist
mjr 5:a70c0bce770d 573 {
mjr 6:cc35eb643e8f 574 AccHist() { x = y = d = 0.0; xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 575 void set(float x, float y, AccHist *prv)
mjr 6:cc35eb643e8f 576 {
mjr 6:cc35eb643e8f 577 // save the raw position
mjr 6:cc35eb643e8f 578 this->x = x;
mjr 6:cc35eb643e8f 579 this->y = y;
mjr 6:cc35eb643e8f 580 this->d = distance(prv);
mjr 6:cc35eb643e8f 581 }
mjr 6:cc35eb643e8f 582
mjr 6:cc35eb643e8f 583 // reading for this entry
mjr 5:a70c0bce770d 584 float x, y;
mjr 5:a70c0bce770d 585
mjr 6:cc35eb643e8f 586 // distance from previous entry
mjr 6:cc35eb643e8f 587 float d;
mjr 5:a70c0bce770d 588
mjr 6:cc35eb643e8f 589 // total and count of samples averaged over this period
mjr 6:cc35eb643e8f 590 float xtot, ytot;
mjr 6:cc35eb643e8f 591 int cnt;
mjr 6:cc35eb643e8f 592
mjr 6:cc35eb643e8f 593 void clearAvg() { xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 594 void addAvg(float x, float y) { xtot += x; ytot += y; ++cnt; }
mjr 6:cc35eb643e8f 595 float xAvg() const { return xtot/cnt; }
mjr 6:cc35eb643e8f 596 float yAvg() const { return ytot/cnt; }
mjr 5:a70c0bce770d 597
mjr 6:cc35eb643e8f 598 float distance(AccHist *p)
mjr 6:cc35eb643e8f 599 { return sqrt(square(p->x - x) + square(p->y - y)); }
mjr 5:a70c0bce770d 600 };
mjr 5:a70c0bce770d 601
mjr 5:a70c0bce770d 602 // accelerometer wrapper class
mjr 3:3514575d4f86 603 class Accel
mjr 3:3514575d4f86 604 {
mjr 3:3514575d4f86 605 public:
mjr 3:3514575d4f86 606 Accel(PinName sda, PinName scl, int i2cAddr, PinName irqPin)
mjr 3:3514575d4f86 607 : mma_(sda, scl, i2cAddr), intIn_(irqPin)
mjr 3:3514575d4f86 608 {
mjr 5:a70c0bce770d 609 // remember the interrupt pin assignment
mjr 5:a70c0bce770d 610 irqPin_ = irqPin;
mjr 5:a70c0bce770d 611
mjr 5:a70c0bce770d 612 // reset and initialize
mjr 5:a70c0bce770d 613 reset();
mjr 5:a70c0bce770d 614 }
mjr 5:a70c0bce770d 615
mjr 5:a70c0bce770d 616 void reset()
mjr 5:a70c0bce770d 617 {
mjr 6:cc35eb643e8f 618 // clear the center point
mjr 6:cc35eb643e8f 619 cx_ = cy_ = 0.0;
mjr 6:cc35eb643e8f 620
mjr 6:cc35eb643e8f 621 // start the calibration timer
mjr 5:a70c0bce770d 622 tCenter_.start();
mjr 5:a70c0bce770d 623 iAccPrv_ = nAccPrv_ = 0;
mjr 6:cc35eb643e8f 624
mjr 5:a70c0bce770d 625 // reset and initialize the MMA8451Q
mjr 5:a70c0bce770d 626 mma_.init();
mjr 6:cc35eb643e8f 627
mjr 6:cc35eb643e8f 628 // set the initial integrated velocity reading to zero
mjr 6:cc35eb643e8f 629 vx_ = vy_ = 0;
mjr 3:3514575d4f86 630
mjr 6:cc35eb643e8f 631 // set up our accelerometer interrupt handling
mjr 6:cc35eb643e8f 632 intIn_.rise(this, &Accel::isr);
mjr 5:a70c0bce770d 633 mma_.setInterruptMode(irqPin_ == PTA14 ? 1 : 2);
mjr 3:3514575d4f86 634
mjr 3:3514575d4f86 635 // read the current registers to clear the data ready flag
mjr 6:cc35eb643e8f 636 mma_.getAccXYZ(ax_, ay_, az_);
mjr 3:3514575d4f86 637
mjr 3:3514575d4f86 638 // start our timers
mjr 3:3514575d4f86 639 tGet_.start();
mjr 3:3514575d4f86 640 tInt_.start();
mjr 3:3514575d4f86 641 }
mjr 3:3514575d4f86 642
mjr 6:cc35eb643e8f 643 void get(int &x, int &y, int &rx, int &ry)
mjr 3:3514575d4f86 644 {
mjr 3:3514575d4f86 645 // disable interrupts while manipulating the shared data
mjr 3:3514575d4f86 646 __disable_irq();
mjr 3:3514575d4f86 647
mjr 3:3514575d4f86 648 // read the shared data and store locally for calculations
mjr 6:cc35eb643e8f 649 float ax = ax_, ay = ay_;
mjr 6:cc35eb643e8f 650 float vx = vx_, vy = vy_;
mjr 5:a70c0bce770d 651
mjr 6:cc35eb643e8f 652 // reset the velocity sum for the next run
mjr 6:cc35eb643e8f 653 vx_ = vy_ = 0;
mjr 3:3514575d4f86 654
mjr 3:3514575d4f86 655 // get the time since the last get() sample
mjr 3:3514575d4f86 656 float dt = tGet_.read_us()/1.0e6;
mjr 3:3514575d4f86 657 tGet_.reset();
mjr 3:3514575d4f86 658
mjr 3:3514575d4f86 659 // done manipulating the shared data
mjr 3:3514575d4f86 660 __enable_irq();
mjr 3:3514575d4f86 661
mjr 6:cc35eb643e8f 662 // adjust the readings for the integration time
mjr 6:cc35eb643e8f 663 vx /= dt;
mjr 6:cc35eb643e8f 664 vy /= dt;
mjr 6:cc35eb643e8f 665
mjr 6:cc35eb643e8f 666 // add this sample to the current calibration interval's running total
mjr 6:cc35eb643e8f 667 AccHist *p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 668 p->addAvg(ax, ay);
mjr 6:cc35eb643e8f 669
mjr 5:a70c0bce770d 670 // check for auto-centering every so often
mjr 5:a70c0bce770d 671 if (tCenter_.read_ms() > 1000)
mjr 5:a70c0bce770d 672 {
mjr 5:a70c0bce770d 673 // add the latest raw sample to the history list
mjr 6:cc35eb643e8f 674 AccHist *prv = p;
mjr 5:a70c0bce770d 675 iAccPrv_ = (iAccPrv_ + 1) % maxAccPrv;
mjr 6:cc35eb643e8f 676 p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 677 p->set(ax, ay, prv);
mjr 5:a70c0bce770d 678
mjr 5:a70c0bce770d 679 // if we have a full complement, check for stability
mjr 5:a70c0bce770d 680 if (nAccPrv_ >= maxAccPrv)
mjr 5:a70c0bce770d 681 {
mjr 5:a70c0bce770d 682 // check if we've been stable for all recent samples
mjr 6:cc35eb643e8f 683 static const float accTol = .01;
mjr 6:cc35eb643e8f 684 AccHist *p0 = accPrv_;
mjr 6:cc35eb643e8f 685 if (p0[0].d < accTol
mjr 6:cc35eb643e8f 686 && p0[1].d < accTol
mjr 6:cc35eb643e8f 687 && p0[2].d < accTol
mjr 6:cc35eb643e8f 688 && p0[3].d < accTol
mjr 6:cc35eb643e8f 689 && p0[4].d < accTol)
mjr 5:a70c0bce770d 690 {
mjr 6:cc35eb643e8f 691 // Figure the new calibration point as the average of
mjr 6:cc35eb643e8f 692 // the samples over the rest period
mjr 6:cc35eb643e8f 693 cx_ = (p0[0].xAvg() + p0[1].xAvg() + p0[2].xAvg() + p0[3].xAvg() + p0[4].xAvg())/5.0;
mjr 6:cc35eb643e8f 694 cy_ = (p0[0].yAvg() + p0[1].yAvg() + p0[2].yAvg() + p0[3].yAvg() + p0[4].yAvg())/5.0;
mjr 5:a70c0bce770d 695 }
mjr 5:a70c0bce770d 696 }
mjr 5:a70c0bce770d 697 else
mjr 5:a70c0bce770d 698 {
mjr 5:a70c0bce770d 699 // not enough samples yet; just up the count
mjr 5:a70c0bce770d 700 ++nAccPrv_;
mjr 5:a70c0bce770d 701 }
mjr 6:cc35eb643e8f 702
mjr 6:cc35eb643e8f 703 // clear the new item's running totals
mjr 6:cc35eb643e8f 704 p->clearAvg();
mjr 5:a70c0bce770d 705
mjr 5:a70c0bce770d 706 // reset the timer
mjr 5:a70c0bce770d 707 tCenter_.reset();
mjr 5:a70c0bce770d 708 }
mjr 5:a70c0bce770d 709
mjr 6:cc35eb643e8f 710 // report our integrated velocity reading in x,y
mjr 6:cc35eb643e8f 711 x = rawToReport(vx);
mjr 6:cc35eb643e8f 712 y = rawToReport(vy);
mjr 5:a70c0bce770d 713
mjr 6:cc35eb643e8f 714 // apply a small dead zone near the center
mjr 6:cc35eb643e8f 715 // if (abs(x) < 6) x = 0;
mjr 6:cc35eb643e8f 716 // if (abs(y) < 6) y = 0;
mjr 5:a70c0bce770d 717
mjr 5:a70c0bce770d 718 // report the calibrated instantaneous acceleration in rx,ry
mjr 6:cc35eb643e8f 719 rx = int(round((ax - cx_)*JOYMAX));
mjr 6:cc35eb643e8f 720 ry = int(round((ay - cy_)*JOYMAX));
mjr 6:cc35eb643e8f 721
mjr 6:cc35eb643e8f 722 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 723 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 724 printf("%f %f %d %d %f\r\n", vx, vy, x, y, dt);
mjr 6:cc35eb643e8f 725 #endif
mjr 3:3514575d4f86 726 }
mjr 3:3514575d4f86 727
mjr 3:3514575d4f86 728 private:
mjr 6:cc35eb643e8f 729 // adjust a raw acceleration figure to a usb report value
mjr 6:cc35eb643e8f 730 int rawToReport(float v)
mjr 5:a70c0bce770d 731 {
mjr 6:cc35eb643e8f 732 // scale to the joystick report range and round to integer
mjr 6:cc35eb643e8f 733 int i = int(round(v*JOYMAX));
mjr 5:a70c0bce770d 734
mjr 6:cc35eb643e8f 735 // if it's near the center, scale it roughly as 20*(i/20)^2,
mjr 6:cc35eb643e8f 736 // to suppress noise near the rest position
mjr 6:cc35eb643e8f 737 static const int filter[] = {
mjr 6:cc35eb643e8f 738 -18, -16, -14, -13, -11, -10, -8, -7, -6, -5, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0,
mjr 6:cc35eb643e8f 739 0,
mjr 6:cc35eb643e8f 740 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 16, 18
mjr 6:cc35eb643e8f 741 };
mjr 6:cc35eb643e8f 742 return (i > 20 || i < -20 ? i : filter[i+20]);
mjr 5:a70c0bce770d 743 }
mjr 5:a70c0bce770d 744
mjr 3:3514575d4f86 745 // interrupt handler
mjr 3:3514575d4f86 746 void isr()
mjr 3:3514575d4f86 747 {
mjr 3:3514575d4f86 748 // Read the axes. Note that we have to read all three axes
mjr 3:3514575d4f86 749 // (even though we only really use x and y) in order to clear
mjr 3:3514575d4f86 750 // the "data ready" status bit in the accelerometer. The
mjr 3:3514575d4f86 751 // interrupt only occurs when the "ready" bit transitions from
mjr 3:3514575d4f86 752 // off to on, so we have to make sure it's off.
mjr 5:a70c0bce770d 753 float x, y, z;
mjr 5:a70c0bce770d 754 mma_.getAccXYZ(x, y, z);
mjr 3:3514575d4f86 755
mjr 3:3514575d4f86 756 // calculate the time since the last interrupt
mjr 3:3514575d4f86 757 float dt = tInt_.read_us()/1.0e6;
mjr 3:3514575d4f86 758 tInt_.reset();
mjr 6:cc35eb643e8f 759
mjr 6:cc35eb643e8f 760 // integrate the time slice from the previous reading to this reading
mjr 6:cc35eb643e8f 761 vx_ += (x + ax_ - 2*cx_)*dt/2;
mjr 6:cc35eb643e8f 762 vy_ += (y + ay_ - 2*cy_)*dt/2;
mjr 3:3514575d4f86 763
mjr 6:cc35eb643e8f 764 // store the updates
mjr 6:cc35eb643e8f 765 ax_ = x;
mjr 6:cc35eb643e8f 766 ay_ = y;
mjr 6:cc35eb643e8f 767 az_ = z;
mjr 3:3514575d4f86 768 }
mjr 3:3514575d4f86 769
mjr 3:3514575d4f86 770 // underlying accelerometer object
mjr 3:3514575d4f86 771 MMA8451Q mma_;
mjr 3:3514575d4f86 772
mjr 5:a70c0bce770d 773 // last raw acceleration readings
mjr 6:cc35eb643e8f 774 float ax_, ay_, az_;
mjr 5:a70c0bce770d 775
mjr 6:cc35eb643e8f 776 // integrated velocity reading since last get()
mjr 6:cc35eb643e8f 777 float vx_, vy_;
mjr 6:cc35eb643e8f 778
mjr 3:3514575d4f86 779 // timer for measuring time between get() samples
mjr 3:3514575d4f86 780 Timer tGet_;
mjr 3:3514575d4f86 781
mjr 3:3514575d4f86 782 // timer for measuring time between interrupts
mjr 3:3514575d4f86 783 Timer tInt_;
mjr 5:a70c0bce770d 784
mjr 6:cc35eb643e8f 785 // Calibration reference point for accelerometer. This is the
mjr 6:cc35eb643e8f 786 // average reading on the accelerometer when in the neutral position
mjr 6:cc35eb643e8f 787 // at rest.
mjr 6:cc35eb643e8f 788 float cx_, cy_;
mjr 5:a70c0bce770d 789
mjr 5:a70c0bce770d 790 // timer for atuo-centering
mjr 5:a70c0bce770d 791 Timer tCenter_;
mjr 6:cc35eb643e8f 792
mjr 6:cc35eb643e8f 793 // Auto-centering history. This is a separate history list that
mjr 6:cc35eb643e8f 794 // records results spaced out sparesely over time, so that we can
mjr 6:cc35eb643e8f 795 // watch for long-lasting periods of rest. When we observe nearly
mjr 6:cc35eb643e8f 796 // no motion for an extended period (on the order of 5 seconds), we
mjr 6:cc35eb643e8f 797 // take this to mean that the cabinet is at rest in its neutral
mjr 6:cc35eb643e8f 798 // position, so we take this as the calibration zero point for the
mjr 6:cc35eb643e8f 799 // accelerometer. We update this history continuously, which allows
mjr 6:cc35eb643e8f 800 // us to continuously re-calibrate the accelerometer. This ensures
mjr 6:cc35eb643e8f 801 // that we'll automatically adjust to any actual changes in the
mjr 6:cc35eb643e8f 802 // cabinet's orientation (e.g., if it gets moved slightly by an
mjr 6:cc35eb643e8f 803 // especially strong nudge) as well as any systematic drift in the
mjr 6:cc35eb643e8f 804 // accelerometer measurement bias (e.g., from temperature changes).
mjr 5:a70c0bce770d 805 int iAccPrv_, nAccPrv_;
mjr 5:a70c0bce770d 806 static const int maxAccPrv = 5;
mjr 6:cc35eb643e8f 807 AccHist accPrv_[maxAccPrv];
mjr 6:cc35eb643e8f 808
mjr 5:a70c0bce770d 809 // interurupt pin name
mjr 5:a70c0bce770d 810 PinName irqPin_;
mjr 5:a70c0bce770d 811
mjr 5:a70c0bce770d 812 // interrupt router
mjr 5:a70c0bce770d 813 InterruptIn intIn_;
mjr 3:3514575d4f86 814 };
mjr 3:3514575d4f86 815
mjr 5:a70c0bce770d 816
mjr 5:a70c0bce770d 817 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 818 //
mjr 5:a70c0bce770d 819 // Clear the I2C bus for the MMA8451!. This seems necessary some of the time
mjr 5:a70c0bce770d 820 // for reasons that aren't clear to me. Doing a hard power cycle has the same
mjr 5:a70c0bce770d 821 // effect, but when we do a soft reset, the hardware sometimes seems to leave
mjr 5:a70c0bce770d 822 // the MMA's SDA line stuck low. Forcing a series of 9 clock pulses through
mjr 5:a70c0bce770d 823 // the SCL line is supposed to clear this conidtion.
mjr 5:a70c0bce770d 824 //
mjr 5:a70c0bce770d 825 void clear_i2c()
mjr 5:a70c0bce770d 826 {
mjr 5:a70c0bce770d 827 // assume a general-purpose output pin to the I2C clock
mjr 5:a70c0bce770d 828 DigitalOut scl(MMA8451_SCL_PIN);
mjr 5:a70c0bce770d 829 DigitalIn sda(MMA8451_SDA_PIN);
mjr 5:a70c0bce770d 830
mjr 5:a70c0bce770d 831 // clock the SCL 9 times
mjr 5:a70c0bce770d 832 for (int i = 0 ; i < 9 ; ++i)
mjr 5:a70c0bce770d 833 {
mjr 5:a70c0bce770d 834 scl = 1;
mjr 5:a70c0bce770d 835 wait_us(20);
mjr 5:a70c0bce770d 836 scl = 0;
mjr 5:a70c0bce770d 837 wait_us(20);
mjr 5:a70c0bce770d 838 }
mjr 5:a70c0bce770d 839 }
mjr 5:a70c0bce770d 840
mjr 5:a70c0bce770d 841 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 842 //
mjr 5:a70c0bce770d 843 // Main program loop. This is invoked on startup and runs forever. Our
mjr 5:a70c0bce770d 844 // main work is to read our devices (the accelerometer and the CCD), process
mjr 5:a70c0bce770d 845 // the readings into nudge and plunger position data, and send the results
mjr 5:a70c0bce770d 846 // to the host computer via the USB joystick interface. We also monitor
mjr 5:a70c0bce770d 847 // the USB connection for incoming LedWiz commands and process those into
mjr 5:a70c0bce770d 848 // port outputs.
mjr 5:a70c0bce770d 849 //
mjr 0:5acbbe3f4cf4 850 int main(void)
mjr 0:5acbbe3f4cf4 851 {
mjr 1:d913e0afb2ac 852 // turn off our on-board indicator LED
mjr 4:02c7cd7b2183 853 ledR = 1;
mjr 4:02c7cd7b2183 854 ledG = 1;
mjr 4:02c7cd7b2183 855 ledB = 1;
mjr 1:d913e0afb2ac 856
mjr 6:cc35eb643e8f 857 // initialize the LedWiz ports
mjr 6:cc35eb643e8f 858 initLwOut();
mjr 6:cc35eb643e8f 859
mjr 6:cc35eb643e8f 860 // we don't need a reset yet
mjr 6:cc35eb643e8f 861 bool needReset = false;
mjr 6:cc35eb643e8f 862
mjr 5:a70c0bce770d 863 // clear the I2C bus for the accelerometer
mjr 5:a70c0bce770d 864 clear_i2c();
mjr 5:a70c0bce770d 865
mjr 2:c174f9ee414a 866 // set up a flash memory controller
mjr 2:c174f9ee414a 867 FreescaleIAP iap;
mjr 2:c174f9ee414a 868
mjr 2:c174f9ee414a 869 // use the last sector of flash for our non-volatile memory structure
mjr 2:c174f9ee414a 870 int flash_addr = (iap.flash_size() - SECTOR_SIZE);
mjr 2:c174f9ee414a 871 NVM *flash = (NVM *)flash_addr;
mjr 2:c174f9ee414a 872 NVM cfg;
mjr 2:c174f9ee414a 873
mjr 2:c174f9ee414a 874 // check for valid flash
mjr 6:cc35eb643e8f 875 bool flash_valid = flash->valid();
mjr 2:c174f9ee414a 876
mjr 2:c174f9ee414a 877 // Number of pixels we read from the sensor on each frame. This can be
mjr 2:c174f9ee414a 878 // less than the physical pixel count if desired; we'll read every nth
mjr 2:c174f9ee414a 879 // piexl if so. E.g., with a 1280-pixel physical sensor, if npix is 320,
mjr 5:a70c0bce770d 880 // we'll read every 4th pixel. It takes time to read each pixel, so the
mjr 5:a70c0bce770d 881 // fewer pixels we read, the higher the refresh rate we can achieve.
mjr 5:a70c0bce770d 882 // It's therefore better not to read more pixels than we have to.
mjr 5:a70c0bce770d 883 //
mjr 5:a70c0bce770d 884 // VP seems to have an internal resolution in the 8-bit range, so there's
mjr 5:a70c0bce770d 885 // no apparent benefit to reading more than 128-256 pixels when using VP.
mjr 5:a70c0bce770d 886 // Empirically, 160 pixels seems about right. The overall travel of a
mjr 5:a70c0bce770d 887 // standard pinball plunger is about 3", so 160 pixels gives us resolution
mjr 5:a70c0bce770d 888 // of about 1/50". This seems to take full advantage of VP's modeling
mjr 5:a70c0bce770d 889 // ability, and is probably also more precise than a human player's
mjr 5:a70c0bce770d 890 // perception of the plunger position.
mjr 2:c174f9ee414a 891 const int npix = 160;
mjr 2:c174f9ee414a 892
mjr 2:c174f9ee414a 893 // if the flash is valid, load it; otherwise initialize to defaults
mjr 2:c174f9ee414a 894 if (flash_valid) {
mjr 2:c174f9ee414a 895 memcpy(&cfg, flash, sizeof(cfg));
mjr 6:cc35eb643e8f 896 printf("Flash restored: plunger cal=%d, min=%d, zero=%d, max=%d\r\n",
mjr 6:cc35eb643e8f 897 cfg.d.plungerCal, cfg.d.plungerMin, cfg.d.plungerZero, cfg.d.plungerMax);
mjr 2:c174f9ee414a 898 }
mjr 2:c174f9ee414a 899 else {
mjr 2:c174f9ee414a 900 printf("Factory reset\r\n");
mjr 2:c174f9ee414a 901 cfg.d.sig = cfg.SIGNATURE;
mjr 2:c174f9ee414a 902 cfg.d.vsn = cfg.VERSION;
mjr 6:cc35eb643e8f 903 cfg.d.plungerCal = 0;
mjr 6:cc35eb643e8f 904 cfg.d.plungerZero = 0;
mjr 2:c174f9ee414a 905 cfg.d.plungerMin = 0;
mjr 2:c174f9ee414a 906 cfg.d.plungerMax = npix;
mjr 6:cc35eb643e8f 907 cfg.d.ledWizUnitNo = DEFAULT_LEDWIZ_UNIT_NUMBER;
mjr 6:cc35eb643e8f 908 cfg.d.ccdEnabled = true;
mjr 2:c174f9ee414a 909 }
mjr 1:d913e0afb2ac 910
mjr 6:cc35eb643e8f 911 // Create the joystick USB client. Note that we use the LedWiz unit
mjr 6:cc35eb643e8f 912 // number from the saved configuration.
mjr 6:cc35eb643e8f 913 MyUSBJoystick js(
mjr 6:cc35eb643e8f 914 USB_VENDOR_ID,
mjr 6:cc35eb643e8f 915 USB_PRODUCT_ID | cfg.d.ledWizUnitNo,
mjr 6:cc35eb643e8f 916 USB_VERSION_NO);
mjr 6:cc35eb643e8f 917
mjr 1:d913e0afb2ac 918 // plunger calibration button debounce timer
mjr 1:d913e0afb2ac 919 Timer calBtnTimer;
mjr 1:d913e0afb2ac 920 calBtnTimer.start();
mjr 1:d913e0afb2ac 921 int calBtnDownTime = 0;
mjr 1:d913e0afb2ac 922 int calBtnLit = false;
mjr 1:d913e0afb2ac 923
mjr 1:d913e0afb2ac 924 // Calibration button state:
mjr 1:d913e0afb2ac 925 // 0 = not pushed
mjr 1:d913e0afb2ac 926 // 1 = pushed, not yet debounced
mjr 1:d913e0afb2ac 927 // 2 = pushed, debounced, waiting for hold time
mjr 1:d913e0afb2ac 928 // 3 = pushed, hold time completed - in calibration mode
mjr 1:d913e0afb2ac 929 int calBtnState = 0;
mjr 1:d913e0afb2ac 930
mjr 1:d913e0afb2ac 931 // set up a timer for our heartbeat indicator
mjr 1:d913e0afb2ac 932 Timer hbTimer;
mjr 1:d913e0afb2ac 933 hbTimer.start();
mjr 1:d913e0afb2ac 934 int hb = 0;
mjr 5:a70c0bce770d 935 uint16_t hbcnt = 0;
mjr 1:d913e0afb2ac 936
mjr 1:d913e0afb2ac 937 // set a timer for accelerometer auto-centering
mjr 1:d913e0afb2ac 938 Timer acTimer;
mjr 1:d913e0afb2ac 939 acTimer.start();
mjr 1:d913e0afb2ac 940
mjr 0:5acbbe3f4cf4 941 // create the accelerometer object
mjr 5:a70c0bce770d 942 Accel accel(MMA8451_SCL_PIN, MMA8451_SDA_PIN, MMA8451_I2C_ADDRESS, MMA8451_INT_PIN);
mjr 0:5acbbe3f4cf4 943
mjr 0:5acbbe3f4cf4 944 // create the CCD array object
mjr 1:d913e0afb2ac 945 TSL1410R ccd(PTE20, PTE21, PTB0);
mjr 2:c174f9ee414a 946
mjr 1:d913e0afb2ac 947 // last accelerometer report, in mouse coordinates
mjr 6:cc35eb643e8f 948 int x = 0, y = 0, z = 0;
mjr 6:cc35eb643e8f 949
mjr 6:cc35eb643e8f 950 // previous two plunger readings, for "debouncing" the results (z0 is
mjr 6:cc35eb643e8f 951 // the most recent, z1 is the one before that)
mjr 6:cc35eb643e8f 952 int z0 = 0, z1 = 0, z2 = 0;
mjr 6:cc35eb643e8f 953
mjr 6:cc35eb643e8f 954 // Firing in progress: we set this when we detect the start of rapid
mjr 6:cc35eb643e8f 955 // plunger movement from a retracted position towards the rest position.
mjr 6:cc35eb643e8f 956 // The actual plunger spring return speed seems to be too slow for VP,
mjr 6:cc35eb643e8f 957 // so when we detect the start of this motion, we immediately tell VP
mjr 6:cc35eb643e8f 958 // to return the plunger to rest, then we monitor the real plunger
mjr 6:cc35eb643e8f 959 // until it atcually stops.
mjr 6:cc35eb643e8f 960 bool firing = false;
mjr 2:c174f9ee414a 961
mjr 2:c174f9ee414a 962 // start the first CCD integration cycle
mjr 2:c174f9ee414a 963 ccd.clear();
mjr 1:d913e0afb2ac 964
mjr 1:d913e0afb2ac 965 // we're all set up - now just loop, processing sensor reports and
mjr 1:d913e0afb2ac 966 // host requests
mjr 0:5acbbe3f4cf4 967 for (;;)
mjr 0:5acbbe3f4cf4 968 {
mjr 0:5acbbe3f4cf4 969 // Look for an incoming report. Continue processing input as
mjr 0:5acbbe3f4cf4 970 // long as there's anything pending - this ensures that we
mjr 0:5acbbe3f4cf4 971 // handle input in as timely a fashion as possible by deferring
mjr 0:5acbbe3f4cf4 972 // output tasks as long as there's input to process.
mjr 0:5acbbe3f4cf4 973 HID_REPORT report;
mjr 6:cc35eb643e8f 974 while (js.readNB(&report))
mjr 0:5acbbe3f4cf4 975 {
mjr 6:cc35eb643e8f 976 // all Led-Wiz reports are 8 bytes exactly
mjr 6:cc35eb643e8f 977 if (report.length == 8)
mjr 1:d913e0afb2ac 978 {
mjr 6:cc35eb643e8f 979 uint8_t *data = report.data;
mjr 6:cc35eb643e8f 980 if (data[0] == 64)
mjr 0:5acbbe3f4cf4 981 {
mjr 6:cc35eb643e8f 982 // LWZ-SBA - first four bytes are bit-packed on/off flags
mjr 6:cc35eb643e8f 983 // for the outputs; 5th byte is the pulse speed (0-7)
mjr 6:cc35eb643e8f 984 //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n",
mjr 6:cc35eb643e8f 985 // data[1], data[2], data[3], data[4], data[5]);
mjr 0:5acbbe3f4cf4 986
mjr 6:cc35eb643e8f 987 // update all on/off states
mjr 6:cc35eb643e8f 988 for (int i = 0, bit = 1, ri = 1 ; i < 32 ; ++i, bit <<= 1)
mjr 6:cc35eb643e8f 989 {
mjr 6:cc35eb643e8f 990 if (bit == 0x100) {
mjr 6:cc35eb643e8f 991 bit = 1;
mjr 6:cc35eb643e8f 992 ++ri;
mjr 6:cc35eb643e8f 993 }
mjr 6:cc35eb643e8f 994 wizOn[i] = ((data[ri] & bit) != 0);
mjr 6:cc35eb643e8f 995 }
mjr 6:cc35eb643e8f 996
mjr 6:cc35eb643e8f 997 // update the physical outputs
mjr 1:d913e0afb2ac 998 updateWizOuts();
mjr 6:cc35eb643e8f 999
mjr 6:cc35eb643e8f 1000 // reset the PBA counter
mjr 6:cc35eb643e8f 1001 pbaIdx = 0;
mjr 6:cc35eb643e8f 1002 }
mjr 6:cc35eb643e8f 1003 else if (data[0] == 65)
mjr 6:cc35eb643e8f 1004 {
mjr 6:cc35eb643e8f 1005 // Private control message. This isn't an LedWiz message - it's
mjr 6:cc35eb643e8f 1006 // an extension for this device. 65 is an invalid PBA setting,
mjr 6:cc35eb643e8f 1007 // and isn't used for any other LedWiz message, so we appropriate
mjr 6:cc35eb643e8f 1008 // it for our own private use. The first byte specifies the
mjr 6:cc35eb643e8f 1009 // message type.
mjr 6:cc35eb643e8f 1010 if (data[1] == 1)
mjr 6:cc35eb643e8f 1011 {
mjr 6:cc35eb643e8f 1012 // Set Configuration:
mjr 6:cc35eb643e8f 1013 // data[2] = LedWiz unit number (0x00 to 0x0f)
mjr 6:cc35eb643e8f 1014 // data[3] = feature enable bit mask:
mjr 6:cc35eb643e8f 1015 // 0x01 = enable CCD
mjr 6:cc35eb643e8f 1016
mjr 6:cc35eb643e8f 1017 // we'll need a reset if the LedWiz unit number is changing
mjr 6:cc35eb643e8f 1018 uint8_t newUnitNo = data[2] & 0x0f;
mjr 6:cc35eb643e8f 1019 needReset |= (newUnitNo != cfg.d.ledWizUnitNo);
mjr 6:cc35eb643e8f 1020
mjr 6:cc35eb643e8f 1021 // set the configuration parameters from the message
mjr 6:cc35eb643e8f 1022 cfg.d.ledWizUnitNo = newUnitNo;
mjr 6:cc35eb643e8f 1023 cfg.d.ccdEnabled = data[3] & 0x01;
mjr 6:cc35eb643e8f 1024
mjr 6:cc35eb643e8f 1025 // save the configuration
mjr 6:cc35eb643e8f 1026 cfg.save(iap, flash_addr);
mjr 6:cc35eb643e8f 1027 }
mjr 6:cc35eb643e8f 1028 }
mjr 6:cc35eb643e8f 1029 else
mjr 6:cc35eb643e8f 1030 {
mjr 6:cc35eb643e8f 1031 // LWZ-PBA - full state dump; each byte is one output
mjr 6:cc35eb643e8f 1032 // in the current bank. pbaIdx keeps track of the bank;
mjr 6:cc35eb643e8f 1033 // this is incremented implicitly by each PBA message.
mjr 6:cc35eb643e8f 1034 //printf("LWZ-PBA[%d] %02x %02x %02x %02x %02x %02x %02x %02x\r\n",
mjr 6:cc35eb643e8f 1035 // pbaIdx, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
mjr 6:cc35eb643e8f 1036
mjr 6:cc35eb643e8f 1037 // update all output profile settings
mjr 6:cc35eb643e8f 1038 for (int i = 0 ; i < 8 ; ++i)
mjr 6:cc35eb643e8f 1039 wizVal[pbaIdx + i] = data[i];
mjr 6:cc35eb643e8f 1040
mjr 6:cc35eb643e8f 1041 // update the physical LED state if this is the last bank
mjr 6:cc35eb643e8f 1042 if (pbaIdx == 24)
mjr 6:cc35eb643e8f 1043 updateWizOuts();
mjr 6:cc35eb643e8f 1044
mjr 6:cc35eb643e8f 1045 // advance to the next bank
mjr 6:cc35eb643e8f 1046 pbaIdx = (pbaIdx + 8) & 31;
mjr 6:cc35eb643e8f 1047 }
mjr 0:5acbbe3f4cf4 1048 }
mjr 0:5acbbe3f4cf4 1049 }
mjr 1:d913e0afb2ac 1050
mjr 1:d913e0afb2ac 1051 // check for plunger calibration
mjr 1:d913e0afb2ac 1052 if (!calBtn)
mjr 0:5acbbe3f4cf4 1053 {
mjr 1:d913e0afb2ac 1054 // check the state
mjr 1:d913e0afb2ac 1055 switch (calBtnState)
mjr 0:5acbbe3f4cf4 1056 {
mjr 1:d913e0afb2ac 1057 case 0:
mjr 1:d913e0afb2ac 1058 // button not yet pushed - start debouncing
mjr 1:d913e0afb2ac 1059 calBtnTimer.reset();
mjr 1:d913e0afb2ac 1060 calBtnDownTime = calBtnTimer.read_ms();
mjr 1:d913e0afb2ac 1061 calBtnState = 1;
mjr 1:d913e0afb2ac 1062 break;
mjr 1:d913e0afb2ac 1063
mjr 1:d913e0afb2ac 1064 case 1:
mjr 1:d913e0afb2ac 1065 // pushed, not yet debounced - if the debounce time has
mjr 1:d913e0afb2ac 1066 // passed, start the hold period
mjr 1:d913e0afb2ac 1067 if (calBtnTimer.read_ms() - calBtnDownTime > 50)
mjr 1:d913e0afb2ac 1068 calBtnState = 2;
mjr 1:d913e0afb2ac 1069 break;
mjr 1:d913e0afb2ac 1070
mjr 1:d913e0afb2ac 1071 case 2:
mjr 1:d913e0afb2ac 1072 // in the hold period - if the button has been held down
mjr 1:d913e0afb2ac 1073 // for the entire hold period, move to calibration mode
mjr 1:d913e0afb2ac 1074 if (calBtnTimer.read_ms() - calBtnDownTime > 2050)
mjr 1:d913e0afb2ac 1075 {
mjr 1:d913e0afb2ac 1076 // enter calibration mode
mjr 1:d913e0afb2ac 1077 calBtnState = 3;
mjr 1:d913e0afb2ac 1078
mjr 6:cc35eb643e8f 1079 // set extremes for the calibration data, so that the actual
mjr 6:cc35eb643e8f 1080 // values we read will set new high/low water marks on the fly
mjr 2:c174f9ee414a 1081 cfg.d.plungerMax = 0;
mjr 6:cc35eb643e8f 1082 cfg.d.plungerZero = npix;
mjr 2:c174f9ee414a 1083 cfg.d.plungerMin = npix;
mjr 1:d913e0afb2ac 1084 }
mjr 1:d913e0afb2ac 1085 break;
mjr 2:c174f9ee414a 1086
mjr 2:c174f9ee414a 1087 case 3:
mjr 2:c174f9ee414a 1088 // Already in calibration mode - pushing the button in this
mjr 2:c174f9ee414a 1089 // state doesn't change the current state, but we won't leave
mjr 2:c174f9ee414a 1090 // this state as long as it's held down. We can simply do
mjr 2:c174f9ee414a 1091 // nothing here.
mjr 2:c174f9ee414a 1092 break;
mjr 0:5acbbe3f4cf4 1093 }
mjr 0:5acbbe3f4cf4 1094 }
mjr 1:d913e0afb2ac 1095 else
mjr 1:d913e0afb2ac 1096 {
mjr 2:c174f9ee414a 1097 // Button released. If we're in calibration mode, and
mjr 2:c174f9ee414a 1098 // the calibration time has elapsed, end the calibration
mjr 2:c174f9ee414a 1099 // and save the results to flash.
mjr 2:c174f9ee414a 1100 //
mjr 2:c174f9ee414a 1101 // Otherwise, return to the base state without saving anything.
mjr 2:c174f9ee414a 1102 // If the button is released before we make it to calibration
mjr 2:c174f9ee414a 1103 // mode, it simply cancels the attempt.
mjr 2:c174f9ee414a 1104 if (calBtnState == 3
mjr 2:c174f9ee414a 1105 && calBtnTimer.read_ms() - calBtnDownTime > 17500)
mjr 2:c174f9ee414a 1106 {
mjr 2:c174f9ee414a 1107 // exit calibration mode
mjr 1:d913e0afb2ac 1108 calBtnState = 0;
mjr 2:c174f9ee414a 1109
mjr 6:cc35eb643e8f 1110 // save the updated configuration
mjr 6:cc35eb643e8f 1111 cfg.d.plungerCal = 1;
mjr 6:cc35eb643e8f 1112 cfg.save(iap, flash_addr);
mjr 2:c174f9ee414a 1113
mjr 2:c174f9ee414a 1114 // the flash state is now valid
mjr 2:c174f9ee414a 1115 flash_valid = true;
mjr 2:c174f9ee414a 1116 }
mjr 2:c174f9ee414a 1117 else if (calBtnState != 3)
mjr 2:c174f9ee414a 1118 {
mjr 2:c174f9ee414a 1119 // didn't make it to calibration mode - cancel the operation
mjr 1:d913e0afb2ac 1120 calBtnState = 0;
mjr 2:c174f9ee414a 1121 }
mjr 1:d913e0afb2ac 1122 }
mjr 1:d913e0afb2ac 1123
mjr 1:d913e0afb2ac 1124 // light/flash the calibration button light, if applicable
mjr 1:d913e0afb2ac 1125 int newCalBtnLit = calBtnLit;
mjr 1:d913e0afb2ac 1126 switch (calBtnState)
mjr 0:5acbbe3f4cf4 1127 {
mjr 1:d913e0afb2ac 1128 case 2:
mjr 1:d913e0afb2ac 1129 // in the hold period - flash the light
mjr 1:d913e0afb2ac 1130 newCalBtnLit = (((calBtnTimer.read_ms() - calBtnDownTime)/250) & 1);
mjr 1:d913e0afb2ac 1131 break;
mjr 1:d913e0afb2ac 1132
mjr 1:d913e0afb2ac 1133 case 3:
mjr 1:d913e0afb2ac 1134 // calibration mode - show steady on
mjr 1:d913e0afb2ac 1135 newCalBtnLit = true;
mjr 1:d913e0afb2ac 1136 break;
mjr 1:d913e0afb2ac 1137
mjr 1:d913e0afb2ac 1138 default:
mjr 1:d913e0afb2ac 1139 // not calibrating/holding - show steady off
mjr 1:d913e0afb2ac 1140 newCalBtnLit = false;
mjr 1:d913e0afb2ac 1141 break;
mjr 1:d913e0afb2ac 1142 }
mjr 3:3514575d4f86 1143
mjr 3:3514575d4f86 1144 // light or flash the external calibration button LED, and
mjr 3:3514575d4f86 1145 // do the same with the on-board blue LED
mjr 1:d913e0afb2ac 1146 if (calBtnLit != newCalBtnLit)
mjr 1:d913e0afb2ac 1147 {
mjr 1:d913e0afb2ac 1148 calBtnLit = newCalBtnLit;
mjr 2:c174f9ee414a 1149 if (calBtnLit) {
mjr 2:c174f9ee414a 1150 calBtnLed = 1;
mjr 4:02c7cd7b2183 1151 ledR = 1;
mjr 4:02c7cd7b2183 1152 ledG = 1;
mjr 4:02c7cd7b2183 1153 ledB = 1;
mjr 2:c174f9ee414a 1154 }
mjr 2:c174f9ee414a 1155 else {
mjr 2:c174f9ee414a 1156 calBtnLed = 0;
mjr 4:02c7cd7b2183 1157 ledR = 1;
mjr 4:02c7cd7b2183 1158 ledG = 1;
mjr 4:02c7cd7b2183 1159 ledB = 0;
mjr 2:c174f9ee414a 1160 }
mjr 1:d913e0afb2ac 1161 }
mjr 1:d913e0afb2ac 1162
mjr 6:cc35eb643e8f 1163 // read the plunger sensor, if it's enabled
mjr 6:cc35eb643e8f 1164 if (cfg.d.ccdEnabled)
mjr 6:cc35eb643e8f 1165 {
mjr 6:cc35eb643e8f 1166 // start with the previous reading, in case we don't have a
mjr 6:cc35eb643e8f 1167 // clear result on this frame
mjr 6:cc35eb643e8f 1168 int znew = z;
mjr 2:c174f9ee414a 1169
mjr 6:cc35eb643e8f 1170 // read the array
mjr 6:cc35eb643e8f 1171 uint16_t pix[npix];
mjr 6:cc35eb643e8f 1172 ccd.read(pix, npix);
mjr 6:cc35eb643e8f 1173
mjr 6:cc35eb643e8f 1174 // get the average brightness at each end of the sensor
mjr 6:cc35eb643e8f 1175 long avg1 = (long(pix[0]) + long(pix[1]) + long(pix[2]) + long(pix[3]) + long(pix[4]))/5;
mjr 6:cc35eb643e8f 1176 long avg2 = (long(pix[npix-1]) + long(pix[npix-2]) + long(pix[npix-3]) + long(pix[npix-4]) + long(pix[npix-5]))/5;
mjr 6:cc35eb643e8f 1177
mjr 6:cc35eb643e8f 1178 // figure the midpoint in the brightness; multiply by 3 so that we can
mjr 6:cc35eb643e8f 1179 // compare sums of three pixels at a time to smooth out noise
mjr 6:cc35eb643e8f 1180 long midpt = (avg1 + avg2)/2 * 3;
mjr 6:cc35eb643e8f 1181
mjr 6:cc35eb643e8f 1182 // Work from the bright end to the dark end. VP interprets the
mjr 6:cc35eb643e8f 1183 // Z axis value as the amount the plunger is pulled: zero is the
mjr 6:cc35eb643e8f 1184 // rest position, and the axis maximum is fully pulled. So we
mjr 6:cc35eb643e8f 1185 // essentially want to report how much of the sensor is lit,
mjr 6:cc35eb643e8f 1186 // since this increases as the plunger is pulled back.
mjr 6:cc35eb643e8f 1187 int si = 1, di = 1;
mjr 6:cc35eb643e8f 1188 if (avg1 < avg2)
mjr 6:cc35eb643e8f 1189 si = npix - 2, di = -1;
mjr 6:cc35eb643e8f 1190
mjr 6:cc35eb643e8f 1191 // If the bright end and dark end don't differ by enough, skip this
mjr 6:cc35eb643e8f 1192 // reading entirely - we must have an overexposed or underexposed frame.
mjr 6:cc35eb643e8f 1193 // Otherwise proceed with the scan.
mjr 6:cc35eb643e8f 1194 if (labs(avg1 - avg2) > 0x1000)
mjr 6:cc35eb643e8f 1195 {
mjr 6:cc35eb643e8f 1196 uint16_t *pixp = pix + si;
mjr 6:cc35eb643e8f 1197 for (int n = 1 ; n < npix - 1 ; ++n, pixp += di)
mjr 6:cc35eb643e8f 1198 {
mjr 6:cc35eb643e8f 1199 // if we've crossed the midpoint, report this position
mjr 6:cc35eb643e8f 1200 if (long(pixp[-1]) + long(pixp[0]) + long(pixp[1]) < midpt)
mjr 6:cc35eb643e8f 1201 {
mjr 6:cc35eb643e8f 1202 // note the new position
mjr 6:cc35eb643e8f 1203 int pos = n;
mjr 6:cc35eb643e8f 1204
mjr 6:cc35eb643e8f 1205 // Calibrate, or apply calibration, depending on the mode.
mjr 6:cc35eb643e8f 1206 // In either case, normalize to our range. VP appears to
mjr 6:cc35eb643e8f 1207 // ignore negative Z axis values.
mjr 6:cc35eb643e8f 1208 if (calBtnState == 3)
mjr 6:cc35eb643e8f 1209 {
mjr 6:cc35eb643e8f 1210 // calibrating - note if we're expanding the calibration envelope
mjr 6:cc35eb643e8f 1211 if (pos < cfg.d.plungerMin)
mjr 6:cc35eb643e8f 1212 cfg.d.plungerMin = pos;
mjr 6:cc35eb643e8f 1213 if (pos < cfg.d.plungerZero)
mjr 6:cc35eb643e8f 1214 cfg.d.plungerZero = pos;
mjr 6:cc35eb643e8f 1215 if (pos > cfg.d.plungerMax)
mjr 6:cc35eb643e8f 1216 cfg.d.plungerMax = pos;
mjr 6:cc35eb643e8f 1217
mjr 6:cc35eb643e8f 1218 // normalize to the full physical range while calibrating
mjr 6:cc35eb643e8f 1219 znew = int(round(float(pos)/npix * JOYMAX));
mjr 6:cc35eb643e8f 1220 }
mjr 6:cc35eb643e8f 1221 else
mjr 6:cc35eb643e8f 1222 {
mjr 6:cc35eb643e8f 1223 // Running normally - normalize to the calibration range. Note
mjr 6:cc35eb643e8f 1224 // that values below the zero point are allowed - the zero point
mjr 6:cc35eb643e8f 1225 // represents the park position, where the plunger sits when at
mjr 6:cc35eb643e8f 1226 // rest, but a mechanical plunger has a smmall amount of travel
mjr 6:cc35eb643e8f 1227 // in the "push" direction. We represent forward travel with
mjr 6:cc35eb643e8f 1228 // negative z values.
mjr 6:cc35eb643e8f 1229 if (pos > cfg.d.plungerMax)
mjr 6:cc35eb643e8f 1230 pos = cfg.d.plungerMax;
mjr 6:cc35eb643e8f 1231 znew = int(round(float(pos - cfg.d.plungerZero)
mjr 6:cc35eb643e8f 1232 / (cfg.d.plungerMax - cfg.d.plungerZero + 1) * JOYMAX));
mjr 6:cc35eb643e8f 1233 }
mjr 6:cc35eb643e8f 1234
mjr 6:cc35eb643e8f 1235 // done
mjr 6:cc35eb643e8f 1236 break;
mjr 6:cc35eb643e8f 1237 }
mjr 6:cc35eb643e8f 1238 }
mjr 6:cc35eb643e8f 1239 }
mjr 2:c174f9ee414a 1240
mjr 6:cc35eb643e8f 1241 // "Debounce" the plunger reading.
mjr 6:cc35eb643e8f 1242 //
mjr 6:cc35eb643e8f 1243 // It takes us about 25ms to read the CCD and calculate the new
mjr 6:cc35eb643e8f 1244 // plunger position. That's not quite fast enough to keep up with
mjr 6:cc35eb643e8f 1245 // very fast plunger motions. And the single most important motion
mjr 6:cc35eb643e8f 1246 // the plunger makes - releasing from a retracted position it to
mjr 6:cc35eb643e8f 1247 // launch the ball - is just such a fast motion. Our scan rate is
mjr 6:cc35eb643e8f 1248 // fast enough to capture one or two intermediate frames in a release
mjr 6:cc35eb643e8f 1249 // motion, but it's not nearly fast enough to get a clean reading on
mjr 6:cc35eb643e8f 1250 // the instantaneous speed, let alone accelerations.
mjr 6:cc35eb643e8f 1251 //
mjr 6:cc35eb643e8f 1252 // Fortunately, we don't need to take speed readings at all. VP has
mjr 6:cc35eb643e8f 1253 // its own internal simulated plunger model, which it uses to calculate
mjr 6:cc35eb643e8f 1254 // the speed and force of the plunger movement. Our readings tell VP
mjr 6:cc35eb643e8f 1255 // where the plunger should be at any given moment, and VP makes its
mjr 6:cc35eb643e8f 1256 // model move in that direction, using the model parameters for speed
mjr 6:cc35eb643e8f 1257 // and acceleration. So whatever speed we see physically is irrelevant;
mjr 6:cc35eb643e8f 1258 // the VP model plunger can only move at the speed set in its model.
mjr 6:cc35eb643e8f 1259 //
mjr 6:cc35eb643e8f 1260 // This works out great for our relatively slow scan rate. We don't
mjr 6:cc35eb643e8f 1261 // have to take readings quickly enough to get instantaneous velocities;
mjr 6:cc35eb643e8f 1262 // we just need to know where the plunger is once in a while so that
mjr 6:cc35eb643e8f 1263 // VP can move its model plunger in the right direction for the right
mjr 6:cc35eb643e8f 1264 // distance, and VP figures out the appropriate speed for the required
mjr 6:cc35eb643e8f 1265 // travel.
mjr 6:cc35eb643e8f 1266 //
mjr 6:cc35eb643e8f 1267 // But there is one complication. We do scan fast enough to see *some*
mjr 6:cc35eb643e8f 1268 // intermediate positions during a fast motion. Suppose that on one
mjr 6:cc35eb643e8f 1269 // scan, the plunger is fully retracted. Now suppose that the player
mjr 6:cc35eb643e8f 1270 // releases the plunger just after that scan, such that our next scan
mjr 6:cc35eb643e8f 1271 // catches the plunger *almost* back to the rest position, but not
mjr 6:cc35eb643e8f 1272 // quite - just a hair short. If we send these two consecutive reports
mjr 6:cc35eb643e8f 1273 // to VP, VP will set its model plunger in motion with the *almost*
mjr 6:cc35eb643e8f 1274 // reading as the destination. VP will step its physics model with
mjr 6:cc35eb643e8f 1275 // this new plunger destination until we send another reading.
mjr 6:cc35eb643e8f 1276 // Ddpending on how the timing of our next scan works out, it's
mjr 6:cc35eb643e8f 1277 // possible that the model plunger will have reached or almost reached
mjr 6:cc35eb643e8f 1278 // the destination by the time we send our next report - so VP might
mjr 6:cc35eb643e8f 1279 // be decelerating or stopping the model plunger as it approaches
mjr 6:cc35eb643e8f 1280 // this position. Our next scan will probably find the plunger back
mjr 6:cc35eb643e8f 1281 // at the rest position, so we'll tell VP to continue moving the
mjr 6:cc35eb643e8f 1282 // plunger to the zero spot. The problem that just happened is that
mjr 6:cc35eb643e8f 1283 // our intermediate *almost there* report might have robbed the
mjr 6:cc35eb643e8f 1284 // motion in the model of some energy that should have been there,
mjr 6:cc35eb643e8f 1285 // by causing it to decelerate briefly near the intermediate position.
mjr 6:cc35eb643e8f 1286 //
mjr 6:cc35eb643e8f 1287 // This is relatively easy to fix. Because VP does all of the fast
mjr 6:cc35eb643e8f 1288 // motion modeling on its own anyway, there's no advantage to sending
mjr 6:cc35eb643e8f 1289 // VP intermediate positions during rapid motions - and there's the
mjr 6:cc35eb643e8f 1290 // disadvantage we just described. So all we need to do is skip
mjr 6:cc35eb643e8f 1291 // reports while the plunger is moving rapidly - we just need to wait
mjr 6:cc35eb643e8f 1292 // for it to settle at a new position before sending an update.
mjr 6:cc35eb643e8f 1293 //
mjr 6:cc35eb643e8f 1294 // So: only report the latest reading if it's relatively close to the
mjr 6:cc35eb643e8f 1295 // previous reading, indicating we're moving slowly or at rest. One
mjr 6:cc35eb643e8f 1296 // exception: if we see a reversal of direction, report the previous
mjr 6:cc35eb643e8f 1297 // reading, which is the peak in the previous direction. This will
mjr 6:cc35eb643e8f 1298 // catch cases where the player is moving the plunger very rapidly
mjr 6:cc35eb643e8f 1299 // back and forth, as well as release motions where the plunger
mjr 6:cc35eb643e8f 1300 // briefly overshoots the rest position.
mjr 6:cc35eb643e8f 1301 #if 1
mjr 6:cc35eb643e8f 1302 // Check to see if plunger firing is in progress. If not, check
mjr 6:cc35eb643e8f 1303 // to see if it looks like we just started firing.
mjr 6:cc35eb643e8f 1304 const int restTol = JOYMAX/npix * 4;
mjr 6:cc35eb643e8f 1305 const int fireTol = JOYMAX/npix * 12;
mjr 6:cc35eb643e8f 1306 if (firing)
mjr 6:cc35eb643e8f 1307 {
mjr 6:cc35eb643e8f 1308 // Firing in progress - we've already told VP to send its
mjr 6:cc35eb643e8f 1309 // model plunger all the way back to the rest position, so
mjr 6:cc35eb643e8f 1310 // send no further reports until the mechanical plunger
mjr 6:cc35eb643e8f 1311 // actually comes to rest somewhere.
mjr 6:cc35eb643e8f 1312 if (abs(z0 - z2) < restTol && abs(znew - z2) < restTol)
mjr 6:cc35eb643e8f 1313 {
mjr 6:cc35eb643e8f 1314 // the plunger is back at rest - firing is done
mjr 6:cc35eb643e8f 1315 firing = false;
mjr 6:cc35eb643e8f 1316
mjr 6:cc35eb643e8f 1317 // resume normal reporting
mjr 6:cc35eb643e8f 1318 z = z2;
mjr 6:cc35eb643e8f 1319 }
mjr 6:cc35eb643e8f 1320 }
mjr 6:cc35eb643e8f 1321 else if (z0 < z2 && z1 < z2 && znew < z2
mjr 6:cc35eb643e8f 1322 && (z0 < z2 - fireTol
mjr 6:cc35eb643e8f 1323 || z1 < z2 - fireTol
mjr 6:cc35eb643e8f 1324 || znew < z2 - fireTol))
mjr 6:cc35eb643e8f 1325 {
mjr 6:cc35eb643e8f 1326 // Big jumps toward rest position in last two readings -
mjr 6:cc35eb643e8f 1327 // firing has begun. Report an immediate return to the
mjr 6:cc35eb643e8f 1328 // rest position, and send no further reports until the
mjr 6:cc35eb643e8f 1329 // physical plunger has come to rest. This effectively
mjr 6:cc35eb643e8f 1330 // detaches VP's model plunger from the real world for
mjr 6:cc35eb643e8f 1331 // the duration of the spring return, letting VP evolve
mjr 6:cc35eb643e8f 1332 // its model without trying to synchronize with the
mjr 6:cc35eb643e8f 1333 // mechanical version. The release motion is too fast
mjr 6:cc35eb643e8f 1334 // for that to work well; we can't take samples quickly
mjr 6:cc35eb643e8f 1335 // enough to get prcise velocity or acceleration
mjr 6:cc35eb643e8f 1336 // readings. It's better to let VP figure the speed
mjr 6:cc35eb643e8f 1337 // and acceleration through modeling. Plus, that lets
mjr 6:cc35eb643e8f 1338 // each virtual table set the desired parameters for its
mjr 6:cc35eb643e8f 1339 // virtual plunger, rather than imposing the actual
mjr 6:cc35eb643e8f 1340 // mechanical charateristics of the physical plunger on
mjr 6:cc35eb643e8f 1341 // every table.
mjr 6:cc35eb643e8f 1342 firing = true;
mjr 6:cc35eb643e8f 1343 z = 0;
mjr 6:cc35eb643e8f 1344 }
mjr 6:cc35eb643e8f 1345 else
mjr 6:cc35eb643e8f 1346 {
mjr 6:cc35eb643e8f 1347 // everything normal; report the 3rd recent position on
mjr 6:cc35eb643e8f 1348 // tape delay
mjr 6:cc35eb643e8f 1349 z = z2;
mjr 6:cc35eb643e8f 1350 }
mjr 6:cc35eb643e8f 1351
mjr 6:cc35eb643e8f 1352 // shift in the new reading
mjr 6:cc35eb643e8f 1353 z2 = z1;
mjr 6:cc35eb643e8f 1354 z1 = z0;
mjr 6:cc35eb643e8f 1355 z0 = znew;
mjr 6:cc35eb643e8f 1356 #endif
mjr 2:c174f9ee414a 1357
mjr 6:cc35eb643e8f 1358
mjr 6:cc35eb643e8f 1359 #if 0
mjr 6:cc35eb643e8f 1360 // check for the anomalous fast return case, where we get two
mjr 6:cc35eb643e8f 1361 // descending readings out of order
mjr 6:cc35eb643e8f 1362 if (znew < z1
mjr 6:cc35eb643e8f 1363 && z0 < z1
mjr 6:cc35eb643e8f 1364 && znew > z0
mjr 6:cc35eb643e8f 1365 && abs(znew - z1) > JOYMAX/npix*3
mjr 6:cc35eb643e8f 1366 && abs(z0 - z1) > JOYMAX/npix*3)
mjr 1:d913e0afb2ac 1367 {
mjr 6:cc35eb643e8f 1368 // drop the middle reading - report nothing this round
mjr 6:cc35eb643e8f 1369 z0 = znew;
mjr 6:cc35eb643e8f 1370 }
mjr 6:cc35eb643e8f 1371 else
mjr 6:cc35eb643e8f 1372 {
mjr 6:cc35eb643e8f 1373 // report the previous reading
mjr 6:cc35eb643e8f 1374 z = z0;
mjr 2:c174f9ee414a 1375
mjr 6:cc35eb643e8f 1376 // shift in the new reading
mjr 6:cc35eb643e8f 1377 z1 = z0;
mjr 6:cc35eb643e8f 1378 z0 = znew;
mjr 6:cc35eb643e8f 1379 }
mjr 6:cc35eb643e8f 1380 #endif
mjr 6:cc35eb643e8f 1381 #if 0
mjr 6:cc35eb643e8f 1382 static int insertion = -1;
mjr 6:cc35eb643e8f 1383 static int insertionList[] = { 0, 400, 800, 1200, 1600, 2000, 2400, 2800, 3200 };
mjr 6:cc35eb643e8f 1384 static int overcnt = 0;
mjr 6:cc35eb643e8f 1385 if (insertion >= 0)
mjr 6:cc35eb643e8f 1386 z = insertionList[insertion--];
mjr 6:cc35eb643e8f 1387 else if (znew > 3500 && z == 0)
mjr 6:cc35eb643e8f 1388 z = 3500, overcnt = 1;
mjr 6:cc35eb643e8f 1389 else if (znew > 3500)
mjr 6:cc35eb643e8f 1390 ++overcnt;
mjr 6:cc35eb643e8f 1391 else if (znew < 3500 && overcnt > 3)
mjr 6:cc35eb643e8f 1392 insertion = sizeof(insertionList)/sizeof(insertionList[0]) - 1, z = 3500, overcnt = 0;
mjr 6:cc35eb643e8f 1393 else
mjr 6:cc35eb643e8f 1394 overcnt = 0, z = 0;
mjr 6:cc35eb643e8f 1395 #endif
mjr 6:cc35eb643e8f 1396 #if 0
mjr 6:cc35eb643e8f 1397 if (znew != z) printf("%d\r\n", znew);
mjr 6:cc35eb643e8f 1398 z = znew;
mjr 6:cc35eb643e8f 1399 #endif
mjr 6:cc35eb643e8f 1400 #if 0
mjr 6:cc35eb643e8f 1401 // average the last three readings
mjr 6:cc35eb643e8f 1402 z = int(round(0.0f + znew + z0 + z1)/3.0f);
mjr 6:cc35eb643e8f 1403
mjr 6:cc35eb643e8f 1404 // shift in the new reading
mjr 6:cc35eb643e8f 1405 z1 = z0;
mjr 6:cc35eb643e8f 1406 z0 = znew;
mjr 6:cc35eb643e8f 1407 #endif
mjr 6:cc35eb643e8f 1408 #if 0
mjr 6:cc35eb643e8f 1409 const int zTol = JOYMAX/npix*5;
mjr 6:cc35eb643e8f 1410 if (abs(znew - z0) < zTol && abs(z0 - z1) < zTol)
mjr 6:cc35eb643e8f 1411 {
mjr 6:cc35eb643e8f 1412 // slow or at rest - report the current reading
mjr 6:cc35eb643e8f 1413 z = znew;
mjr 6:cc35eb643e8f 1414 }
mjr 6:cc35eb643e8f 1415 else if ((z0 < z1 && znew > z0) || (z0 > z1 && znew < z0))
mjr 6:cc35eb643e8f 1416 {
mjr 6:cc35eb643e8f 1417 // direction reveersal - report the peak reading
mjr 6:cc35eb643e8f 1418 z = z0;
mjr 6:cc35eb643e8f 1419 }
mjr 2:c174f9ee414a 1420
mjr 6:cc35eb643e8f 1421 // in any case, remember this new reading, whether reporting it or not
mjr 6:cc35eb643e8f 1422 z1 = z0;
mjr 6:cc35eb643e8f 1423 z0 = znew;
mjr 6:cc35eb643e8f 1424 #endif
mjr 2:c174f9ee414a 1425 }
mjr 6:cc35eb643e8f 1426
mjr 1:d913e0afb2ac 1427 // read the accelerometer
mjr 6:cc35eb643e8f 1428 int xa, ya, rxa, rya;
mjr 3:3514575d4f86 1429 accel.get(xa, ya, rxa, rya);
mjr 1:d913e0afb2ac 1430
mjr 6:cc35eb643e8f 1431 // confine the results to our joystick axis range
mjr 6:cc35eb643e8f 1432 if (xa < -JOYMAX) xa = -JOYMAX;
mjr 6:cc35eb643e8f 1433 if (xa > JOYMAX) xa = JOYMAX;
mjr 6:cc35eb643e8f 1434 if (ya < -JOYMAX) ya = -JOYMAX;
mjr 6:cc35eb643e8f 1435 if (ya > JOYMAX) ya = JOYMAX;
mjr 1:d913e0afb2ac 1436
mjr 6:cc35eb643e8f 1437 // store the updated accelerometer coordinates
mjr 6:cc35eb643e8f 1438 x = xa;
mjr 6:cc35eb643e8f 1439 y = ya;
mjr 6:cc35eb643e8f 1440
mjr 6:cc35eb643e8f 1441 // Send the status report.
mjr 5:a70c0bce770d 1442 //
mjr 5:a70c0bce770d 1443 // $$$ button updates are for diagnostics, so we can see that the
mjr 5:a70c0bce770d 1444 // device is sending data properly if the accelerometer gets stuck
mjr 6:cc35eb643e8f 1445 uint16_t btns = hb ? 0x5500 : 0xAA00;
mjr 6:cc35eb643e8f 1446 js.update(x, y, z, rxa, rya, btns);
mjr 1:d913e0afb2ac 1447
mjr 6:cc35eb643e8f 1448 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 1449 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 1450 printf("%d,%d\r\n", x, y);
mjr 6:cc35eb643e8f 1451 #endif
mjr 6:cc35eb643e8f 1452
mjr 6:cc35eb643e8f 1453 // provide a visual status indication on the on-board LED
mjr 5:a70c0bce770d 1454 if (calBtnState < 2 && hbTimer.read_ms() > 1000)
mjr 1:d913e0afb2ac 1455 {
mjr 5:a70c0bce770d 1456 if (js.isSuspended() || !js.isConnected())
mjr 2:c174f9ee414a 1457 {
mjr 5:a70c0bce770d 1458 // suspended - turn off the LED
mjr 4:02c7cd7b2183 1459 ledR = 1;
mjr 4:02c7cd7b2183 1460 ledG = 1;
mjr 4:02c7cd7b2183 1461 ledB = 1;
mjr 5:a70c0bce770d 1462
mjr 5:a70c0bce770d 1463 // show a status flash every so often
mjr 5:a70c0bce770d 1464 if (hbcnt % 3 == 0)
mjr 5:a70c0bce770d 1465 {
mjr 6:cc35eb643e8f 1466 // disconnected = red/red flash; suspended = red
mjr 5:a70c0bce770d 1467 for (int n = js.isConnected() ? 1 : 2 ; n > 0 ; --n)
mjr 5:a70c0bce770d 1468 {
mjr 5:a70c0bce770d 1469 ledR = 0;
mjr 5:a70c0bce770d 1470 wait(0.05);
mjr 5:a70c0bce770d 1471 ledR = 1;
mjr 5:a70c0bce770d 1472 wait(0.25);
mjr 5:a70c0bce770d 1473 }
mjr 5:a70c0bce770d 1474 }
mjr 2:c174f9ee414a 1475 }
mjr 6:cc35eb643e8f 1476 else if (needReset)
mjr 2:c174f9ee414a 1477 {
mjr 6:cc35eb643e8f 1478 // connected, need to reset due to changes in config parameters -
mjr 6:cc35eb643e8f 1479 // flash red/green
mjr 6:cc35eb643e8f 1480 hb = !hb;
mjr 6:cc35eb643e8f 1481 ledR = (hb ? 0 : 1);
mjr 6:cc35eb643e8f 1482 ledG = (hb ? 1 : 0);
mjr 6:cc35eb643e8f 1483 ledB = 0;
mjr 6:cc35eb643e8f 1484 }
mjr 6:cc35eb643e8f 1485 else if (cfg.d.ccdEnabled && !cfg.d.plungerCal)
mjr 6:cc35eb643e8f 1486 {
mjr 6:cc35eb643e8f 1487 // connected, plunger calibration needed - flash yellow/green
mjr 6:cc35eb643e8f 1488 hb = !hb;
mjr 6:cc35eb643e8f 1489 ledR = (hb ? 0 : 1);
mjr 6:cc35eb643e8f 1490 ledG = 0;
mjr 6:cc35eb643e8f 1491 ledB = 1;
mjr 6:cc35eb643e8f 1492 }
mjr 6:cc35eb643e8f 1493 else
mjr 6:cc35eb643e8f 1494 {
mjr 6:cc35eb643e8f 1495 // connected - flash blue/green
mjr 2:c174f9ee414a 1496 hb = !hb;
mjr 4:02c7cd7b2183 1497 ledR = 1;
mjr 4:02c7cd7b2183 1498 ledG = (hb ? 0 : 1);
mjr 4:02c7cd7b2183 1499 ledB = (hb ? 1 : 0);
mjr 2:c174f9ee414a 1500 }
mjr 1:d913e0afb2ac 1501
mjr 1:d913e0afb2ac 1502 // reset the heartbeat timer
mjr 1:d913e0afb2ac 1503 hbTimer.reset();
mjr 5:a70c0bce770d 1504 ++hbcnt;
mjr 1:d913e0afb2ac 1505 }
mjr 1:d913e0afb2ac 1506 }
mjr 0:5acbbe3f4cf4 1507 }