work in progress

Dependencies:   FastAnalogIn FastIO USBDevice mbed FastPWM SimpleDMA

Fork of Pinscape_Controller by Mike R

Committer:
mjr
Date:
Fri Feb 27 04:14:04 2015 +0000
Revision:
17:ab3cec0c8bf4
Parent:
16:c35f905c3311
Child:
18:5e890ebd0023
FastIO and FastAnalogIn; better firing event sensing; potentiometer plunger sensor option; new key debouncing; ZB Launch Ball feature

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 6:cc35eb643e8f 140 // The on-board LED on the KL25Z flashes to indicate the current device status:
mjr 6:cc35eb643e8f 141 //
mjr 6:cc35eb643e8f 142 // two short red flashes = the device is powered but hasn't successfully
mjr 6:cc35eb643e8f 143 // connected to the host via USB (either it's not physically connected
mjr 6:cc35eb643e8f 144 // to the USB port, or there was a problem with the software handshake
mjr 6:cc35eb643e8f 145 // with the USB device driver on the computer)
mjr 6:cc35eb643e8f 146 //
mjr 6:cc35eb643e8f 147 // short red flash = the host computer is in sleep/suspend mode
mjr 6:cc35eb643e8f 148 //
mjr 6:cc35eb643e8f 149 // long red/green = the LedWiz unti number has been changed, so a reset
mjr 6:cc35eb643e8f 150 // is needed. You can simply unplug the device and plug it back in,
mjr 6:cc35eb643e8f 151 // or presss and hold the reset button on the device for a few seconds.
mjr 6:cc35eb643e8f 152 //
mjr 6:cc35eb643e8f 153 // long yellow/green = everything's working, but the plunger hasn't
mjr 6:cc35eb643e8f 154 // been calibrated; follow the calibration procedure described above.
mjr 6:cc35eb643e8f 155 // This flash mode won't appear if the CCD has been disabled. Note
mjr 6:cc35eb643e8f 156 // that the device can't tell whether a CCD is physically attached,
mjr 6:cc35eb643e8f 157 // so you should use the config command to disable the CCD software
mjr 6:cc35eb643e8f 158 // features if you won't be attaching a CCD.
mjr 6:cc35eb643e8f 159 //
mjr 6:cc35eb643e8f 160 // alternating blue/green = everything's working
mjr 6:cc35eb643e8f 161 //
mjr 6:cc35eb643e8f 162 // Software configuration: you can change option settings by sending special
mjr 6:cc35eb643e8f 163 // USB commands from the PC. I've provided a Windows program for this purpose;
mjr 6:cc35eb643e8f 164 // refer to the documentation for details. For reference, here's the format
mjr 6:cc35eb643e8f 165 // of the USB command for option changes:
mjr 6:cc35eb643e8f 166 //
mjr 6:cc35eb643e8f 167 // length of report = 8 bytes
mjr 6:cc35eb643e8f 168 // byte 0 = 65 (0x41)
mjr 6:cc35eb643e8f 169 // byte 1 = 1 (0x01)
mjr 6:cc35eb643e8f 170 // byte 2 = new LedWiz unit number, 0x01 to 0x0f
mjr 6:cc35eb643e8f 171 // byte 3 = feature enable bit mask:
mjr 6:cc35eb643e8f 172 // 0x01 = enable CCD (default = on)
mjr 9:fd65b0a94720 173 //
mjr 9:fd65b0a94720 174 // Plunger calibration mode: the host can activate plunger calibration mode
mjr 9:fd65b0a94720 175 // by sending this packet. This has the same effect as pressing and holding
mjr 9:fd65b0a94720 176 // the plunger calibration button for two seconds, to allow activating this
mjr 9:fd65b0a94720 177 // mode without attaching a physical button.
mjr 9:fd65b0a94720 178 //
mjr 9:fd65b0a94720 179 // length = 8 bytes
mjr 9:fd65b0a94720 180 // byte 0 = 65 (0x41)
mjr 9:fd65b0a94720 181 // byte 1 = 2 (0x02)
mjr 9:fd65b0a94720 182 //
mjr 10:976666ffa4ef 183 // Exposure reports: the host can request a report of the full set of pixel
mjr 10:976666ffa4ef 184 // values for the next frame by sending this special packet:
mjr 10:976666ffa4ef 185 //
mjr 10:976666ffa4ef 186 // length = 8 bytes
mjr 10:976666ffa4ef 187 // byte 0 = 65 (0x41)
mjr 10:976666ffa4ef 188 // byte 1 = 3 (0x03)
mjr 10:976666ffa4ef 189 //
mjr 10:976666ffa4ef 190 // We'll respond with a series of special reports giving the exposure status.
mjr 10:976666ffa4ef 191 // Each report has the following structure:
mjr 10:976666ffa4ef 192 //
mjr 10:976666ffa4ef 193 // bytes 0:1 = 11-bit index, with high 5 bits set to 10000. For
mjr 10:976666ffa4ef 194 // example, 0x04 0x80 indicates index 4. This is the
mjr 10:976666ffa4ef 195 // starting pixel number in the report. The first report
mjr 10:976666ffa4ef 196 // will be 0x00 0x80 to indicate pixel #0.
mjr 10:976666ffa4ef 197 // bytes 2:3 = 16-bit unsigned int brightness level of pixel at index
mjr 10:976666ffa4ef 198 // bytes 4:5 = brightness of pixel at index+1
mjr 10:976666ffa4ef 199 // etc for the rest of the packet
mjr 10:976666ffa4ef 200 //
mjr 10:976666ffa4ef 201 // This still has the form of a joystick packet at the USB level, but
mjr 10:976666ffa4ef 202 // can be differentiated by the host via the status bits. It would have
mjr 10:976666ffa4ef 203 // been cleaner to use a different Report ID at the USB level, but this
mjr 10:976666ffa4ef 204 // would have necessitated a different container structure in the report
mjr 10:976666ffa4ef 205 // descriptor, which would have broken LedWiz compatibility. Given that
mjr 10:976666ffa4ef 206 // constraint, we have to re-use the joystick report type, making for
mjr 10:976666ffa4ef 207 // this somewhat kludgey approach.
mjr 6:cc35eb643e8f 208
mjr 0:5acbbe3f4cf4 209 #include "mbed.h"
mjr 6:cc35eb643e8f 210 #include "math.h"
mjr 0:5acbbe3f4cf4 211 #include "USBJoystick.h"
mjr 0:5acbbe3f4cf4 212 #include "MMA8451Q.h"
mjr 1:d913e0afb2ac 213 #include "tsl1410r.h"
mjr 1:d913e0afb2ac 214 #include "FreescaleIAP.h"
mjr 2:c174f9ee414a 215 #include "crc32.h"
mjr 2:c174f9ee414a 216
mjr 17:ab3cec0c8bf4 217 // our local configuration file
mjr 17:ab3cec0c8bf4 218 #include "config.h"
mjr 17:ab3cec0c8bf4 219
mjr 5:a70c0bce770d 220
mjr 5:a70c0bce770d 221 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 222 // utilities
mjr 17:ab3cec0c8bf4 223
mjr 17:ab3cec0c8bf4 224 // number of elements in an array
mjr 17:ab3cec0c8bf4 225 #define countof(x) (sizeof(x)/sizeof((x)[0]))
mjr 17:ab3cec0c8bf4 226
mjr 17:ab3cec0c8bf4 227
mjr 17:ab3cec0c8bf4 228 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 229 // USB device vendor ID, product ID, and version.
mjr 5:a70c0bce770d 230 //
mjr 5:a70c0bce770d 231 // We use the vendor ID for the LedWiz, so that the PC-side software can
mjr 5:a70c0bce770d 232 // identify us as capable of performing LedWiz commands. The LedWiz uses
mjr 5:a70c0bce770d 233 // a product ID value from 0xF0 to 0xFF; the last four bits identify the
mjr 5:a70c0bce770d 234 // unit number (e.g., product ID 0xF7 means unit #7). This allows multiple
mjr 5:a70c0bce770d 235 // LedWiz units to be installed in a single PC; the software on the PC side
mjr 5:a70c0bce770d 236 // uses the unit number to route commands to the devices attached to each
mjr 5:a70c0bce770d 237 // unit. On the real LedWiz, the unit number must be set in the firmware
mjr 5:a70c0bce770d 238 // at the factory; it's not configurable by the end user. Most LedWiz's
mjr 5:a70c0bce770d 239 // ship with the unit number set to 0, but the vendor will set different
mjr 5:a70c0bce770d 240 // unit numbers if requested at the time of purchase. So if you have a
mjr 5:a70c0bce770d 241 // single LedWiz already installed in your cabinet, and you didn't ask for
mjr 5:a70c0bce770d 242 // a non-default unit number, your existing LedWiz will be unit 0.
mjr 5:a70c0bce770d 243 //
mjr 6:cc35eb643e8f 244 // Note that the USB_PRODUCT_ID value set here omits the unit number. We
mjr 6:cc35eb643e8f 245 // take the unit number from the saved configuration. We provide a
mjr 6:cc35eb643e8f 246 // configuration command that can be sent via the USB connection to change
mjr 6:cc35eb643e8f 247 // the unit number, so that users can select the unit number without having
mjr 6:cc35eb643e8f 248 // to install a different version of the software. We'll combine the base
mjr 6:cc35eb643e8f 249 // product ID here with the unit number to get the actual product ID that
mjr 6:cc35eb643e8f 250 // we send to the USB controller.
mjr 5:a70c0bce770d 251 const uint16_t USB_VENDOR_ID = 0xFAFA;
mjr 6:cc35eb643e8f 252 const uint16_t USB_PRODUCT_ID = 0x00F0;
mjr 6:cc35eb643e8f 253 const uint16_t USB_VERSION_NO = 0x0006;
mjr 0:5acbbe3f4cf4 254
mjr 5:a70c0bce770d 255
mjr 6:cc35eb643e8f 256 // Joystick axis report range - we report from -JOYMAX to +JOYMAX
mjr 6:cc35eb643e8f 257 #define JOYMAX 4096
mjr 6:cc35eb643e8f 258
mjr 5:a70c0bce770d 259
mjr 17:ab3cec0c8bf4 260 // --------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 261 //
mjr 17:ab3cec0c8bf4 262 // Potentiometer configuration
mjr 17:ab3cec0c8bf4 263 //
mjr 17:ab3cec0c8bf4 264 #ifdef POT_SENSOR_ENABLED
mjr 17:ab3cec0c8bf4 265 #define IF_POT(x) x
mjr 17:ab3cec0c8bf4 266 #else
mjr 17:ab3cec0c8bf4 267 #define IF_POT(x)
mjr 17:ab3cec0c8bf4 268 #endif
mjr 9:fd65b0a94720 269
mjr 17:ab3cec0c8bf4 270
mjr 17:ab3cec0c8bf4 271 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 272 //
mjr 17:ab3cec0c8bf4 273 // On-board RGB LED elements - we use these for diagnostic displays.
mjr 17:ab3cec0c8bf4 274 //
mjr 17:ab3cec0c8bf4 275 DigitalOut ledR(LED1), ledG(LED2), ledB(LED3);
mjr 17:ab3cec0c8bf4 276
mjr 9:fd65b0a94720 277
mjr 9:fd65b0a94720 278 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 279 //
mjr 5:a70c0bce770d 280 // LedWiz emulation
mjr 5:a70c0bce770d 281 //
mjr 5:a70c0bce770d 282
mjr 0:5acbbe3f4cf4 283 static int pbaIdx = 0;
mjr 0:5acbbe3f4cf4 284
mjr 6:cc35eb643e8f 285 // LedWiz output pin interface. We create a cover class to virtualize
mjr 6:cc35eb643e8f 286 // digital vs PWM outputs and give them a common interface. The KL25Z
mjr 6:cc35eb643e8f 287 // unfortunately doesn't have enough hardware PWM channels to support
mjr 6:cc35eb643e8f 288 // PWM on all 32 LedWiz outputs, so we provide as many PWM channels as
mjr 6:cc35eb643e8f 289 // we can (10), and fill out the rest of the outputs with plain digital
mjr 6:cc35eb643e8f 290 // outs.
mjr 6:cc35eb643e8f 291 class LwOut
mjr 6:cc35eb643e8f 292 {
mjr 6:cc35eb643e8f 293 public:
mjr 6:cc35eb643e8f 294 virtual void set(float val) = 0;
mjr 6:cc35eb643e8f 295 };
mjr 6:cc35eb643e8f 296 class LwPwmOut: public LwOut
mjr 6:cc35eb643e8f 297 {
mjr 6:cc35eb643e8f 298 public:
mjr 13:72dda449c3c0 299 LwPwmOut(PinName pin) : p(pin) { prv = -1; }
mjr 13:72dda449c3c0 300 virtual void set(float val)
mjr 13:72dda449c3c0 301 {
mjr 13:72dda449c3c0 302 if (val != prv)
mjr 13:72dda449c3c0 303 p.write(prv = val);
mjr 13:72dda449c3c0 304 }
mjr 6:cc35eb643e8f 305 PwmOut p;
mjr 13:72dda449c3c0 306 float prv;
mjr 6:cc35eb643e8f 307 };
mjr 6:cc35eb643e8f 308 class LwDigOut: public LwOut
mjr 6:cc35eb643e8f 309 {
mjr 6:cc35eb643e8f 310 public:
mjr 13:72dda449c3c0 311 LwDigOut(PinName pin) : p(pin) { prv = -1; }
mjr 13:72dda449c3c0 312 virtual void set(float val)
mjr 13:72dda449c3c0 313 {
mjr 13:72dda449c3c0 314 if (val != prv)
mjr 13:72dda449c3c0 315 p.write((prv = val) == 0.0 ? 0 : 1);
mjr 13:72dda449c3c0 316 }
mjr 6:cc35eb643e8f 317 DigitalOut p;
mjr 13:72dda449c3c0 318 float prv;
mjr 6:cc35eb643e8f 319 };
mjr 11:bd9da7088e6e 320 class LwUnusedOut: public LwOut
mjr 11:bd9da7088e6e 321 {
mjr 11:bd9da7088e6e 322 public:
mjr 11:bd9da7088e6e 323 LwUnusedOut() { }
mjr 11:bd9da7088e6e 324 virtual void set(float val) { }
mjr 11:bd9da7088e6e 325 };
mjr 6:cc35eb643e8f 326
mjr 6:cc35eb643e8f 327 // output pin array
mjr 6:cc35eb643e8f 328 static LwOut *lwPin[32];
mjr 6:cc35eb643e8f 329
mjr 6:cc35eb643e8f 330 // initialize the output pin array
mjr 6:cc35eb643e8f 331 void initLwOut()
mjr 6:cc35eb643e8f 332 {
mjr 9:fd65b0a94720 333 for (int i = 0 ; i < countof(lwPin) ; ++i)
mjr 6:cc35eb643e8f 334 {
mjr 11:bd9da7088e6e 335 PinName p = (i < countof(ledWizPortMap) ? ledWizPortMap[i].pin : NC);
mjr 11:bd9da7088e6e 336 if (p == NC)
mjr 11:bd9da7088e6e 337 lwPin[i] = new LwUnusedOut();
mjr 11:bd9da7088e6e 338 else if (ledWizPortMap[i].isPWM)
mjr 11:bd9da7088e6e 339 lwPin[i] = new LwPwmOut(p);
mjr 11:bd9da7088e6e 340 else
mjr 11:bd9da7088e6e 341 lwPin[i] = new LwDigOut(p);
mjr 6:cc35eb643e8f 342 }
mjr 6:cc35eb643e8f 343 }
mjr 6:cc35eb643e8f 344
mjr 0:5acbbe3f4cf4 345 // on/off state for each LedWiz output
mjr 1:d913e0afb2ac 346 static uint8_t wizOn[32];
mjr 0:5acbbe3f4cf4 347
mjr 0:5acbbe3f4cf4 348 // profile (brightness/blink) state for each LedWiz output
mjr 1:d913e0afb2ac 349 static uint8_t wizVal[32] = {
mjr 13:72dda449c3c0 350 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 351 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 352 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 353 48, 48, 48, 48, 48, 48, 48, 48
mjr 0:5acbbe3f4cf4 354 };
mjr 0:5acbbe3f4cf4 355
mjr 1:d913e0afb2ac 356 static float wizState(int idx)
mjr 0:5acbbe3f4cf4 357 {
mjr 13:72dda449c3c0 358 if (wizOn[idx])
mjr 13:72dda449c3c0 359 {
mjr 0:5acbbe3f4cf4 360 // on - map profile brightness state to PWM level
mjr 1:d913e0afb2ac 361 uint8_t val = wizVal[idx];
mjr 13:72dda449c3c0 362 if (val <= 48)
mjr 13:72dda449c3c0 363 {
mjr 15:944bbc29c4dd 364 // PWM brightness/intensity level. Rescale from the LedWiz
mjr 15:944bbc29c4dd 365 // 0..48 integer range to our internal PwmOut 0..1 float range.
mjr 15:944bbc29c4dd 366 // Note that on the actual LedWiz, level 48 is actually about
mjr 15:944bbc29c4dd 367 // 98% on - contrary to the LedWiz documentation, level 49 is
mjr 15:944bbc29c4dd 368 // the true 100% level. (In the documentation, level 49 is
mjr 15:944bbc29c4dd 369 // simply not a valid setting.) Even so, we treat level 48 as
mjr 15:944bbc29c4dd 370 // 100% on to match the documentation. This won't be perfectly
mjr 15:944bbc29c4dd 371 // ocmpatible with the actual LedWiz, but it makes for such a
mjr 15:944bbc29c4dd 372 // small difference in brightness (if the output device is an
mjr 15:944bbc29c4dd 373 // LED, say) that no one should notice. It seems better to
mjr 15:944bbc29c4dd 374 // err in this direction, because while the difference in
mjr 15:944bbc29c4dd 375 // brightness when attached to an LED won't be noticeable, the
mjr 15:944bbc29c4dd 376 // difference in duty cycle when attached to something like a
mjr 15:944bbc29c4dd 377 // contactor *can* be noticeable - anything less than 100%
mjr 15:944bbc29c4dd 378 // can cause a contactor or relay to chatter. There's almost
mjr 15:944bbc29c4dd 379 // never a situation where you'd want values other than 0% and
mjr 15:944bbc29c4dd 380 // 100% for a contactor or relay, so treating level 48 as 100%
mjr 15:944bbc29c4dd 381 // makes us work properly with software that's expecting the
mjr 15:944bbc29c4dd 382 // documented LedWiz behavior and therefore uses level 48 to
mjr 15:944bbc29c4dd 383 // turn a contactor or relay fully on.
mjr 13:72dda449c3c0 384 return val/48.0;
mjr 13:72dda449c3c0 385 }
mjr 13:72dda449c3c0 386 else if (val == 49)
mjr 13:72dda449c3c0 387 {
mjr 15:944bbc29c4dd 388 // 49 is undefined in the LedWiz documentation, but actually
mjr 15:944bbc29c4dd 389 // means 100% on. The documentation says that levels 1-48 are
mjr 15:944bbc29c4dd 390 // the full PWM range, but empirically it appears that the real
mjr 15:944bbc29c4dd 391 // range implemented in the firmware is 1-49. Some software on
mjr 15:944bbc29c4dd 392 // the PC side (notably DOF) is aware of this and uses level 49
mjr 15:944bbc29c4dd 393 // to mean "100% on". To ensure compatibility with existing
mjr 15:944bbc29c4dd 394 // PC-side software, we need to recognize level 49.
mjr 13:72dda449c3c0 395 return 1.0;
mjr 13:72dda449c3c0 396 }
mjr 0:5acbbe3f4cf4 397 else if (val >= 129 && val <= 132)
mjr 13:72dda449c3c0 398 {
mjr 13:72dda449c3c0 399 // Values of 129-132 select different flashing modes. We don't
mjr 13:72dda449c3c0 400 // support any of these. Instead, simply treat them as fully on.
mjr 13:72dda449c3c0 401 // Note that DOF doesn't ever use modes 129-132, as it implements
mjr 13:72dda449c3c0 402 // all flashing modes itself on the host side, so this limitation
mjr 13:72dda449c3c0 403 // won't have any effect on DOF users. You can observe it using
mjr 13:72dda449c3c0 404 // LedBlinky, though.
mjr 13:72dda449c3c0 405 return 1.0;
mjr 13:72dda449c3c0 406 }
mjr 0:5acbbe3f4cf4 407 else
mjr 13:72dda449c3c0 408 {
mjr 13:72dda449c3c0 409 // Other values are undefined in the LedWiz documentation. Hosts
mjr 13:72dda449c3c0 410 // *should* never send undefined values, since whatever behavior an
mjr 13:72dda449c3c0 411 // LedWiz unit exhibits in response is accidental and could change
mjr 13:72dda449c3c0 412 // in a future version. We'll treat all undefined values as equivalent
mjr 13:72dda449c3c0 413 // to 48 (fully on).
mjr 13:72dda449c3c0 414 //
mjr 13:72dda449c3c0 415 // NB: the 49 and 129-132 cases are broken out above for the sake
mjr 13:72dda449c3c0 416 // of documentation. We end up using 1.0 as the return value for
mjr 13:72dda449c3c0 417 // everything outside of the defined 0-48 range, so we could collapse
mjr 13:72dda449c3c0 418 // this whole thing to a single 'else' branch, but I wanted to call
mjr 13:72dda449c3c0 419 // out the specific reasons for handling the settings above as we do.
mjr 0:5acbbe3f4cf4 420 return 1.0;
mjr 13:72dda449c3c0 421 }
mjr 0:5acbbe3f4cf4 422 }
mjr 13:72dda449c3c0 423 else
mjr 13:72dda449c3c0 424 {
mjr 13:72dda449c3c0 425 // off - show at 0 intensity
mjr 13:72dda449c3c0 426 return 0.0;
mjr 0:5acbbe3f4cf4 427 }
mjr 0:5acbbe3f4cf4 428 }
mjr 0:5acbbe3f4cf4 429
mjr 1:d913e0afb2ac 430 static void updateWizOuts()
mjr 1:d913e0afb2ac 431 {
mjr 6:cc35eb643e8f 432 for (int i = 0 ; i < 32 ; ++i)
mjr 6:cc35eb643e8f 433 lwPin[i]->set(wizState(i));
mjr 1:d913e0afb2ac 434 }
mjr 1:d913e0afb2ac 435
mjr 11:bd9da7088e6e 436
mjr 11:bd9da7088e6e 437 // ---------------------------------------------------------------------------
mjr 11:bd9da7088e6e 438 //
mjr 11:bd9da7088e6e 439 // Button input
mjr 11:bd9da7088e6e 440 //
mjr 11:bd9da7088e6e 441
mjr 11:bd9da7088e6e 442 // button input map array
mjr 11:bd9da7088e6e 443 DigitalIn *buttonDigIn[32];
mjr 11:bd9da7088e6e 444
mjr 12:669df364a565 445 // timer for button reports
mjr 12:669df364a565 446 static Timer buttonTimer;
mjr 12:669df364a565 447
mjr 11:bd9da7088e6e 448 // initialize the button inputs
mjr 11:bd9da7088e6e 449 void initButtons()
mjr 11:bd9da7088e6e 450 {
mjr 11:bd9da7088e6e 451 // create the digital inputs
mjr 11:bd9da7088e6e 452 for (int i = 0 ; i < countof(buttonDigIn) ; ++i)
mjr 11:bd9da7088e6e 453 {
mjr 11:bd9da7088e6e 454 if (i < countof(buttonMap) && buttonMap[i] != NC)
mjr 11:bd9da7088e6e 455 buttonDigIn[i] = new DigitalIn(buttonMap[i]);
mjr 11:bd9da7088e6e 456 else
mjr 11:bd9da7088e6e 457 buttonDigIn[i] = 0;
mjr 11:bd9da7088e6e 458 }
mjr 12:669df364a565 459
mjr 12:669df364a565 460 // start the button timer
mjr 12:669df364a565 461 buttonTimer.start();
mjr 11:bd9da7088e6e 462 }
mjr 11:bd9da7088e6e 463
mjr 11:bd9da7088e6e 464
mjr 11:bd9da7088e6e 465 // read the raw button input state
mjr 11:bd9da7088e6e 466 uint32_t readButtonsRaw()
mjr 11:bd9da7088e6e 467 {
mjr 11:bd9da7088e6e 468 // start with all buttons off
mjr 11:bd9da7088e6e 469 uint32_t buttons = 0;
mjr 11:bd9da7088e6e 470
mjr 11:bd9da7088e6e 471 // scan the button list
mjr 11:bd9da7088e6e 472 uint32_t bit = 1;
mjr 11:bd9da7088e6e 473 for (int i = 0 ; i < countof(buttonDigIn) ; ++i, bit <<= 1)
mjr 11:bd9da7088e6e 474 {
mjr 11:bd9da7088e6e 475 if (buttonDigIn[i] != 0 && !buttonDigIn[i]->read())
mjr 11:bd9da7088e6e 476 buttons |= bit;
mjr 11:bd9da7088e6e 477 }
mjr 11:bd9da7088e6e 478
mjr 11:bd9da7088e6e 479 // return the button list
mjr 11:bd9da7088e6e 480 return buttons;
mjr 11:bd9da7088e6e 481 }
mjr 11:bd9da7088e6e 482
mjr 17:ab3cec0c8bf4 483 // Read buttons with debouncing.
mjr 17:ab3cec0c8bf4 484 //
mjr 17:ab3cec0c8bf4 485 // Debouncing is the process of filtering out transients from button
mjr 17:ab3cec0c8bf4 486 // state changes. When an electrical switch is closed or opened, the
mjr 17:ab3cec0c8bf4 487 // signal can have a brief period of instability that makes the switch
mjr 17:ab3cec0c8bf4 488 // appear to turn on and off very rapidly. This is known as "bouncing".
mjr 17:ab3cec0c8bf4 489 //
mjr 17:ab3cec0c8bf4 490 // To remove the transients, we filter the signal by requiring each
mjr 17:ab3cec0c8bf4 491 // change to stick for at least a minimum interval (we use 50ms). We
mjr 17:ab3cec0c8bf4 492 // keep a short recent history of each button's state for this purpose.
mjr 17:ab3cec0c8bf4 493 // If we see a button change state, we ignore the change if we saw the
mjr 17:ab3cec0c8bf4 494 // same button make another change within the same interval.
mjr 11:bd9da7088e6e 495 uint32_t readButtonsDebounced()
mjr 11:bd9da7088e6e 496 {
mjr 11:bd9da7088e6e 497 struct reading {
mjr 17:ab3cec0c8bf4 498 // elapsed time between this reading and the previous reading
mjr 17:ab3cec0c8bf4 499 int dt;
mjr 17:ab3cec0c8bf4 500
mjr 17:ab3cec0c8bf4 501 // Final button state for each button that changed on this
mjr 17:ab3cec0c8bf4 502 // report. OR this with a new report (after applying the
mjr 17:ab3cec0c8bf4 503 // mask 'm') to carry forward the changes that occurred in
mjr 17:ab3cec0c8bf4 504 // this report to the new report.
mjr 17:ab3cec0c8bf4 505 uint32_t b;
mjr 17:ab3cec0c8bf4 506
mjr 17:ab3cec0c8bf4 507 // Change mask at this report. This is a bit mask of the buttons
mjr 17:ab3cec0c8bf4 508 // that *didn't* change on this report. AND this mask with a
mjr 17:ab3cec0c8bf4 509 // new reading to filter buttons out of the new reading that
mjr 17:ab3cec0c8bf4 510 // changed on this report.
mjr 17:ab3cec0c8bf4 511 uint32_t m;
mjr 11:bd9da7088e6e 512 };
mjr 11:bd9da7088e6e 513 static reading readings[8]; // circular buffer of readings
mjr 11:bd9da7088e6e 514 static int ri = 0; // reading buffer index (next write position)
mjr 17:ab3cec0c8bf4 515 static int bPrv = 0; // immediately previous report
mjr 11:bd9da7088e6e 516
mjr 11:bd9da7088e6e 517 // get the write pointer
mjr 11:bd9da7088e6e 518 reading *r = &readings[ri];
mjr 11:bd9da7088e6e 519
mjr 11:bd9da7088e6e 520 // figure the time since the last reading, and read the raw button state
mjr 17:ab3cec0c8bf4 521 int ms = r->dt = buttonTimer.read_ms();
mjr 17:ab3cec0c8bf4 522 uint32_t b = readButtonsRaw();
mjr 11:bd9da7088e6e 523
mjr 11:bd9da7088e6e 524 // start timing the next interval
mjr 12:669df364a565 525 buttonTimer.reset();
mjr 11:bd9da7088e6e 526
mjr 17:ab3cec0c8bf4 527 // mask out changes for any buttons that changed state within the
mjr 17:ab3cec0c8bf4 528 // past 50ms
mjr 17:ab3cec0c8bf4 529 for (int i = 1 ; i < countof(readings) && ms < 50 ; ++i)
mjr 11:bd9da7088e6e 530 {
mjr 11:bd9da7088e6e 531 // find the next prior reading, wrapping in the circular buffer
mjr 11:bd9da7088e6e 532 int j = ri - i;
mjr 11:bd9da7088e6e 533 if (j < 0)
mjr 11:bd9da7088e6e 534 j = countof(readings) - 1;
mjr 11:bd9da7088e6e 535 reading *rj = &readings[j];
mjr 17:ab3cec0c8bf4 536
mjr 17:ab3cec0c8bf4 537 // For any button that changed state in the prior reading 'rj',
mjr 17:ab3cec0c8bf4 538 // remove any new change and restore it to its 'rj' state.
mjr 17:ab3cec0c8bf4 539 b &= rj->m;
mjr 17:ab3cec0c8bf4 540 b |= rj->b;
mjr 17:ab3cec0c8bf4 541
mjr 17:ab3cec0c8bf4 542 // add in the time to the next prior report
mjr 11:bd9da7088e6e 543 ms += rj->dt;
mjr 11:bd9da7088e6e 544 }
mjr 11:bd9da7088e6e 545
mjr 17:ab3cec0c8bf4 546 // figure which buttons changed on this report vs the prior report
mjr 17:ab3cec0c8bf4 547 uint32_t m = b ^ bPrv;
mjr 17:ab3cec0c8bf4 548
mjr 17:ab3cec0c8bf4 549 // save the change mask and changed button vector in our history entry
mjr 17:ab3cec0c8bf4 550 r->m = ~m;
mjr 17:ab3cec0c8bf4 551 r->b = b & m;
mjr 17:ab3cec0c8bf4 552
mjr 17:ab3cec0c8bf4 553 // save this as the prior report
mjr 17:ab3cec0c8bf4 554 bPrv = b;
mjr 17:ab3cec0c8bf4 555
mjr 11:bd9da7088e6e 556 // advance the write position for next time
mjr 11:bd9da7088e6e 557 ri += 1;
mjr 12:669df364a565 558 if (ri >= countof(readings))
mjr 11:bd9da7088e6e 559 ri = 0;
mjr 11:bd9da7088e6e 560
mjr 11:bd9da7088e6e 561 // return the debounced result
mjr 11:bd9da7088e6e 562 return b;
mjr 11:bd9da7088e6e 563 }
mjr 11:bd9da7088e6e 564
mjr 5:a70c0bce770d 565 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 566 //
mjr 5:a70c0bce770d 567 // Customization joystick subbclass
mjr 5:a70c0bce770d 568 //
mjr 5:a70c0bce770d 569
mjr 5:a70c0bce770d 570 class MyUSBJoystick: public USBJoystick
mjr 5:a70c0bce770d 571 {
mjr 5:a70c0bce770d 572 public:
mjr 5:a70c0bce770d 573 MyUSBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release)
mjr 5:a70c0bce770d 574 : USBJoystick(vendor_id, product_id, product_release, true)
mjr 5:a70c0bce770d 575 {
mjr 5:a70c0bce770d 576 suspended_ = false;
mjr 5:a70c0bce770d 577 }
mjr 5:a70c0bce770d 578
mjr 5:a70c0bce770d 579 // are we connected?
mjr 5:a70c0bce770d 580 int isConnected() { return configured(); }
mjr 5:a70c0bce770d 581
mjr 5:a70c0bce770d 582 // Are we in suspend mode?
mjr 5:a70c0bce770d 583 int isSuspended() const { return suspended_; }
mjr 5:a70c0bce770d 584
mjr 5:a70c0bce770d 585 protected:
mjr 5:a70c0bce770d 586 virtual void suspendStateChanged(unsigned int suspended)
mjr 5:a70c0bce770d 587 { suspended_ = suspended; }
mjr 5:a70c0bce770d 588
mjr 5:a70c0bce770d 589 // are we suspended?
mjr 5:a70c0bce770d 590 int suspended_;
mjr 5:a70c0bce770d 591 };
mjr 5:a70c0bce770d 592
mjr 5:a70c0bce770d 593 // ---------------------------------------------------------------------------
mjr 6:cc35eb643e8f 594 //
mjr 6:cc35eb643e8f 595 // Some simple math service routines
mjr 6:cc35eb643e8f 596 //
mjr 6:cc35eb643e8f 597
mjr 6:cc35eb643e8f 598 inline float square(float x) { return x*x; }
mjr 6:cc35eb643e8f 599 inline float round(float x) { return x > 0 ? floor(x + 0.5) : ceil(x - 0.5); }
mjr 6:cc35eb643e8f 600
mjr 6:cc35eb643e8f 601 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 602 //
mjr 5:a70c0bce770d 603 // Accelerometer (MMA8451Q)
mjr 5:a70c0bce770d 604 //
mjr 5:a70c0bce770d 605
mjr 5:a70c0bce770d 606 // The MMA8451Q is the KL25Z's on-board 3-axis accelerometer.
mjr 5:a70c0bce770d 607 //
mjr 5:a70c0bce770d 608 // This is a custom wrapper for the library code to interface to the
mjr 6:cc35eb643e8f 609 // MMA8451Q. This class encapsulates an interrupt handler and
mjr 6:cc35eb643e8f 610 // automatic calibration.
mjr 5:a70c0bce770d 611 //
mjr 5:a70c0bce770d 612 // We install an interrupt handler on the accelerometer "data ready"
mjr 6:cc35eb643e8f 613 // interrupt to ensure that we fetch each sample immediately when it
mjr 6:cc35eb643e8f 614 // becomes available. The accelerometer data rate is fiarly high
mjr 6:cc35eb643e8f 615 // (800 Hz), so it's not practical to keep up with it by polling.
mjr 6:cc35eb643e8f 616 // Using an interrupt handler lets us respond quickly and read
mjr 6:cc35eb643e8f 617 // every sample.
mjr 5:a70c0bce770d 618 //
mjr 6:cc35eb643e8f 619 // We automatically calibrate the accelerometer so that it's not
mjr 6:cc35eb643e8f 620 // necessary to get it exactly level when installing it, and so
mjr 6:cc35eb643e8f 621 // that it's also not necessary to calibrate it manually. There's
mjr 6:cc35eb643e8f 622 // lots of experience that tells us that manual calibration is a
mjr 6:cc35eb643e8f 623 // terrible solution, mostly because cabinets tend to shift slightly
mjr 6:cc35eb643e8f 624 // during use, requiring frequent recalibration. Instead, we
mjr 6:cc35eb643e8f 625 // calibrate automatically. We continuously monitor the acceleration
mjr 6:cc35eb643e8f 626 // data, watching for periods of constant (or nearly constant) values.
mjr 6:cc35eb643e8f 627 // Any time it appears that the machine has been at rest for a while
mjr 6:cc35eb643e8f 628 // (about 5 seconds), we'll average the readings during that rest
mjr 6:cc35eb643e8f 629 // period and use the result as the level rest position. This is
mjr 6:cc35eb643e8f 630 // is ongoing, so we'll quickly find the center point again if the
mjr 6:cc35eb643e8f 631 // machine is moved during play (by an especially aggressive bout
mjr 6:cc35eb643e8f 632 // of nudging, say).
mjr 5:a70c0bce770d 633 //
mjr 5:a70c0bce770d 634
mjr 17:ab3cec0c8bf4 635 // I2C address of the accelerometer (this is a constant of the KL25Z)
mjr 17:ab3cec0c8bf4 636 const int MMA8451_I2C_ADDRESS = (0x1d<<1);
mjr 17:ab3cec0c8bf4 637
mjr 17:ab3cec0c8bf4 638 // SCL and SDA pins for the accelerometer (constant for the KL25Z)
mjr 17:ab3cec0c8bf4 639 #define MMA8451_SCL_PIN PTE25
mjr 17:ab3cec0c8bf4 640 #define MMA8451_SDA_PIN PTE24
mjr 17:ab3cec0c8bf4 641
mjr 17:ab3cec0c8bf4 642 // Digital in pin to use for the accelerometer interrupt. For the KL25Z,
mjr 17:ab3cec0c8bf4 643 // this can be either PTA14 or PTA15, since those are the pins physically
mjr 17:ab3cec0c8bf4 644 // wired on this board to the MMA8451 interrupt controller.
mjr 17:ab3cec0c8bf4 645 #define MMA8451_INT_PIN PTA15
mjr 17:ab3cec0c8bf4 646
mjr 17:ab3cec0c8bf4 647
mjr 6:cc35eb643e8f 648 // accelerometer input history item, for gathering calibration data
mjr 6:cc35eb643e8f 649 struct AccHist
mjr 5:a70c0bce770d 650 {
mjr 6:cc35eb643e8f 651 AccHist() { x = y = d = 0.0; xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 652 void set(float x, float y, AccHist *prv)
mjr 6:cc35eb643e8f 653 {
mjr 6:cc35eb643e8f 654 // save the raw position
mjr 6:cc35eb643e8f 655 this->x = x;
mjr 6:cc35eb643e8f 656 this->y = y;
mjr 6:cc35eb643e8f 657 this->d = distance(prv);
mjr 6:cc35eb643e8f 658 }
mjr 6:cc35eb643e8f 659
mjr 6:cc35eb643e8f 660 // reading for this entry
mjr 5:a70c0bce770d 661 float x, y;
mjr 5:a70c0bce770d 662
mjr 6:cc35eb643e8f 663 // distance from previous entry
mjr 6:cc35eb643e8f 664 float d;
mjr 5:a70c0bce770d 665
mjr 6:cc35eb643e8f 666 // total and count of samples averaged over this period
mjr 6:cc35eb643e8f 667 float xtot, ytot;
mjr 6:cc35eb643e8f 668 int cnt;
mjr 6:cc35eb643e8f 669
mjr 6:cc35eb643e8f 670 void clearAvg() { xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 671 void addAvg(float x, float y) { xtot += x; ytot += y; ++cnt; }
mjr 6:cc35eb643e8f 672 float xAvg() const { return xtot/cnt; }
mjr 6:cc35eb643e8f 673 float yAvg() const { return ytot/cnt; }
mjr 5:a70c0bce770d 674
mjr 6:cc35eb643e8f 675 float distance(AccHist *p)
mjr 6:cc35eb643e8f 676 { return sqrt(square(p->x - x) + square(p->y - y)); }
mjr 5:a70c0bce770d 677 };
mjr 5:a70c0bce770d 678
mjr 5:a70c0bce770d 679 // accelerometer wrapper class
mjr 3:3514575d4f86 680 class Accel
mjr 3:3514575d4f86 681 {
mjr 3:3514575d4f86 682 public:
mjr 3:3514575d4f86 683 Accel(PinName sda, PinName scl, int i2cAddr, PinName irqPin)
mjr 3:3514575d4f86 684 : mma_(sda, scl, i2cAddr), intIn_(irqPin)
mjr 3:3514575d4f86 685 {
mjr 5:a70c0bce770d 686 // remember the interrupt pin assignment
mjr 5:a70c0bce770d 687 irqPin_ = irqPin;
mjr 5:a70c0bce770d 688
mjr 5:a70c0bce770d 689 // reset and initialize
mjr 5:a70c0bce770d 690 reset();
mjr 5:a70c0bce770d 691 }
mjr 5:a70c0bce770d 692
mjr 5:a70c0bce770d 693 void reset()
mjr 5:a70c0bce770d 694 {
mjr 6:cc35eb643e8f 695 // clear the center point
mjr 6:cc35eb643e8f 696 cx_ = cy_ = 0.0;
mjr 6:cc35eb643e8f 697
mjr 6:cc35eb643e8f 698 // start the calibration timer
mjr 5:a70c0bce770d 699 tCenter_.start();
mjr 5:a70c0bce770d 700 iAccPrv_ = nAccPrv_ = 0;
mjr 6:cc35eb643e8f 701
mjr 5:a70c0bce770d 702 // reset and initialize the MMA8451Q
mjr 5:a70c0bce770d 703 mma_.init();
mjr 6:cc35eb643e8f 704
mjr 6:cc35eb643e8f 705 // set the initial integrated velocity reading to zero
mjr 6:cc35eb643e8f 706 vx_ = vy_ = 0;
mjr 3:3514575d4f86 707
mjr 6:cc35eb643e8f 708 // set up our accelerometer interrupt handling
mjr 6:cc35eb643e8f 709 intIn_.rise(this, &Accel::isr);
mjr 5:a70c0bce770d 710 mma_.setInterruptMode(irqPin_ == PTA14 ? 1 : 2);
mjr 3:3514575d4f86 711
mjr 3:3514575d4f86 712 // read the current registers to clear the data ready flag
mjr 6:cc35eb643e8f 713 mma_.getAccXYZ(ax_, ay_, az_);
mjr 3:3514575d4f86 714
mjr 3:3514575d4f86 715 // start our timers
mjr 3:3514575d4f86 716 tGet_.start();
mjr 3:3514575d4f86 717 tInt_.start();
mjr 3:3514575d4f86 718 }
mjr 3:3514575d4f86 719
mjr 9:fd65b0a94720 720 void get(int &x, int &y)
mjr 3:3514575d4f86 721 {
mjr 3:3514575d4f86 722 // disable interrupts while manipulating the shared data
mjr 3:3514575d4f86 723 __disable_irq();
mjr 3:3514575d4f86 724
mjr 3:3514575d4f86 725 // read the shared data and store locally for calculations
mjr 6:cc35eb643e8f 726 float ax = ax_, ay = ay_;
mjr 6:cc35eb643e8f 727 float vx = vx_, vy = vy_;
mjr 5:a70c0bce770d 728
mjr 6:cc35eb643e8f 729 // reset the velocity sum for the next run
mjr 6:cc35eb643e8f 730 vx_ = vy_ = 0;
mjr 3:3514575d4f86 731
mjr 3:3514575d4f86 732 // get the time since the last get() sample
mjr 3:3514575d4f86 733 float dt = tGet_.read_us()/1.0e6;
mjr 3:3514575d4f86 734 tGet_.reset();
mjr 3:3514575d4f86 735
mjr 3:3514575d4f86 736 // done manipulating the shared data
mjr 3:3514575d4f86 737 __enable_irq();
mjr 3:3514575d4f86 738
mjr 6:cc35eb643e8f 739 // adjust the readings for the integration time
mjr 6:cc35eb643e8f 740 vx /= dt;
mjr 6:cc35eb643e8f 741 vy /= dt;
mjr 6:cc35eb643e8f 742
mjr 6:cc35eb643e8f 743 // add this sample to the current calibration interval's running total
mjr 6:cc35eb643e8f 744 AccHist *p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 745 p->addAvg(ax, ay);
mjr 6:cc35eb643e8f 746
mjr 5:a70c0bce770d 747 // check for auto-centering every so often
mjr 5:a70c0bce770d 748 if (tCenter_.read_ms() > 1000)
mjr 5:a70c0bce770d 749 {
mjr 5:a70c0bce770d 750 // add the latest raw sample to the history list
mjr 6:cc35eb643e8f 751 AccHist *prv = p;
mjr 5:a70c0bce770d 752 iAccPrv_ = (iAccPrv_ + 1) % maxAccPrv;
mjr 6:cc35eb643e8f 753 p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 754 p->set(ax, ay, prv);
mjr 5:a70c0bce770d 755
mjr 5:a70c0bce770d 756 // if we have a full complement, check for stability
mjr 5:a70c0bce770d 757 if (nAccPrv_ >= maxAccPrv)
mjr 5:a70c0bce770d 758 {
mjr 5:a70c0bce770d 759 // check if we've been stable for all recent samples
mjr 6:cc35eb643e8f 760 static const float accTol = .01;
mjr 6:cc35eb643e8f 761 AccHist *p0 = accPrv_;
mjr 6:cc35eb643e8f 762 if (p0[0].d < accTol
mjr 6:cc35eb643e8f 763 && p0[1].d < accTol
mjr 6:cc35eb643e8f 764 && p0[2].d < accTol
mjr 6:cc35eb643e8f 765 && p0[3].d < accTol
mjr 6:cc35eb643e8f 766 && p0[4].d < accTol)
mjr 5:a70c0bce770d 767 {
mjr 6:cc35eb643e8f 768 // Figure the new calibration point as the average of
mjr 6:cc35eb643e8f 769 // the samples over the rest period
mjr 6:cc35eb643e8f 770 cx_ = (p0[0].xAvg() + p0[1].xAvg() + p0[2].xAvg() + p0[3].xAvg() + p0[4].xAvg())/5.0;
mjr 6:cc35eb643e8f 771 cy_ = (p0[0].yAvg() + p0[1].yAvg() + p0[2].yAvg() + p0[3].yAvg() + p0[4].yAvg())/5.0;
mjr 5:a70c0bce770d 772 }
mjr 5:a70c0bce770d 773 }
mjr 5:a70c0bce770d 774 else
mjr 5:a70c0bce770d 775 {
mjr 5:a70c0bce770d 776 // not enough samples yet; just up the count
mjr 5:a70c0bce770d 777 ++nAccPrv_;
mjr 5:a70c0bce770d 778 }
mjr 6:cc35eb643e8f 779
mjr 6:cc35eb643e8f 780 // clear the new item's running totals
mjr 6:cc35eb643e8f 781 p->clearAvg();
mjr 5:a70c0bce770d 782
mjr 5:a70c0bce770d 783 // reset the timer
mjr 5:a70c0bce770d 784 tCenter_.reset();
mjr 5:a70c0bce770d 785 }
mjr 5:a70c0bce770d 786
mjr 6:cc35eb643e8f 787 // report our integrated velocity reading in x,y
mjr 6:cc35eb643e8f 788 x = rawToReport(vx);
mjr 6:cc35eb643e8f 789 y = rawToReport(vy);
mjr 5:a70c0bce770d 790
mjr 6:cc35eb643e8f 791 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 792 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 793 printf("%f %f %d %d %f\r\n", vx, vy, x, y, dt);
mjr 6:cc35eb643e8f 794 #endif
mjr 3:3514575d4f86 795 }
mjr 3:3514575d4f86 796
mjr 3:3514575d4f86 797 private:
mjr 6:cc35eb643e8f 798 // adjust a raw acceleration figure to a usb report value
mjr 6:cc35eb643e8f 799 int rawToReport(float v)
mjr 5:a70c0bce770d 800 {
mjr 6:cc35eb643e8f 801 // scale to the joystick report range and round to integer
mjr 6:cc35eb643e8f 802 int i = int(round(v*JOYMAX));
mjr 5:a70c0bce770d 803
mjr 6:cc35eb643e8f 804 // if it's near the center, scale it roughly as 20*(i/20)^2,
mjr 6:cc35eb643e8f 805 // to suppress noise near the rest position
mjr 6:cc35eb643e8f 806 static const int filter[] = {
mjr 6:cc35eb643e8f 807 -18, -16, -14, -13, -11, -10, -8, -7, -6, -5, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0,
mjr 6:cc35eb643e8f 808 0,
mjr 6:cc35eb643e8f 809 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 16, 18
mjr 6:cc35eb643e8f 810 };
mjr 6:cc35eb643e8f 811 return (i > 20 || i < -20 ? i : filter[i+20]);
mjr 5:a70c0bce770d 812 }
mjr 5:a70c0bce770d 813
mjr 3:3514575d4f86 814 // interrupt handler
mjr 3:3514575d4f86 815 void isr()
mjr 3:3514575d4f86 816 {
mjr 3:3514575d4f86 817 // Read the axes. Note that we have to read all three axes
mjr 3:3514575d4f86 818 // (even though we only really use x and y) in order to clear
mjr 3:3514575d4f86 819 // the "data ready" status bit in the accelerometer. The
mjr 3:3514575d4f86 820 // interrupt only occurs when the "ready" bit transitions from
mjr 3:3514575d4f86 821 // off to on, so we have to make sure it's off.
mjr 5:a70c0bce770d 822 float x, y, z;
mjr 5:a70c0bce770d 823 mma_.getAccXYZ(x, y, z);
mjr 3:3514575d4f86 824
mjr 3:3514575d4f86 825 // calculate the time since the last interrupt
mjr 3:3514575d4f86 826 float dt = tInt_.read_us()/1.0e6;
mjr 3:3514575d4f86 827 tInt_.reset();
mjr 6:cc35eb643e8f 828
mjr 6:cc35eb643e8f 829 // integrate the time slice from the previous reading to this reading
mjr 6:cc35eb643e8f 830 vx_ += (x + ax_ - 2*cx_)*dt/2;
mjr 6:cc35eb643e8f 831 vy_ += (y + ay_ - 2*cy_)*dt/2;
mjr 3:3514575d4f86 832
mjr 6:cc35eb643e8f 833 // store the updates
mjr 6:cc35eb643e8f 834 ax_ = x;
mjr 6:cc35eb643e8f 835 ay_ = y;
mjr 6:cc35eb643e8f 836 az_ = z;
mjr 3:3514575d4f86 837 }
mjr 3:3514575d4f86 838
mjr 3:3514575d4f86 839 // underlying accelerometer object
mjr 3:3514575d4f86 840 MMA8451Q mma_;
mjr 3:3514575d4f86 841
mjr 5:a70c0bce770d 842 // last raw acceleration readings
mjr 6:cc35eb643e8f 843 float ax_, ay_, az_;
mjr 5:a70c0bce770d 844
mjr 6:cc35eb643e8f 845 // integrated velocity reading since last get()
mjr 6:cc35eb643e8f 846 float vx_, vy_;
mjr 6:cc35eb643e8f 847
mjr 3:3514575d4f86 848 // timer for measuring time between get() samples
mjr 3:3514575d4f86 849 Timer tGet_;
mjr 3:3514575d4f86 850
mjr 3:3514575d4f86 851 // timer for measuring time between interrupts
mjr 3:3514575d4f86 852 Timer tInt_;
mjr 5:a70c0bce770d 853
mjr 6:cc35eb643e8f 854 // Calibration reference point for accelerometer. This is the
mjr 6:cc35eb643e8f 855 // average reading on the accelerometer when in the neutral position
mjr 6:cc35eb643e8f 856 // at rest.
mjr 6:cc35eb643e8f 857 float cx_, cy_;
mjr 5:a70c0bce770d 858
mjr 5:a70c0bce770d 859 // timer for atuo-centering
mjr 5:a70c0bce770d 860 Timer tCenter_;
mjr 6:cc35eb643e8f 861
mjr 6:cc35eb643e8f 862 // Auto-centering history. This is a separate history list that
mjr 6:cc35eb643e8f 863 // records results spaced out sparesely over time, so that we can
mjr 6:cc35eb643e8f 864 // watch for long-lasting periods of rest. When we observe nearly
mjr 6:cc35eb643e8f 865 // no motion for an extended period (on the order of 5 seconds), we
mjr 6:cc35eb643e8f 866 // take this to mean that the cabinet is at rest in its neutral
mjr 6:cc35eb643e8f 867 // position, so we take this as the calibration zero point for the
mjr 6:cc35eb643e8f 868 // accelerometer. We update this history continuously, which allows
mjr 6:cc35eb643e8f 869 // us to continuously re-calibrate the accelerometer. This ensures
mjr 6:cc35eb643e8f 870 // that we'll automatically adjust to any actual changes in the
mjr 6:cc35eb643e8f 871 // cabinet's orientation (e.g., if it gets moved slightly by an
mjr 6:cc35eb643e8f 872 // especially strong nudge) as well as any systematic drift in the
mjr 6:cc35eb643e8f 873 // accelerometer measurement bias (e.g., from temperature changes).
mjr 5:a70c0bce770d 874 int iAccPrv_, nAccPrv_;
mjr 5:a70c0bce770d 875 static const int maxAccPrv = 5;
mjr 6:cc35eb643e8f 876 AccHist accPrv_[maxAccPrv];
mjr 6:cc35eb643e8f 877
mjr 5:a70c0bce770d 878 // interurupt pin name
mjr 5:a70c0bce770d 879 PinName irqPin_;
mjr 5:a70c0bce770d 880
mjr 5:a70c0bce770d 881 // interrupt router
mjr 5:a70c0bce770d 882 InterruptIn intIn_;
mjr 3:3514575d4f86 883 };
mjr 3:3514575d4f86 884
mjr 5:a70c0bce770d 885
mjr 5:a70c0bce770d 886 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 887 //
mjr 14:df700b22ca08 888 // Clear the I2C bus for the MMA8451Q. This seems necessary some of the time
mjr 5:a70c0bce770d 889 // for reasons that aren't clear to me. Doing a hard power cycle has the same
mjr 5:a70c0bce770d 890 // effect, but when we do a soft reset, the hardware sometimes seems to leave
mjr 5:a70c0bce770d 891 // the MMA's SDA line stuck low. Forcing a series of 9 clock pulses through
mjr 14:df700b22ca08 892 // the SCL line is supposed to clear this condition. I'm not convinced this
mjr 14:df700b22ca08 893 // actually works with the way this component is wired on the KL25Z, but it
mjr 14:df700b22ca08 894 // seems harmless, so we'll do it on reset in case it does some good. What
mjr 14:df700b22ca08 895 // we really seem to need is a way to power cycle the MMA8451Q if it ever
mjr 14:df700b22ca08 896 // gets stuck, but this is simply not possible in software on the KL25Z.
mjr 14:df700b22ca08 897 //
mjr 14:df700b22ca08 898 // If the accelerometer does get stuck, and a software reboot doesn't reset
mjr 14:df700b22ca08 899 // it, the only workaround is to manually power cycle the whole KL25Z by
mjr 14:df700b22ca08 900 // unplugging both of its USB connections.
mjr 5:a70c0bce770d 901 //
mjr 5:a70c0bce770d 902 void clear_i2c()
mjr 5:a70c0bce770d 903 {
mjr 5:a70c0bce770d 904 // assume a general-purpose output pin to the I2C clock
mjr 5:a70c0bce770d 905 DigitalOut scl(MMA8451_SCL_PIN);
mjr 5:a70c0bce770d 906 DigitalIn sda(MMA8451_SDA_PIN);
mjr 5:a70c0bce770d 907
mjr 5:a70c0bce770d 908 // clock the SCL 9 times
mjr 5:a70c0bce770d 909 for (int i = 0 ; i < 9 ; ++i)
mjr 5:a70c0bce770d 910 {
mjr 5:a70c0bce770d 911 scl = 1;
mjr 5:a70c0bce770d 912 wait_us(20);
mjr 5:a70c0bce770d 913 scl = 0;
mjr 5:a70c0bce770d 914 wait_us(20);
mjr 5:a70c0bce770d 915 }
mjr 5:a70c0bce770d 916 }
mjr 14:df700b22ca08 917
mjr 14:df700b22ca08 918 // ---------------------------------------------------------------------------
mjr 14:df700b22ca08 919 //
mjr 14:df700b22ca08 920 // CCD read interval callback. When reading the CCD, we'll call this
mjr 14:df700b22ca08 921 // several times over the course of the read loop to refresh the button
mjr 14:df700b22ca08 922 // states. This allows us to debounce the buttons while the long CCD
mjr 14:df700b22ca08 923 // read cycle is taking place, so that we can reliably report button
mjr 14:df700b22ca08 924 // states after each CCD read cycle. (The read cycle takes about 30ms,
mjr 14:df700b22ca08 925 // which should be enough time to reliably debounce the buttons.)
mjr 14:df700b22ca08 926 //
mjr 14:df700b22ca08 927 void ccdReadCB(void *)
mjr 14:df700b22ca08 928 {
mjr 14:df700b22ca08 929 // read the keyboard
mjr 14:df700b22ca08 930 readButtonsDebounced();
mjr 14:df700b22ca08 931 }
mjr 5:a70c0bce770d 932
mjr 5:a70c0bce770d 933 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 934 //
mjr 17:ab3cec0c8bf4 935 // Include the appropriate plunger sensor definition. This will define a
mjr 17:ab3cec0c8bf4 936 // class called PlungerSensor, with a standard interface that we use in
mjr 17:ab3cec0c8bf4 937 // the main loop below. This is *kind of* like a virtual class interface,
mjr 17:ab3cec0c8bf4 938 // but it actually defines the methods statically, which is a little more
mjr 17:ab3cec0c8bf4 939 // efficient at run-time. There's no need for a true virtual interface
mjr 17:ab3cec0c8bf4 940 // because we don't need to be able to change sensor types on the fly.
mjr 17:ab3cec0c8bf4 941 //
mjr 17:ab3cec0c8bf4 942
mjr 17:ab3cec0c8bf4 943 #ifdef ENABLE_CCD_SENSOR
mjr 17:ab3cec0c8bf4 944 #include "ccdSensor.h"
mjr 17:ab3cec0c8bf4 945 #elif ENABLE_POT_SENSOR
mjr 17:ab3cec0c8bf4 946 #include "potSensor.h"
mjr 17:ab3cec0c8bf4 947 #else
mjr 17:ab3cec0c8bf4 948 #include "nullSensor.h"
mjr 17:ab3cec0c8bf4 949 #endif
mjr 17:ab3cec0c8bf4 950
mjr 17:ab3cec0c8bf4 951
mjr 17:ab3cec0c8bf4 952 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 953 //
mjr 17:ab3cec0c8bf4 954 // Non-volatile memory (NVM)
mjr 17:ab3cec0c8bf4 955 //
mjr 17:ab3cec0c8bf4 956
mjr 17:ab3cec0c8bf4 957 // Structure defining our NVM storage layout. We store a small
mjr 17:ab3cec0c8bf4 958 // amount of persistent data in flash memory to retain calibration
mjr 17:ab3cec0c8bf4 959 // data when powered off.
mjr 17:ab3cec0c8bf4 960 struct NVM
mjr 17:ab3cec0c8bf4 961 {
mjr 17:ab3cec0c8bf4 962 // checksum - we use this to determine if the flash record
mjr 17:ab3cec0c8bf4 963 // has been properly initialized
mjr 17:ab3cec0c8bf4 964 uint32_t checksum;
mjr 17:ab3cec0c8bf4 965
mjr 17:ab3cec0c8bf4 966 // signature value
mjr 17:ab3cec0c8bf4 967 static const uint32_t SIGNATURE = 0x4D4A522A;
mjr 17:ab3cec0c8bf4 968 static const uint16_t VERSION = 0x0003;
mjr 17:ab3cec0c8bf4 969
mjr 17:ab3cec0c8bf4 970 // Is the data structure valid? We test the signature and
mjr 17:ab3cec0c8bf4 971 // checksum to determine if we've been properly stored.
mjr 17:ab3cec0c8bf4 972 int valid() const
mjr 17:ab3cec0c8bf4 973 {
mjr 17:ab3cec0c8bf4 974 return (d.sig == SIGNATURE
mjr 17:ab3cec0c8bf4 975 && d.vsn == VERSION
mjr 17:ab3cec0c8bf4 976 && d.sz == sizeof(NVM)
mjr 17:ab3cec0c8bf4 977 && checksum == CRC32(&d, sizeof(d)));
mjr 17:ab3cec0c8bf4 978 }
mjr 17:ab3cec0c8bf4 979
mjr 17:ab3cec0c8bf4 980 // save to non-volatile memory
mjr 17:ab3cec0c8bf4 981 void save(FreescaleIAP &iap, int addr)
mjr 17:ab3cec0c8bf4 982 {
mjr 17:ab3cec0c8bf4 983 // update the checksum and structure size
mjr 17:ab3cec0c8bf4 984 checksum = CRC32(&d, sizeof(d));
mjr 17:ab3cec0c8bf4 985 d.sz = sizeof(NVM);
mjr 17:ab3cec0c8bf4 986
mjr 17:ab3cec0c8bf4 987 // erase the sector
mjr 17:ab3cec0c8bf4 988 iap.erase_sector(addr);
mjr 17:ab3cec0c8bf4 989
mjr 17:ab3cec0c8bf4 990 // save the data
mjr 17:ab3cec0c8bf4 991 iap.program_flash(addr, this, sizeof(*this));
mjr 17:ab3cec0c8bf4 992 }
mjr 17:ab3cec0c8bf4 993
mjr 17:ab3cec0c8bf4 994 // reset calibration data for calibration mode
mjr 17:ab3cec0c8bf4 995 void resetPlunger()
mjr 17:ab3cec0c8bf4 996 {
mjr 17:ab3cec0c8bf4 997 // set extremes for the calibration data
mjr 17:ab3cec0c8bf4 998 d.plungerMax = 0;
mjr 17:ab3cec0c8bf4 999 d.plungerZero = npix;
mjr 17:ab3cec0c8bf4 1000 d.plungerMin = npix;
mjr 17:ab3cec0c8bf4 1001 }
mjr 17:ab3cec0c8bf4 1002
mjr 17:ab3cec0c8bf4 1003 // stored data (excluding the checksum)
mjr 17:ab3cec0c8bf4 1004 struct
mjr 17:ab3cec0c8bf4 1005 {
mjr 17:ab3cec0c8bf4 1006 // Signature, structure version, and structure size - further verification
mjr 17:ab3cec0c8bf4 1007 // that we have valid initialized data. The size is a simple proxy for a
mjr 17:ab3cec0c8bf4 1008 // structure version, as the most common type of change to the structure as
mjr 17:ab3cec0c8bf4 1009 // the software evolves will be the addition of new elements. We also
mjr 17:ab3cec0c8bf4 1010 // provide an explicit version number that we can update manually if we
mjr 17:ab3cec0c8bf4 1011 // make any changes that don't affect the structure size but would affect
mjr 17:ab3cec0c8bf4 1012 // compatibility with a saved record (e.g., swapping two existing elements).
mjr 17:ab3cec0c8bf4 1013 uint32_t sig;
mjr 17:ab3cec0c8bf4 1014 uint16_t vsn;
mjr 17:ab3cec0c8bf4 1015 int sz;
mjr 17:ab3cec0c8bf4 1016
mjr 17:ab3cec0c8bf4 1017 // has the plunger been manually calibrated?
mjr 17:ab3cec0c8bf4 1018 int plungerCal;
mjr 17:ab3cec0c8bf4 1019
mjr 17:ab3cec0c8bf4 1020 // Plunger calibration min, zero, and max. The zero point is the
mjr 17:ab3cec0c8bf4 1021 // rest position (aka park position), where it's in equilibrium between
mjr 17:ab3cec0c8bf4 1022 // the main spring and the barrel spring. It can travel a small distance
mjr 17:ab3cec0c8bf4 1023 // forward of the rest position, because the barrel spring can be
mjr 17:ab3cec0c8bf4 1024 // compressed by the user pushing on the plunger or by the momentum
mjr 17:ab3cec0c8bf4 1025 // of a release motion. The minimum is the maximum forward point where
mjr 17:ab3cec0c8bf4 1026 // the barrel spring can't be compressed any further.
mjr 17:ab3cec0c8bf4 1027 int plungerMin;
mjr 17:ab3cec0c8bf4 1028 int plungerZero;
mjr 17:ab3cec0c8bf4 1029 int plungerMax;
mjr 17:ab3cec0c8bf4 1030
mjr 17:ab3cec0c8bf4 1031 // is the plunger sensor enabled?
mjr 17:ab3cec0c8bf4 1032 int plungerEnabled;
mjr 17:ab3cec0c8bf4 1033
mjr 17:ab3cec0c8bf4 1034 // LedWiz unit number
mjr 17:ab3cec0c8bf4 1035 uint8_t ledWizUnitNo;
mjr 17:ab3cec0c8bf4 1036 } d;
mjr 17:ab3cec0c8bf4 1037 };
mjr 17:ab3cec0c8bf4 1038
mjr 17:ab3cec0c8bf4 1039
mjr 17:ab3cec0c8bf4 1040 // ---------------------------------------------------------------------------
mjr 17:ab3cec0c8bf4 1041 //
mjr 5:a70c0bce770d 1042 // Main program loop. This is invoked on startup and runs forever. Our
mjr 5:a70c0bce770d 1043 // main work is to read our devices (the accelerometer and the CCD), process
mjr 5:a70c0bce770d 1044 // the readings into nudge and plunger position data, and send the results
mjr 5:a70c0bce770d 1045 // to the host computer via the USB joystick interface. We also monitor
mjr 5:a70c0bce770d 1046 // the USB connection for incoming LedWiz commands and process those into
mjr 5:a70c0bce770d 1047 // port outputs.
mjr 5:a70c0bce770d 1048 //
mjr 0:5acbbe3f4cf4 1049 int main(void)
mjr 0:5acbbe3f4cf4 1050 {
mjr 1:d913e0afb2ac 1051 // turn off our on-board indicator LED
mjr 4:02c7cd7b2183 1052 ledR = 1;
mjr 4:02c7cd7b2183 1053 ledG = 1;
mjr 4:02c7cd7b2183 1054 ledB = 1;
mjr 1:d913e0afb2ac 1055
mjr 6:cc35eb643e8f 1056 // initialize the LedWiz ports
mjr 6:cc35eb643e8f 1057 initLwOut();
mjr 6:cc35eb643e8f 1058
mjr 11:bd9da7088e6e 1059 // initialize the button input ports
mjr 11:bd9da7088e6e 1060 initButtons();
mjr 11:bd9da7088e6e 1061
mjr 6:cc35eb643e8f 1062 // we don't need a reset yet
mjr 6:cc35eb643e8f 1063 bool needReset = false;
mjr 6:cc35eb643e8f 1064
mjr 5:a70c0bce770d 1065 // clear the I2C bus for the accelerometer
mjr 5:a70c0bce770d 1066 clear_i2c();
mjr 5:a70c0bce770d 1067
mjr 2:c174f9ee414a 1068 // set up a flash memory controller
mjr 2:c174f9ee414a 1069 FreescaleIAP iap;
mjr 2:c174f9ee414a 1070
mjr 2:c174f9ee414a 1071 // use the last sector of flash for our non-volatile memory structure
mjr 2:c174f9ee414a 1072 int flash_addr = (iap.flash_size() - SECTOR_SIZE);
mjr 2:c174f9ee414a 1073 NVM *flash = (NVM *)flash_addr;
mjr 2:c174f9ee414a 1074 NVM cfg;
mjr 2:c174f9ee414a 1075
mjr 2:c174f9ee414a 1076 // check for valid flash
mjr 6:cc35eb643e8f 1077 bool flash_valid = flash->valid();
mjr 2:c174f9ee414a 1078
mjr 2:c174f9ee414a 1079 // if the flash is valid, load it; otherwise initialize to defaults
mjr 2:c174f9ee414a 1080 if (flash_valid) {
mjr 2:c174f9ee414a 1081 memcpy(&cfg, flash, sizeof(cfg));
mjr 6:cc35eb643e8f 1082 printf("Flash restored: plunger cal=%d, min=%d, zero=%d, max=%d\r\n",
mjr 6:cc35eb643e8f 1083 cfg.d.plungerCal, cfg.d.plungerMin, cfg.d.plungerZero, cfg.d.plungerMax);
mjr 2:c174f9ee414a 1084 }
mjr 2:c174f9ee414a 1085 else {
mjr 2:c174f9ee414a 1086 printf("Factory reset\r\n");
mjr 2:c174f9ee414a 1087 cfg.d.sig = cfg.SIGNATURE;
mjr 2:c174f9ee414a 1088 cfg.d.vsn = cfg.VERSION;
mjr 6:cc35eb643e8f 1089 cfg.d.plungerCal = 0;
mjr 17:ab3cec0c8bf4 1090 cfg.d.plungerMin = 0; // assume we can go all the way forward...
mjr 17:ab3cec0c8bf4 1091 cfg.d.plungerMax = npix; // ...and all the way back
mjr 17:ab3cec0c8bf4 1092 cfg.d.plungerZero = npix/6; // the rest position is usually around 1/2" back
mjr 6:cc35eb643e8f 1093 cfg.d.ledWizUnitNo = DEFAULT_LEDWIZ_UNIT_NUMBER;
mjr 17:ab3cec0c8bf4 1094 cfg.d.plungerEnabled = true;
mjr 2:c174f9ee414a 1095 }
mjr 1:d913e0afb2ac 1096
mjr 6:cc35eb643e8f 1097 // Create the joystick USB client. Note that we use the LedWiz unit
mjr 6:cc35eb643e8f 1098 // number from the saved configuration.
mjr 6:cc35eb643e8f 1099 MyUSBJoystick js(
mjr 6:cc35eb643e8f 1100 USB_VENDOR_ID,
mjr 6:cc35eb643e8f 1101 USB_PRODUCT_ID | cfg.d.ledWizUnitNo,
mjr 6:cc35eb643e8f 1102 USB_VERSION_NO);
mjr 17:ab3cec0c8bf4 1103
mjr 17:ab3cec0c8bf4 1104 // last report timer - we use this to throttle reports, since VP
mjr 17:ab3cec0c8bf4 1105 // doesn't want to hear from us more than about every 10ms
mjr 17:ab3cec0c8bf4 1106 Timer reportTimer;
mjr 17:ab3cec0c8bf4 1107 reportTimer.start();
mjr 17:ab3cec0c8bf4 1108
mjr 17:ab3cec0c8bf4 1109 // initialize the calibration buttons, if present
mjr 17:ab3cec0c8bf4 1110 DigitalIn *calBtn = (CAL_BUTTON_PIN == NC ? 0 : new DigitalIn(CAL_BUTTON_PIN));
mjr 17:ab3cec0c8bf4 1111 DigitalOut *calBtnLed = (CAL_BUTTON_LED == NC ? 0 : new DigitalOut(CAL_BUTTON_LED));
mjr 6:cc35eb643e8f 1112
mjr 1:d913e0afb2ac 1113 // plunger calibration button debounce timer
mjr 1:d913e0afb2ac 1114 Timer calBtnTimer;
mjr 1:d913e0afb2ac 1115 calBtnTimer.start();
mjr 1:d913e0afb2ac 1116 int calBtnLit = false;
mjr 1:d913e0afb2ac 1117
mjr 1:d913e0afb2ac 1118 // Calibration button state:
mjr 1:d913e0afb2ac 1119 // 0 = not pushed
mjr 1:d913e0afb2ac 1120 // 1 = pushed, not yet debounced
mjr 1:d913e0afb2ac 1121 // 2 = pushed, debounced, waiting for hold time
mjr 1:d913e0afb2ac 1122 // 3 = pushed, hold time completed - in calibration mode
mjr 1:d913e0afb2ac 1123 int calBtnState = 0;
mjr 1:d913e0afb2ac 1124
mjr 1:d913e0afb2ac 1125 // set up a timer for our heartbeat indicator
mjr 1:d913e0afb2ac 1126 Timer hbTimer;
mjr 1:d913e0afb2ac 1127 hbTimer.start();
mjr 1:d913e0afb2ac 1128 int hb = 0;
mjr 5:a70c0bce770d 1129 uint16_t hbcnt = 0;
mjr 1:d913e0afb2ac 1130
mjr 1:d913e0afb2ac 1131 // set a timer for accelerometer auto-centering
mjr 1:d913e0afb2ac 1132 Timer acTimer;
mjr 1:d913e0afb2ac 1133 acTimer.start();
mjr 1:d913e0afb2ac 1134
mjr 0:5acbbe3f4cf4 1135 // create the accelerometer object
mjr 5:a70c0bce770d 1136 Accel accel(MMA8451_SCL_PIN, MMA8451_SDA_PIN, MMA8451_I2C_ADDRESS, MMA8451_INT_PIN);
mjr 0:5acbbe3f4cf4 1137
mjr 17:ab3cec0c8bf4 1138 // last accelerometer report, in joystick units (we report the nudge
mjr 17:ab3cec0c8bf4 1139 // acceleration via the joystick x & y axes, per the VP convention)
mjr 17:ab3cec0c8bf4 1140 int x = 0, y = 0;
mjr 17:ab3cec0c8bf4 1141
mjr 17:ab3cec0c8bf4 1142 // create our plunger sensor object
mjr 17:ab3cec0c8bf4 1143 PlungerSensor plungerSensor;
mjr 17:ab3cec0c8bf4 1144
mjr 17:ab3cec0c8bf4 1145 // last plunger report position, in 'npix' normalized pixel units
mjr 17:ab3cec0c8bf4 1146 int pos = 0;
mjr 17:ab3cec0c8bf4 1147
mjr 17:ab3cec0c8bf4 1148 // last plunger report, in joystick units (we report the plunger as the
mjr 17:ab3cec0c8bf4 1149 // "z" axis of the joystick, per the VP convention)
mjr 17:ab3cec0c8bf4 1150 int z = 0;
mjr 17:ab3cec0c8bf4 1151
mjr 17:ab3cec0c8bf4 1152 // most recent prior plunger readings, for tracking release events(z0 is
mjr 17:ab3cec0c8bf4 1153 // reading just before the last one we reported, z1 is the one before that,
mjr 17:ab3cec0c8bf4 1154 // z2 the next before that)
mjr 17:ab3cec0c8bf4 1155 int z0 = 0, z1 = 0, z2 = 0;
mjr 17:ab3cec0c8bf4 1156
mjr 17:ab3cec0c8bf4 1157 // Simulated "bounce" position when firing. We model the bounce off of
mjr 17:ab3cec0c8bf4 1158 // the barrel spring when the plunger is released as proportional to the
mjr 17:ab3cec0c8bf4 1159 // distance it was retracted just before being released.
mjr 17:ab3cec0c8bf4 1160 int zBounce = 0;
mjr 2:c174f9ee414a 1161
mjr 17:ab3cec0c8bf4 1162 // Simulated Launch Ball button state. If a "ZB Launch Ball" port is
mjr 17:ab3cec0c8bf4 1163 // defined for our LedWiz port mapping, any time that port is turned ON,
mjr 17:ab3cec0c8bf4 1164 // we'll simulate pushing the Launch Ball button if the player pulls
mjr 17:ab3cec0c8bf4 1165 // back and releases the plunger, or simply pushes on the plunger from
mjr 17:ab3cec0c8bf4 1166 // the rest position. This allows the plunger to be used in lieu of a
mjr 17:ab3cec0c8bf4 1167 // physical Launch Ball button for tables that don't have plungers.
mjr 17:ab3cec0c8bf4 1168 //
mjr 17:ab3cec0c8bf4 1169 // States:
mjr 17:ab3cec0c8bf4 1170 // 0 = default
mjr 17:ab3cec0c8bf4 1171 // 1 = cocked (plunger has been pulled back about 1" from state 0)
mjr 17:ab3cec0c8bf4 1172 // 2 = uncocked (plunger is pulled back less than 1" from state 1)
mjr 17:ab3cec0c8bf4 1173 // 3 = launching (plunger has been released from state 1 or 2, or
mjr 17:ab3cec0c8bf4 1174 // pushed forward about 1/4" from state 0)
mjr 17:ab3cec0c8bf4 1175 // 4 = launching, plunger is no longer pushed forward
mjr 17:ab3cec0c8bf4 1176 int lbState = 0;
mjr 6:cc35eb643e8f 1177
mjr 17:ab3cec0c8bf4 1178 // Time since last lbState transition. Some of the states are time-
mjr 17:ab3cec0c8bf4 1179 // sensitive. In the "uncocked" state, we'll return to state 0 if
mjr 17:ab3cec0c8bf4 1180 // we remain in this state for more than a few milliseconds, since
mjr 17:ab3cec0c8bf4 1181 // it indicates that the plunger is being slowly returned to rest
mjr 17:ab3cec0c8bf4 1182 // rather than released. In the "launching" state, we need to release
mjr 17:ab3cec0c8bf4 1183 // the Launch Ball button after a moment, and we need to wait for
mjr 17:ab3cec0c8bf4 1184 // the plunger to come to rest before returning to state 0.
mjr 17:ab3cec0c8bf4 1185 Timer lbTimer;
mjr 17:ab3cec0c8bf4 1186 lbTimer.start();
mjr 17:ab3cec0c8bf4 1187
mjr 17:ab3cec0c8bf4 1188 // Simulated button states. This is a vector of button states
mjr 17:ab3cec0c8bf4 1189 // for the simulated buttons. We combine this with the physical
mjr 17:ab3cec0c8bf4 1190 // button states on each USB joystick report, so we will report
mjr 17:ab3cec0c8bf4 1191 // a button as pressed if either the physical button is being pressed
mjr 17:ab3cec0c8bf4 1192 // or we're simulating a press on the button. This is used for the
mjr 17:ab3cec0c8bf4 1193 // simulated Launch Ball button.
mjr 17:ab3cec0c8bf4 1194 uint32_t simButtons = 0;
mjr 6:cc35eb643e8f 1195
mjr 6:cc35eb643e8f 1196 // Firing in progress: we set this when we detect the start of rapid
mjr 6:cc35eb643e8f 1197 // plunger movement from a retracted position towards the rest position.
mjr 17:ab3cec0c8bf4 1198 //
mjr 17:ab3cec0c8bf4 1199 // When we detect a firing event, we send VP a series of synthetic
mjr 17:ab3cec0c8bf4 1200 // reports simulating the idealized plunger motion. The actual physical
mjr 17:ab3cec0c8bf4 1201 // motion is much too fast to report to VP; in the time between two USB
mjr 17:ab3cec0c8bf4 1202 // reports, the plunger can shoot all the way forward, rebound off of
mjr 17:ab3cec0c8bf4 1203 // the barrel spring, bounce back part way, and bounce forward again,
mjr 17:ab3cec0c8bf4 1204 // or even do all of this more than once. This means that sampling the
mjr 17:ab3cec0c8bf4 1205 // physical motion at the USB report rate would create a misleading
mjr 17:ab3cec0c8bf4 1206 // picture of the plunger motion, since our samples would catch the
mjr 17:ab3cec0c8bf4 1207 // plunger at random points in this oscillating motion. From the
mjr 17:ab3cec0c8bf4 1208 // user's perspective, the physical action that occurred is simply that
mjr 17:ab3cec0c8bf4 1209 // the plunger was released from a particular distance, so it's this
mjr 17:ab3cec0c8bf4 1210 // high-level event that we want to convey to VP. To do this, we
mjr 17:ab3cec0c8bf4 1211 // synthesize a series of reports to convey an idealized version of
mjr 17:ab3cec0c8bf4 1212 // the release motion that's perfectly synchronized to the VP reports.
mjr 17:ab3cec0c8bf4 1213 // Essentially we pretend that our USB position samples are exactly
mjr 17:ab3cec0c8bf4 1214 // aligned in time with (1) the point of retraction just before the
mjr 17:ab3cec0c8bf4 1215 // user released the plunger, (2) the point of maximum forward motion
mjr 17:ab3cec0c8bf4 1216 // just after the user released the plunger (the point of maximum
mjr 17:ab3cec0c8bf4 1217 // compression as the plunger bounces off of the barrel spring), and
mjr 17:ab3cec0c8bf4 1218 // (3) the plunger coming to rest at the park position. This series
mjr 17:ab3cec0c8bf4 1219 // of reports is synthetic in the sense that it's not what we actually
mjr 17:ab3cec0c8bf4 1220 // see on the CCD at the times of these reports - the true plunger
mjr 17:ab3cec0c8bf4 1221 // position is oscillating at high speed during this period. But at
mjr 17:ab3cec0c8bf4 1222 // the same time it conveys a more faithful picture of the true physical
mjr 17:ab3cec0c8bf4 1223 // motion to VP, and allows VP to reproduce the true physical motion
mjr 17:ab3cec0c8bf4 1224 // more faithfully in its simulation model, by correcting for the
mjr 17:ab3cec0c8bf4 1225 // relatively low sampling rate in the communication path between the
mjr 17:ab3cec0c8bf4 1226 // real plunger and VP's model plunger.
mjr 17:ab3cec0c8bf4 1227 //
mjr 17:ab3cec0c8bf4 1228 // If 'firing' is non-zero, it's the index of our current report in
mjr 17:ab3cec0c8bf4 1229 // the synthetic firing report series.
mjr 9:fd65b0a94720 1230 int firing = 0;
mjr 2:c174f9ee414a 1231
mjr 2:c174f9ee414a 1232 // start the first CCD integration cycle
mjr 17:ab3cec0c8bf4 1233 plungerSensor.init();
mjr 9:fd65b0a94720 1234
mjr 9:fd65b0a94720 1235 // Device status. We report this on each update so that the host config
mjr 9:fd65b0a94720 1236 // tool can detect our current settings. This is a bit mask consisting
mjr 9:fd65b0a94720 1237 // of these bits:
mjr 9:fd65b0a94720 1238 // 0x01 -> plunger sensor enabled
mjr 17:ab3cec0c8bf4 1239 uint16_t statusFlags = (cfg.d.plungerEnabled ? 0x01 : 0x00);
mjr 10:976666ffa4ef 1240
mjr 10:976666ffa4ef 1241 // flag: send a pixel dump after the next read
mjr 10:976666ffa4ef 1242 bool reportPix = false;
mjr 1:d913e0afb2ac 1243
mjr 1:d913e0afb2ac 1244 // we're all set up - now just loop, processing sensor reports and
mjr 1:d913e0afb2ac 1245 // host requests
mjr 0:5acbbe3f4cf4 1246 for (;;)
mjr 0:5acbbe3f4cf4 1247 {
mjr 0:5acbbe3f4cf4 1248 // Look for an incoming report. Continue processing input as
mjr 0:5acbbe3f4cf4 1249 // long as there's anything pending - this ensures that we
mjr 0:5acbbe3f4cf4 1250 // handle input in as timely a fashion as possible by deferring
mjr 0:5acbbe3f4cf4 1251 // output tasks as long as there's input to process.
mjr 0:5acbbe3f4cf4 1252 HID_REPORT report;
mjr 6:cc35eb643e8f 1253 while (js.readNB(&report))
mjr 0:5acbbe3f4cf4 1254 {
mjr 6:cc35eb643e8f 1255 // all Led-Wiz reports are 8 bytes exactly
mjr 6:cc35eb643e8f 1256 if (report.length == 8)
mjr 1:d913e0afb2ac 1257 {
mjr 6:cc35eb643e8f 1258 uint8_t *data = report.data;
mjr 6:cc35eb643e8f 1259 if (data[0] == 64)
mjr 0:5acbbe3f4cf4 1260 {
mjr 6:cc35eb643e8f 1261 // LWZ-SBA - first four bytes are bit-packed on/off flags
mjr 6:cc35eb643e8f 1262 // for the outputs; 5th byte is the pulse speed (0-7)
mjr 6:cc35eb643e8f 1263 //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n",
mjr 6:cc35eb643e8f 1264 // data[1], data[2], data[3], data[4], data[5]);
mjr 0:5acbbe3f4cf4 1265
mjr 6:cc35eb643e8f 1266 // update all on/off states
mjr 6:cc35eb643e8f 1267 for (int i = 0, bit = 1, ri = 1 ; i < 32 ; ++i, bit <<= 1)
mjr 6:cc35eb643e8f 1268 {
mjr 6:cc35eb643e8f 1269 if (bit == 0x100) {
mjr 6:cc35eb643e8f 1270 bit = 1;
mjr 6:cc35eb643e8f 1271 ++ri;
mjr 6:cc35eb643e8f 1272 }
mjr 6:cc35eb643e8f 1273 wizOn[i] = ((data[ri] & bit) != 0);
mjr 6:cc35eb643e8f 1274 }
mjr 6:cc35eb643e8f 1275
mjr 6:cc35eb643e8f 1276 // update the physical outputs
mjr 1:d913e0afb2ac 1277 updateWizOuts();
mjr 6:cc35eb643e8f 1278
mjr 6:cc35eb643e8f 1279 // reset the PBA counter
mjr 6:cc35eb643e8f 1280 pbaIdx = 0;
mjr 6:cc35eb643e8f 1281 }
mjr 6:cc35eb643e8f 1282 else if (data[0] == 65)
mjr 6:cc35eb643e8f 1283 {
mjr 6:cc35eb643e8f 1284 // Private control message. This isn't an LedWiz message - it's
mjr 6:cc35eb643e8f 1285 // an extension for this device. 65 is an invalid PBA setting,
mjr 6:cc35eb643e8f 1286 // and isn't used for any other LedWiz message, so we appropriate
mjr 6:cc35eb643e8f 1287 // it for our own private use. The first byte specifies the
mjr 6:cc35eb643e8f 1288 // message type.
mjr 6:cc35eb643e8f 1289 if (data[1] == 1)
mjr 6:cc35eb643e8f 1290 {
mjr 9:fd65b0a94720 1291 // 1 = Set Configuration:
mjr 6:cc35eb643e8f 1292 // data[2] = LedWiz unit number (0x00 to 0x0f)
mjr 6:cc35eb643e8f 1293 // data[3] = feature enable bit mask:
mjr 6:cc35eb643e8f 1294 // 0x01 = enable CCD
mjr 6:cc35eb643e8f 1295
mjr 6:cc35eb643e8f 1296 // we'll need a reset if the LedWiz unit number is changing
mjr 6:cc35eb643e8f 1297 uint8_t newUnitNo = data[2] & 0x0f;
mjr 6:cc35eb643e8f 1298 needReset |= (newUnitNo != cfg.d.ledWizUnitNo);
mjr 6:cc35eb643e8f 1299
mjr 6:cc35eb643e8f 1300 // set the configuration parameters from the message
mjr 6:cc35eb643e8f 1301 cfg.d.ledWizUnitNo = newUnitNo;
mjr 17:ab3cec0c8bf4 1302 cfg.d.plungerEnabled = data[3] & 0x01;
mjr 6:cc35eb643e8f 1303
mjr 9:fd65b0a94720 1304 // update the status flags
mjr 9:fd65b0a94720 1305 statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01);
mjr 9:fd65b0a94720 1306
mjr 9:fd65b0a94720 1307 // if the ccd is no longer enabled, use 0 for z reports
mjr 17:ab3cec0c8bf4 1308 if (!cfg.d.plungerEnabled)
mjr 9:fd65b0a94720 1309 z = 0;
mjr 9:fd65b0a94720 1310
mjr 6:cc35eb643e8f 1311 // save the configuration
mjr 6:cc35eb643e8f 1312 cfg.save(iap, flash_addr);
mjr 6:cc35eb643e8f 1313 }
mjr 9:fd65b0a94720 1314 else if (data[1] == 2)
mjr 9:fd65b0a94720 1315 {
mjr 9:fd65b0a94720 1316 // 2 = Calibrate plunger
mjr 9:fd65b0a94720 1317 // (No parameters)
mjr 9:fd65b0a94720 1318
mjr 9:fd65b0a94720 1319 // enter calibration mode
mjr 9:fd65b0a94720 1320 calBtnState = 3;
mjr 9:fd65b0a94720 1321 calBtnTimer.reset();
mjr 9:fd65b0a94720 1322 cfg.resetPlunger();
mjr 9:fd65b0a94720 1323 }
mjr 10:976666ffa4ef 1324 else if (data[1] == 3)
mjr 10:976666ffa4ef 1325 {
mjr 10:976666ffa4ef 1326 // 3 = pixel dump
mjr 10:976666ffa4ef 1327 // (No parameters)
mjr 10:976666ffa4ef 1328 reportPix = true;
mjr 10:976666ffa4ef 1329
mjr 10:976666ffa4ef 1330 // show purple until we finish sending the report
mjr 10:976666ffa4ef 1331 ledR = 0;
mjr 10:976666ffa4ef 1332 ledB = 0;
mjr 10:976666ffa4ef 1333 ledG = 1;
mjr 10:976666ffa4ef 1334 }
mjr 6:cc35eb643e8f 1335 }
mjr 6:cc35eb643e8f 1336 else
mjr 6:cc35eb643e8f 1337 {
mjr 6:cc35eb643e8f 1338 // LWZ-PBA - full state dump; each byte is one output
mjr 6:cc35eb643e8f 1339 // in the current bank. pbaIdx keeps track of the bank;
mjr 6:cc35eb643e8f 1340 // this is incremented implicitly by each PBA message.
mjr 6:cc35eb643e8f 1341 //printf("LWZ-PBA[%d] %02x %02x %02x %02x %02x %02x %02x %02x\r\n",
mjr 6:cc35eb643e8f 1342 // pbaIdx, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
mjr 6:cc35eb643e8f 1343
mjr 6:cc35eb643e8f 1344 // update all output profile settings
mjr 6:cc35eb643e8f 1345 for (int i = 0 ; i < 8 ; ++i)
mjr 6:cc35eb643e8f 1346 wizVal[pbaIdx + i] = data[i];
mjr 6:cc35eb643e8f 1347
mjr 6:cc35eb643e8f 1348 // update the physical LED state if this is the last bank
mjr 6:cc35eb643e8f 1349 if (pbaIdx == 24)
mjr 13:72dda449c3c0 1350 {
mjr 6:cc35eb643e8f 1351 updateWizOuts();
mjr 13:72dda449c3c0 1352 pbaIdx = 0;
mjr 13:72dda449c3c0 1353 }
mjr 13:72dda449c3c0 1354 else
mjr 13:72dda449c3c0 1355 pbaIdx += 8;
mjr 6:cc35eb643e8f 1356 }
mjr 0:5acbbe3f4cf4 1357 }
mjr 0:5acbbe3f4cf4 1358 }
mjr 1:d913e0afb2ac 1359
mjr 1:d913e0afb2ac 1360 // check for plunger calibration
mjr 17:ab3cec0c8bf4 1361 if (calBtn != 0 && !calBtn->read())
mjr 0:5acbbe3f4cf4 1362 {
mjr 1:d913e0afb2ac 1363 // check the state
mjr 1:d913e0afb2ac 1364 switch (calBtnState)
mjr 0:5acbbe3f4cf4 1365 {
mjr 1:d913e0afb2ac 1366 case 0:
mjr 1:d913e0afb2ac 1367 // button not yet pushed - start debouncing
mjr 1:d913e0afb2ac 1368 calBtnTimer.reset();
mjr 1:d913e0afb2ac 1369 calBtnState = 1;
mjr 1:d913e0afb2ac 1370 break;
mjr 1:d913e0afb2ac 1371
mjr 1:d913e0afb2ac 1372 case 1:
mjr 1:d913e0afb2ac 1373 // pushed, not yet debounced - if the debounce time has
mjr 1:d913e0afb2ac 1374 // passed, start the hold period
mjr 9:fd65b0a94720 1375 if (calBtnTimer.read_ms() > 50)
mjr 1:d913e0afb2ac 1376 calBtnState = 2;
mjr 1:d913e0afb2ac 1377 break;
mjr 1:d913e0afb2ac 1378
mjr 1:d913e0afb2ac 1379 case 2:
mjr 1:d913e0afb2ac 1380 // in the hold period - if the button has been held down
mjr 1:d913e0afb2ac 1381 // for the entire hold period, move to calibration mode
mjr 9:fd65b0a94720 1382 if (calBtnTimer.read_ms() > 2050)
mjr 1:d913e0afb2ac 1383 {
mjr 1:d913e0afb2ac 1384 // enter calibration mode
mjr 1:d913e0afb2ac 1385 calBtnState = 3;
mjr 9:fd65b0a94720 1386 calBtnTimer.reset();
mjr 9:fd65b0a94720 1387 cfg.resetPlunger();
mjr 1:d913e0afb2ac 1388 }
mjr 1:d913e0afb2ac 1389 break;
mjr 2:c174f9ee414a 1390
mjr 2:c174f9ee414a 1391 case 3:
mjr 9:fd65b0a94720 1392 // Already in calibration mode - pushing the button here
mjr 9:fd65b0a94720 1393 // doesn't change the current state, but we won't leave this
mjr 9:fd65b0a94720 1394 // state as long as it's held down. So nothing changes here.
mjr 2:c174f9ee414a 1395 break;
mjr 0:5acbbe3f4cf4 1396 }
mjr 0:5acbbe3f4cf4 1397 }
mjr 1:d913e0afb2ac 1398 else
mjr 1:d913e0afb2ac 1399 {
mjr 2:c174f9ee414a 1400 // Button released. If we're in calibration mode, and
mjr 2:c174f9ee414a 1401 // the calibration time has elapsed, end the calibration
mjr 2:c174f9ee414a 1402 // and save the results to flash.
mjr 2:c174f9ee414a 1403 //
mjr 2:c174f9ee414a 1404 // Otherwise, return to the base state without saving anything.
mjr 2:c174f9ee414a 1405 // If the button is released before we make it to calibration
mjr 2:c174f9ee414a 1406 // mode, it simply cancels the attempt.
mjr 9:fd65b0a94720 1407 if (calBtnState == 3 && calBtnTimer.read_ms() > 15000)
mjr 2:c174f9ee414a 1408 {
mjr 2:c174f9ee414a 1409 // exit calibration mode
mjr 1:d913e0afb2ac 1410 calBtnState = 0;
mjr 2:c174f9ee414a 1411
mjr 6:cc35eb643e8f 1412 // save the updated configuration
mjr 6:cc35eb643e8f 1413 cfg.d.plungerCal = 1;
mjr 6:cc35eb643e8f 1414 cfg.save(iap, flash_addr);
mjr 2:c174f9ee414a 1415
mjr 2:c174f9ee414a 1416 // the flash state is now valid
mjr 2:c174f9ee414a 1417 flash_valid = true;
mjr 2:c174f9ee414a 1418 }
mjr 2:c174f9ee414a 1419 else if (calBtnState != 3)
mjr 2:c174f9ee414a 1420 {
mjr 2:c174f9ee414a 1421 // didn't make it to calibration mode - cancel the operation
mjr 1:d913e0afb2ac 1422 calBtnState = 0;
mjr 2:c174f9ee414a 1423 }
mjr 1:d913e0afb2ac 1424 }
mjr 1:d913e0afb2ac 1425
mjr 1:d913e0afb2ac 1426 // light/flash the calibration button light, if applicable
mjr 1:d913e0afb2ac 1427 int newCalBtnLit = calBtnLit;
mjr 1:d913e0afb2ac 1428 switch (calBtnState)
mjr 0:5acbbe3f4cf4 1429 {
mjr 1:d913e0afb2ac 1430 case 2:
mjr 1:d913e0afb2ac 1431 // in the hold period - flash the light
mjr 9:fd65b0a94720 1432 newCalBtnLit = ((calBtnTimer.read_ms()/250) & 1);
mjr 1:d913e0afb2ac 1433 break;
mjr 1:d913e0afb2ac 1434
mjr 1:d913e0afb2ac 1435 case 3:
mjr 1:d913e0afb2ac 1436 // calibration mode - show steady on
mjr 1:d913e0afb2ac 1437 newCalBtnLit = true;
mjr 1:d913e0afb2ac 1438 break;
mjr 1:d913e0afb2ac 1439
mjr 1:d913e0afb2ac 1440 default:
mjr 1:d913e0afb2ac 1441 // not calibrating/holding - show steady off
mjr 1:d913e0afb2ac 1442 newCalBtnLit = false;
mjr 1:d913e0afb2ac 1443 break;
mjr 1:d913e0afb2ac 1444 }
mjr 3:3514575d4f86 1445
mjr 3:3514575d4f86 1446 // light or flash the external calibration button LED, and
mjr 3:3514575d4f86 1447 // do the same with the on-board blue LED
mjr 1:d913e0afb2ac 1448 if (calBtnLit != newCalBtnLit)
mjr 1:d913e0afb2ac 1449 {
mjr 1:d913e0afb2ac 1450 calBtnLit = newCalBtnLit;
mjr 2:c174f9ee414a 1451 if (calBtnLit) {
mjr 17:ab3cec0c8bf4 1452 if (calBtnLed != 0)
mjr 17:ab3cec0c8bf4 1453 calBtnLed->write(1);
mjr 4:02c7cd7b2183 1454 ledR = 1;
mjr 4:02c7cd7b2183 1455 ledG = 1;
mjr 9:fd65b0a94720 1456 ledB = 0;
mjr 2:c174f9ee414a 1457 }
mjr 2:c174f9ee414a 1458 else {
mjr 17:ab3cec0c8bf4 1459 if (calBtnLed != 0)
mjr 17:ab3cec0c8bf4 1460 calBtnLed->write(0);
mjr 4:02c7cd7b2183 1461 ledR = 1;
mjr 4:02c7cd7b2183 1462 ledG = 1;
mjr 9:fd65b0a94720 1463 ledB = 1;
mjr 2:c174f9ee414a 1464 }
mjr 1:d913e0afb2ac 1465 }
mjr 1:d913e0afb2ac 1466
mjr 17:ab3cec0c8bf4 1467 // If the plunger is enabled, and we're not already in a firing event,
mjr 17:ab3cec0c8bf4 1468 // and the last plunger reading had the plunger pulled back at least
mjr 17:ab3cec0c8bf4 1469 // a bit, watch for plunger release events until it's time for our next
mjr 17:ab3cec0c8bf4 1470 // USB report.
mjr 17:ab3cec0c8bf4 1471 if (!firing && cfg.d.plungerEnabled && z >= JOYMAX/6)
mjr 17:ab3cec0c8bf4 1472 {
mjr 17:ab3cec0c8bf4 1473 // monitor the plunger until it's time for our next report
mjr 17:ab3cec0c8bf4 1474 while (reportTimer.read_ms() < 15)
mjr 17:ab3cec0c8bf4 1475 {
mjr 17:ab3cec0c8bf4 1476 // do a fast low-res scan; if it's at or past the zero point,
mjr 17:ab3cec0c8bf4 1477 // start a firing event
mjr 17:ab3cec0c8bf4 1478 if (plungerSensor.lowResScan() <= cfg.d.plungerZero)
mjr 17:ab3cec0c8bf4 1479 firing = 1;
mjr 17:ab3cec0c8bf4 1480 }
mjr 17:ab3cec0c8bf4 1481 }
mjr 17:ab3cec0c8bf4 1482
mjr 6:cc35eb643e8f 1483 // read the plunger sensor, if it's enabled
mjr 17:ab3cec0c8bf4 1484 if (cfg.d.plungerEnabled)
mjr 6:cc35eb643e8f 1485 {
mjr 6:cc35eb643e8f 1486 // start with the previous reading, in case we don't have a
mjr 6:cc35eb643e8f 1487 // clear result on this frame
mjr 6:cc35eb643e8f 1488 int znew = z;
mjr 17:ab3cec0c8bf4 1489 if (plungerSensor.highResScan(pos))
mjr 6:cc35eb643e8f 1490 {
mjr 17:ab3cec0c8bf4 1491 // We got a new reading. If we're in calibration mode, use it
mjr 17:ab3cec0c8bf4 1492 // to figure the new calibration, otherwise adjust the new reading
mjr 17:ab3cec0c8bf4 1493 // for the established calibration.
mjr 17:ab3cec0c8bf4 1494 if (calBtnState == 3)
mjr 6:cc35eb643e8f 1495 {
mjr 17:ab3cec0c8bf4 1496 // Calibration mode. If this reading is outside of the current
mjr 17:ab3cec0c8bf4 1497 // calibration bounds, expand the bounds.
mjr 17:ab3cec0c8bf4 1498 if (pos < cfg.d.plungerMin)
mjr 17:ab3cec0c8bf4 1499 cfg.d.plungerMin = pos;
mjr 17:ab3cec0c8bf4 1500 if (pos < cfg.d.plungerZero)
mjr 17:ab3cec0c8bf4 1501 cfg.d.plungerZero = pos;
mjr 17:ab3cec0c8bf4 1502 if (pos > cfg.d.plungerMax)
mjr 17:ab3cec0c8bf4 1503 cfg.d.plungerMax = pos;
mjr 6:cc35eb643e8f 1504
mjr 17:ab3cec0c8bf4 1505 // normalize to the full physical range while calibrating
mjr 17:ab3cec0c8bf4 1506 znew = int(round(float(pos)/npix * JOYMAX));
mjr 17:ab3cec0c8bf4 1507 }
mjr 17:ab3cec0c8bf4 1508 else
mjr 17:ab3cec0c8bf4 1509 {
mjr 17:ab3cec0c8bf4 1510 // Not in calibration mode, so normalize the new reading to the
mjr 17:ab3cec0c8bf4 1511 // established calibration range.
mjr 17:ab3cec0c8bf4 1512 //
mjr 17:ab3cec0c8bf4 1513 // Note that negative values are allowed. Zero represents the
mjr 17:ab3cec0c8bf4 1514 // "park" position, where the plunger sits when at rest. A mechanical
mjr 17:ab3cec0c8bf4 1515 // plunger has a smmall amount of travel in the "push" direction,
mjr 17:ab3cec0c8bf4 1516 // since the barrel spring can be compressed slightly. Negative
mjr 17:ab3cec0c8bf4 1517 // values represent travel in the push direction.
mjr 17:ab3cec0c8bf4 1518 if (pos > cfg.d.plungerMax)
mjr 17:ab3cec0c8bf4 1519 pos = cfg.d.plungerMax;
mjr 17:ab3cec0c8bf4 1520 znew = int(round(float(pos - cfg.d.plungerZero)
mjr 17:ab3cec0c8bf4 1521 / (cfg.d.plungerMax - cfg.d.plungerZero + 1) * JOYMAX));
mjr 6:cc35eb643e8f 1522 }
mjr 6:cc35eb643e8f 1523 }
mjr 7:100a25f8bf56 1524
mjr 17:ab3cec0c8bf4 1525 // If we're not already in a firing event, check to see if the
mjr 17:ab3cec0c8bf4 1526 // new position is forward of the last report. If it is, a firing
mjr 17:ab3cec0c8bf4 1527 // event might have started during the high-res scan. This might
mjr 17:ab3cec0c8bf4 1528 // seem unlikely given that the scan only takes about 5ms, but that
mjr 17:ab3cec0c8bf4 1529 // 5ms represents about 25-30% of our total time between reports,
mjr 17:ab3cec0c8bf4 1530 // there's about a 1 in 4 chance that a release starts during a
mjr 17:ab3cec0c8bf4 1531 // scan.
mjr 17:ab3cec0c8bf4 1532 if (!firing && z0 > 0 && znew < z0)
mjr 17:ab3cec0c8bf4 1533 {
mjr 17:ab3cec0c8bf4 1534 // The plunger has moved forward since the previous report.
mjr 17:ab3cec0c8bf4 1535 // Watch it for a few more ms to see if we can get a stable
mjr 17:ab3cec0c8bf4 1536 // new position.
mjr 17:ab3cec0c8bf4 1537 int pos1 = plungerSensor.lowResScan();
mjr 17:ab3cec0c8bf4 1538 Timer tw;
mjr 17:ab3cec0c8bf4 1539 tw.start();
mjr 17:ab3cec0c8bf4 1540 while (tw.read_ms() < 6)
mjr 17:ab3cec0c8bf4 1541 {
mjr 17:ab3cec0c8bf4 1542 // if we've crossed the rest position, it's a firing event
mjr 17:ab3cec0c8bf4 1543 if (pos1 < cfg.d.plungerZero)
mjr 17:ab3cec0c8bf4 1544 {
mjr 17:ab3cec0c8bf4 1545 firing = 1;
mjr 17:ab3cec0c8bf4 1546 break;
mjr 17:ab3cec0c8bf4 1547 }
mjr 17:ab3cec0c8bf4 1548
mjr 17:ab3cec0c8bf4 1549 // read the new position
mjr 17:ab3cec0c8bf4 1550 int pos2 = plungerSensor.lowResScan();
mjr 17:ab3cec0c8bf4 1551
mjr 17:ab3cec0c8bf4 1552 // if it's stable, stop looping
mjr 17:ab3cec0c8bf4 1553 if (abs(pos2 - pos1) < int(npix/(3.2*8)))
mjr 17:ab3cec0c8bf4 1554 break;
mjr 17:ab3cec0c8bf4 1555
mjr 17:ab3cec0c8bf4 1556 // the new reading is now the prior reading
mjr 17:ab3cec0c8bf4 1557 pos1 = pos2;
mjr 17:ab3cec0c8bf4 1558 }
mjr 17:ab3cec0c8bf4 1559 }
mjr 17:ab3cec0c8bf4 1560
mjr 17:ab3cec0c8bf4 1561 // Check for a simulated Launch Ball button press, if enabled
mjr 17:ab3cec0c8bf4 1562 if (ZBLaunchBallPort != 0 && wizOn[ZBLaunchBallPort-1])
mjr 17:ab3cec0c8bf4 1563 {
mjr 17:ab3cec0c8bf4 1564 int newState = lbState;
mjr 17:ab3cec0c8bf4 1565 switch (lbState)
mjr 17:ab3cec0c8bf4 1566 {
mjr 17:ab3cec0c8bf4 1567 case 0:
mjr 17:ab3cec0c8bf4 1568 // Base state. If the plunger is pulled back by an inch
mjr 17:ab3cec0c8bf4 1569 // or more, go to "cocked" state. If the plunger is pushed
mjr 17:ab3cec0c8bf4 1570 // forward by 1/4" or more, go to "launch" state.
mjr 17:ab3cec0c8bf4 1571 if (znew >= JOYMAX/3)
mjr 17:ab3cec0c8bf4 1572 newState = 1;
mjr 17:ab3cec0c8bf4 1573 else if (znew < -JOYMAX/12)
mjr 17:ab3cec0c8bf4 1574 newState = 3;
mjr 17:ab3cec0c8bf4 1575 break;
mjr 17:ab3cec0c8bf4 1576
mjr 17:ab3cec0c8bf4 1577 case 1:
mjr 17:ab3cec0c8bf4 1578 // Cocked state. If a firing event is now in progress,
mjr 17:ab3cec0c8bf4 1579 // go to "launch" state. Otherwise, if the plunger is less
mjr 17:ab3cec0c8bf4 1580 // than 1" retracted, go to "uncocked" state - the player
mjr 17:ab3cec0c8bf4 1581 // might be slowly returning the plunger to rest so as not
mjr 17:ab3cec0c8bf4 1582 // to trigger a launch.
mjr 17:ab3cec0c8bf4 1583 if (firing || znew <= 0)
mjr 17:ab3cec0c8bf4 1584 newState = 3;
mjr 17:ab3cec0c8bf4 1585 else if (znew < JOYMAX/3)
mjr 17:ab3cec0c8bf4 1586 newState = 2;
mjr 17:ab3cec0c8bf4 1587 break;
mjr 17:ab3cec0c8bf4 1588
mjr 17:ab3cec0c8bf4 1589 case 2:
mjr 17:ab3cec0c8bf4 1590 // Uncocked state. If the plunger is more than an inch
mjr 17:ab3cec0c8bf4 1591 // retracted, return to cocked state. If we've been in
mjr 17:ab3cec0c8bf4 1592 // the uncocked state for more than half a second, return
mjr 17:ab3cec0c8bf4 1593 // to the base state.
mjr 17:ab3cec0c8bf4 1594 if (znew >= JOYMAX/3)
mjr 17:ab3cec0c8bf4 1595 newState = 1;
mjr 17:ab3cec0c8bf4 1596 else if (lbTimer.read_ms() > 500)
mjr 17:ab3cec0c8bf4 1597 newState = 0;
mjr 17:ab3cec0c8bf4 1598 break;
mjr 17:ab3cec0c8bf4 1599
mjr 17:ab3cec0c8bf4 1600 case 3:
mjr 17:ab3cec0c8bf4 1601 // Launch state. If the plunger is no longer pushed
mjr 17:ab3cec0c8bf4 1602 // forward, switch to launch rest state.
mjr 17:ab3cec0c8bf4 1603 if (znew > -JOYMAX/24)
mjr 17:ab3cec0c8bf4 1604 newState = 4;
mjr 17:ab3cec0c8bf4 1605 break;
mjr 17:ab3cec0c8bf4 1606
mjr 17:ab3cec0c8bf4 1607 case 4:
mjr 17:ab3cec0c8bf4 1608 // Launch rest state. If the plunger is pushed forward
mjr 17:ab3cec0c8bf4 1609 // again, switch back to launch state. If not, and we've
mjr 17:ab3cec0c8bf4 1610 // been in this state for at least 200ms, return to the
mjr 17:ab3cec0c8bf4 1611 // default state.
mjr 17:ab3cec0c8bf4 1612 if (znew < -JOYMAX/12)
mjr 17:ab3cec0c8bf4 1613 newState = 3;
mjr 17:ab3cec0c8bf4 1614 else if (lbTimer.read_ms() > 200)
mjr 17:ab3cec0c8bf4 1615 newState = 0;
mjr 17:ab3cec0c8bf4 1616 break;
mjr 17:ab3cec0c8bf4 1617 }
mjr 17:ab3cec0c8bf4 1618
mjr 17:ab3cec0c8bf4 1619 // change states if desired
mjr 17:ab3cec0c8bf4 1620 if (newState != lbState)
mjr 17:ab3cec0c8bf4 1621 {
mjr 17:ab3cec0c8bf4 1622 // if we're entering Launch state, press the Launch Ball button
mjr 17:ab3cec0c8bf4 1623 if (newState == 3 && lbState != 4)
mjr 17:ab3cec0c8bf4 1624 simButtons |= (1 << (LaunchBallButton - 1));
mjr 17:ab3cec0c8bf4 1625
mjr 17:ab3cec0c8bf4 1626 // if we're switching to state 0, release the button
mjr 17:ab3cec0c8bf4 1627 if (newState == 0)
mjr 17:ab3cec0c8bf4 1628 simButtons &= ~(1 << (LaunchBallButton - 1));
mjr 17:ab3cec0c8bf4 1629
mjr 17:ab3cec0c8bf4 1630 // switch to the new state
mjr 17:ab3cec0c8bf4 1631 lbState = newState;
mjr 17:ab3cec0c8bf4 1632
mjr 17:ab3cec0c8bf4 1633 // start timing in the new state
mjr 17:ab3cec0c8bf4 1634 lbTimer.reset();
mjr 17:ab3cec0c8bf4 1635 }
mjr 17:ab3cec0c8bf4 1636 }
mjr 17:ab3cec0c8bf4 1637
mjr 17:ab3cec0c8bf4 1638 // If a firing event is in progress, generate synthetic reports to
mjr 17:ab3cec0c8bf4 1639 // describe an idealized version of the plunger motion to VP rather
mjr 17:ab3cec0c8bf4 1640 // than reporting the actual physical plunger position.
mjr 6:cc35eb643e8f 1641 //
mjr 17:ab3cec0c8bf4 1642 // We use the synthetic reports during a release event because the
mjr 17:ab3cec0c8bf4 1643 // physical plunger motion when released is too fast for VP to track.
mjr 17:ab3cec0c8bf4 1644 // VP only syncs its internal physics model with the outside world
mjr 17:ab3cec0c8bf4 1645 // about every 10ms. In that amount of time, the plunger moves
mjr 17:ab3cec0c8bf4 1646 // fast enough when released that it can shoot all the way forward,
mjr 17:ab3cec0c8bf4 1647 // bounce off of the barrel spring, and rebound part of the way
mjr 17:ab3cec0c8bf4 1648 // back. The result is the classic analog-to-digital problem of
mjr 17:ab3cec0c8bf4 1649 // sample aliasing. If we happen to time our sample during the
mjr 17:ab3cec0c8bf4 1650 // release motion so that we catch the plunger at the peak of a
mjr 17:ab3cec0c8bf4 1651 // bounce, the digital signal incorrectly looks like the plunger
mjr 17:ab3cec0c8bf4 1652 // is moving slowly forward - VP thinks we went from fully
mjr 17:ab3cec0c8bf4 1653 // retracted to half retracted in the sample interval, whereas
mjr 17:ab3cec0c8bf4 1654 // we actually traveled all the way forward and half way back,
mjr 17:ab3cec0c8bf4 1655 // so the speed VP infers is about 1/3 of the actual speed.
mjr 9:fd65b0a94720 1656 //
mjr 17:ab3cec0c8bf4 1657 // To correct this, we take advantage of our ability to sample
mjr 17:ab3cec0c8bf4 1658 // the CCD image several times in the course of a VP report. If
mjr 17:ab3cec0c8bf4 1659 // we catch the plunger near the origin after we've seen it
mjr 17:ab3cec0c8bf4 1660 // retracted, we go into Release Event mode. During this mode,
mjr 17:ab3cec0c8bf4 1661 // we stop reporting the true physical plunger position, and
mjr 17:ab3cec0c8bf4 1662 // instead report an idealized pattern: we report the plunger
mjr 17:ab3cec0c8bf4 1663 // immediately shooting forward to a position in front of the
mjr 17:ab3cec0c8bf4 1664 // park position that's in proportion to how far back the plunger
mjr 17:ab3cec0c8bf4 1665 // was just before the release, and we then report it stationary
mjr 17:ab3cec0c8bf4 1666 // at the park position. We continue to report the stationary
mjr 17:ab3cec0c8bf4 1667 // park position until the actual physical plunger motion has
mjr 17:ab3cec0c8bf4 1668 // stabilized on a new position. We then exit Release Event
mjr 17:ab3cec0c8bf4 1669 // mode and return to reporting the true physical position.
mjr 17:ab3cec0c8bf4 1670 if (firing)
mjr 6:cc35eb643e8f 1671 {
mjr 17:ab3cec0c8bf4 1672 // Firing in progress. Keep reporting the park position
mjr 17:ab3cec0c8bf4 1673 // until the physical plunger position comes to rest.
mjr 17:ab3cec0c8bf4 1674 const int restTol = JOYMAX/24;
mjr 17:ab3cec0c8bf4 1675 if (firing == 1)
mjr 6:cc35eb643e8f 1676 {
mjr 17:ab3cec0c8bf4 1677 // For the first couple of frames, show the plunger shooting
mjr 17:ab3cec0c8bf4 1678 // forward past the zero point, to simulate the momentum carrying
mjr 17:ab3cec0c8bf4 1679 // it forward to bounce off of the barrel spring. Show the
mjr 17:ab3cec0c8bf4 1680 // bounce as proportional to the distance it was retracted
mjr 17:ab3cec0c8bf4 1681 // in the prior report.
mjr 17:ab3cec0c8bf4 1682 z = zBounce = -z0/6;
mjr 17:ab3cec0c8bf4 1683 ++firing;
mjr 6:cc35eb643e8f 1684 }
mjr 17:ab3cec0c8bf4 1685 else if (firing == 2)
mjr 9:fd65b0a94720 1686 {
mjr 17:ab3cec0c8bf4 1687 // second frame - keep the bounce a little longer
mjr 17:ab3cec0c8bf4 1688 z = zBounce;
mjr 17:ab3cec0c8bf4 1689 ++firing;
mjr 17:ab3cec0c8bf4 1690 }
mjr 17:ab3cec0c8bf4 1691 else if (firing > 4
mjr 17:ab3cec0c8bf4 1692 && abs(znew - z0) < restTol
mjr 17:ab3cec0c8bf4 1693 && abs(znew - z1) < restTol
mjr 17:ab3cec0c8bf4 1694 && abs(znew - z2) < restTol)
mjr 17:ab3cec0c8bf4 1695 {
mjr 17:ab3cec0c8bf4 1696 // The physical plunger has come to rest. Exit firing
mjr 17:ab3cec0c8bf4 1697 // mode and resume reporting the actual position.
mjr 17:ab3cec0c8bf4 1698 firing = false;
mjr 17:ab3cec0c8bf4 1699 z = znew;
mjr 9:fd65b0a94720 1700 }
mjr 9:fd65b0a94720 1701 else
mjr 9:fd65b0a94720 1702 {
mjr 17:ab3cec0c8bf4 1703 // until the physical plunger comes to rest, simply
mjr 17:ab3cec0c8bf4 1704 // report the park position
mjr 9:fd65b0a94720 1705 z = 0;
mjr 17:ab3cec0c8bf4 1706 ++firing;
mjr 9:fd65b0a94720 1707 }
mjr 6:cc35eb643e8f 1708 }
mjr 6:cc35eb643e8f 1709 else
mjr 6:cc35eb643e8f 1710 {
mjr 17:ab3cec0c8bf4 1711 // not in firing mode - report the true physical position
mjr 17:ab3cec0c8bf4 1712 z = znew;
mjr 6:cc35eb643e8f 1713 }
mjr 17:ab3cec0c8bf4 1714
mjr 17:ab3cec0c8bf4 1715 // shift the new reading into the recent history buffer
mjr 6:cc35eb643e8f 1716 z2 = z1;
mjr 6:cc35eb643e8f 1717 z1 = z0;
mjr 6:cc35eb643e8f 1718 z0 = znew;
mjr 2:c174f9ee414a 1719 }
mjr 6:cc35eb643e8f 1720
mjr 11:bd9da7088e6e 1721 // update the buttons
mjr 11:bd9da7088e6e 1722 uint32_t buttons = readButtonsDebounced();
mjr 17:ab3cec0c8bf4 1723
mjr 17:ab3cec0c8bf4 1724 // If it's been long enough since our last USB status report,
mjr 17:ab3cec0c8bf4 1725 // send the new report. We throttle the report rate because
mjr 17:ab3cec0c8bf4 1726 // it can overwhelm the PC side if we report too frequently.
mjr 17:ab3cec0c8bf4 1727 // VP only wants to sync with the real world in 10ms intervals,
mjr 17:ab3cec0c8bf4 1728 // so reporting more frequently only creates i/o overhead
mjr 17:ab3cec0c8bf4 1729 // without doing anything to improve the simulation.
mjr 17:ab3cec0c8bf4 1730 if (reportTimer.read_ms() > 15)
mjr 17:ab3cec0c8bf4 1731 {
mjr 17:ab3cec0c8bf4 1732 // read the accelerometer
mjr 17:ab3cec0c8bf4 1733 int xa, ya;
mjr 17:ab3cec0c8bf4 1734 accel.get(xa, ya);
mjr 17:ab3cec0c8bf4 1735
mjr 17:ab3cec0c8bf4 1736 // confine the results to our joystick axis range
mjr 17:ab3cec0c8bf4 1737 if (xa < -JOYMAX) xa = -JOYMAX;
mjr 17:ab3cec0c8bf4 1738 if (xa > JOYMAX) xa = JOYMAX;
mjr 17:ab3cec0c8bf4 1739 if (ya < -JOYMAX) ya = -JOYMAX;
mjr 17:ab3cec0c8bf4 1740 if (ya > JOYMAX) ya = JOYMAX;
mjr 17:ab3cec0c8bf4 1741
mjr 17:ab3cec0c8bf4 1742 // store the updated accelerometer coordinates
mjr 17:ab3cec0c8bf4 1743 x = xa;
mjr 17:ab3cec0c8bf4 1744 y = ya;
mjr 17:ab3cec0c8bf4 1745
mjr 17:ab3cec0c8bf4 1746 // Send the status report. Note that the nominal x and y axes
mjr 17:ab3cec0c8bf4 1747 // are reversed - this makes it more intuitive to set up in VP.
mjr 17:ab3cec0c8bf4 1748 // If we mount the Freesale card flat on the floor of the cabinet
mjr 17:ab3cec0c8bf4 1749 // with the USB connectors facing the front of the cabinet, this
mjr 17:ab3cec0c8bf4 1750 // arrangement of our nominal axes aligns with VP's standard
mjr 17:ab3cec0c8bf4 1751 // setting, so that we can configure VP with X Axis = X on the
mjr 17:ab3cec0c8bf4 1752 // joystick and Y Axis = Y on the joystick.
mjr 17:ab3cec0c8bf4 1753 js.update(y, x, z, buttons | simButtons, statusFlags);
mjr 17:ab3cec0c8bf4 1754
mjr 17:ab3cec0c8bf4 1755 // we've just started a new report interval, so reset the timer
mjr 17:ab3cec0c8bf4 1756 reportTimer.reset();
mjr 17:ab3cec0c8bf4 1757 }
mjr 1:d913e0afb2ac 1758
mjr 10:976666ffa4ef 1759 // If we're in pixel dump mode, report all pixel exposure values
mjr 10:976666ffa4ef 1760 if (reportPix)
mjr 10:976666ffa4ef 1761 {
mjr 17:ab3cec0c8bf4 1762 // send the report
mjr 17:ab3cec0c8bf4 1763 plungerSensor.sendExposureReport(js);
mjr 17:ab3cec0c8bf4 1764
mjr 10:976666ffa4ef 1765 // we have satisfied this request
mjr 10:976666ffa4ef 1766 reportPix = false;
mjr 10:976666ffa4ef 1767 }
mjr 10:976666ffa4ef 1768
mjr 6:cc35eb643e8f 1769 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 1770 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 1771 printf("%d,%d\r\n", x, y);
mjr 6:cc35eb643e8f 1772 #endif
mjr 6:cc35eb643e8f 1773
mjr 6:cc35eb643e8f 1774 // provide a visual status indication on the on-board LED
mjr 5:a70c0bce770d 1775 if (calBtnState < 2 && hbTimer.read_ms() > 1000)
mjr 1:d913e0afb2ac 1776 {
mjr 5:a70c0bce770d 1777 if (js.isSuspended() || !js.isConnected())
mjr 2:c174f9ee414a 1778 {
mjr 5:a70c0bce770d 1779 // suspended - turn off the LED
mjr 4:02c7cd7b2183 1780 ledR = 1;
mjr 4:02c7cd7b2183 1781 ledG = 1;
mjr 4:02c7cd7b2183 1782 ledB = 1;
mjr 5:a70c0bce770d 1783
mjr 5:a70c0bce770d 1784 // show a status flash every so often
mjr 5:a70c0bce770d 1785 if (hbcnt % 3 == 0)
mjr 5:a70c0bce770d 1786 {
mjr 6:cc35eb643e8f 1787 // disconnected = red/red flash; suspended = red
mjr 5:a70c0bce770d 1788 for (int n = js.isConnected() ? 1 : 2 ; n > 0 ; --n)
mjr 5:a70c0bce770d 1789 {
mjr 5:a70c0bce770d 1790 ledR = 0;
mjr 5:a70c0bce770d 1791 wait(0.05);
mjr 5:a70c0bce770d 1792 ledR = 1;
mjr 5:a70c0bce770d 1793 wait(0.25);
mjr 5:a70c0bce770d 1794 }
mjr 5:a70c0bce770d 1795 }
mjr 2:c174f9ee414a 1796 }
mjr 6:cc35eb643e8f 1797 else if (needReset)
mjr 2:c174f9ee414a 1798 {
mjr 6:cc35eb643e8f 1799 // connected, need to reset due to changes in config parameters -
mjr 6:cc35eb643e8f 1800 // flash red/green
mjr 6:cc35eb643e8f 1801 hb = !hb;
mjr 6:cc35eb643e8f 1802 ledR = (hb ? 0 : 1);
mjr 6:cc35eb643e8f 1803 ledG = (hb ? 1 : 0);
mjr 6:cc35eb643e8f 1804 ledB = 0;
mjr 6:cc35eb643e8f 1805 }
mjr 17:ab3cec0c8bf4 1806 else if (cfg.d.plungerEnabled && !cfg.d.plungerCal)
mjr 6:cc35eb643e8f 1807 {
mjr 6:cc35eb643e8f 1808 // connected, plunger calibration needed - flash yellow/green
mjr 6:cc35eb643e8f 1809 hb = !hb;
mjr 6:cc35eb643e8f 1810 ledR = (hb ? 0 : 1);
mjr 6:cc35eb643e8f 1811 ledG = 0;
mjr 6:cc35eb643e8f 1812 ledB = 1;
mjr 6:cc35eb643e8f 1813 }
mjr 6:cc35eb643e8f 1814 else
mjr 6:cc35eb643e8f 1815 {
mjr 6:cc35eb643e8f 1816 // connected - flash blue/green
mjr 2:c174f9ee414a 1817 hb = !hb;
mjr 4:02c7cd7b2183 1818 ledR = 1;
mjr 4:02c7cd7b2183 1819 ledG = (hb ? 0 : 1);
mjr 4:02c7cd7b2183 1820 ledB = (hb ? 1 : 0);
mjr 2:c174f9ee414a 1821 }
mjr 1:d913e0afb2ac 1822
mjr 1:d913e0afb2ac 1823 // reset the heartbeat timer
mjr 1:d913e0afb2ac 1824 hbTimer.reset();
mjr 5:a70c0bce770d 1825 ++hbcnt;
mjr 1:d913e0afb2ac 1826 }
mjr 1:d913e0afb2ac 1827 }
mjr 0:5acbbe3f4cf4 1828 }