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

Dependencies:   FastIO FastPWM SimpleDMA mbed

Fork of Pinscape_Controller by Mike R

Committer:
mjr
Date:
Mon Dec 29 19:26:45 2014 +0000
Revision:
15:944bbc29c4dd
Parent:
14:df700b22ca08
Child:
16:c35f905c3311
Fix and expand comments on LedWiz behavior vis-a-vis levels 48 and 49

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mjr 5:a70c0bce770d 1 /* Copyright 2014 M J Roberts, MIT License
mjr 5:a70c0bce770d 2 *
mjr 5:a70c0bce770d 3 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
mjr 5:a70c0bce770d 4 * and associated documentation files (the "Software"), to deal in the Software without
mjr 5:a70c0bce770d 5 * restriction, including without limitation the rights to use, copy, modify, merge, publish,
mjr 5:a70c0bce770d 6 * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
mjr 5:a70c0bce770d 7 * Software is furnished to do so, subject to the following conditions:
mjr 5:a70c0bce770d 8 *
mjr 5:a70c0bce770d 9 * The above copyright notice and this permission notice shall be included in all copies or
mjr 5:a70c0bce770d 10 * substantial portions of the Software.
mjr 5:a70c0bce770d 11 *
mjr 5:a70c0bce770d 12 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
mjr 5:a70c0bce770d 13 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
mjr 5:a70c0bce770d 14 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
mjr 5:a70c0bce770d 15 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
mjr 5:a70c0bce770d 16 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
mjr 5:a70c0bce770d 17 */
mjr 5:a70c0bce770d 18
mjr 5:a70c0bce770d 19 //
mjr 5:a70c0bce770d 20 // Pinscape Controller
mjr 5:a70c0bce770d 21 //
mjr 5:a70c0bce770d 22 // "Pinscape" is the name of my custom-built virtual pinball cabinet. I wrote this
mjr 5:a70c0bce770d 23 // software to perform a number of tasks that I needed for my cabinet. It runs on a
mjr 5:a70c0bce770d 24 // Freescale KL25Z microcontroller, which is a small and inexpensive device that
mjr 5:a70c0bce770d 25 // attaches to the host PC via USB and can interface with numerous types of external
mjr 5:a70c0bce770d 26 // hardware.
mjr 5:a70c0bce770d 27 //
mjr 5:a70c0bce770d 28 // I designed the software and hardware in this project especially for Pinscape, but
mjr 5:a70c0bce770d 29 // it uses standard interfaces in Windows and Visual Pinball, so it should be
mjr 5:a70c0bce770d 30 // readily usable in anyone else's VP-based cabinet. I've tried to document the
mjr 5:a70c0bce770d 31 // hardware in enough detail for anyone else to duplicate the entire project, and
mjr 5:a70c0bce770d 32 // the full software is open source.
mjr 5:a70c0bce770d 33 //
mjr 6:cc35eb643e8f 34 // The device appears to the host computer as a USB joystick. This works with the
mjr 6:cc35eb643e8f 35 // standard Windows joystick device drivers, so there's no need to install any
mjr 6:cc35eb643e8f 36 // software on the PC - Windows should recognize it as a joystick when you plug
mjr 6:cc35eb643e8f 37 // it in and shouldn't ask you to install anything. If you bring up the control
mjr 6:cc35eb643e8f 38 // panel for USB Game Controllers, this device will appear as "Pinscape Controller".
mjr 6:cc35eb643e8f 39 // *Don't* do any calibration with the Windows control panel or third-part
mjr 6:cc35eb643e8f 40 // calibration tools. The device calibrates itself automatically for the
mjr 6:cc35eb643e8f 41 // accelerometer data, and has its own special calibration procedure for the
mjr 6:cc35eb643e8f 42 // plunger (see below).
mjr 6:cc35eb643e8f 43 //
mjr 5:a70c0bce770d 44 // The controller provides the following functions. It should be possible to use
mjr 5:a70c0bce770d 45 // any subet of the features without using all of them. External hardware for any
mjr 5:a70c0bce770d 46 // particular function can simply be omitted if that feature isn't needed.
mjr 5:a70c0bce770d 47 //
mjr 5:a70c0bce770d 48 // - Nudge sensing via the KL25Z's on-board accelerometer. Nudge accelerations are
mjr 5:a70c0bce770d 49 // processed into a physics model of a rolling ball, and changes to the ball's
mjr 5:a70c0bce770d 50 // motion are sent to the host computer via the joystick interface. This is designed
mjr 5:a70c0bce770d 51 // especially to work with Visuall Pinball's nudge handling to produce realistic
mjr 5:a70c0bce770d 52 // on-screen results in VP. By doing some physics modeling right on the device,
mjr 5:a70c0bce770d 53 // rather than sending raw accelerometer data to VP, we can produce better results
mjr 5:a70c0bce770d 54 // using our awareness of the real physical parameters of a pinball cabinet.
mjr 5:a70c0bce770d 55 // VP's nudge handling has to be more generic, so it can't make the same sorts
mjr 5:a70c0bce770d 56 // of assumptions that we can about the dynamics of a real cabinet.
mjr 5:a70c0bce770d 57 //
mjr 5:a70c0bce770d 58 // The nudge data reports are compatible with the built-in Windows USB joystick
mjr 5:a70c0bce770d 59 // drivers and with VP's own joystick input scheme, so the nudge sensing is almost
mjr 5:a70c0bce770d 60 // plug-and-play. There are no Windiows drivers to install, and the only VP work
mjr 5:a70c0bce770d 61 // needed is to customize a few global preference settings.
mjr 5:a70c0bce770d 62 //
mjr 5:a70c0bce770d 63 // - Plunger position sensing via an attached TAOS TSL 1410R CCD linear array sensor.
mjr 5:a70c0bce770d 64 // The sensor must be wired to a particular set of I/O ports on the KL25Z, and must
mjr 5:a70c0bce770d 65 // be positioned adjacent to the plunger with proper lighting. The physical and
mjr 5:a70c0bce770d 66 // electronic installation details are desribed in the project documentation. We read
mjr 5:a70c0bce770d 67 // the CCD to determine how far back the plunger is pulled, and report this to Visual
mjr 5:a70c0bce770d 68 // Pinball via the joystick interface. As with the nudge data, this is all nearly
mjr 5:a70c0bce770d 69 // plug-and-play, in that it works with the default Windows USB drivers and works
mjr 5:a70c0bce770d 70 // with the existing VP handling for analog plunger input. A few VP settings are
mjr 5:a70c0bce770d 71 // needed to tell VP to allow the plunger.
mjr 5:a70c0bce770d 72 //
mjr 6:cc35eb643e8f 73 // For best results, the plunger sensor should be calibrated. The calibration
mjr 6:cc35eb643e8f 74 // is stored in non-volatile memory on board the KL25Z, so it's only necessary
mjr 6:cc35eb643e8f 75 // to do the calibration once, when you first install everything. (You might
mjr 6:cc35eb643e8f 76 // also want to re-calibrate if you physically remove and reinstall the CCD
mjr 6:cc35eb643e8f 77 // sensor or the mechanical plunger, since their alignment might change slightly
mjr 6:cc35eb643e8f 78 // when you put everything back together.) To calibrate, you have to attach a
mjr 6:cc35eb643e8f 79 // momentary switch (e.g., a push-button switch) between one of the KL25Z ground
mjr 6:cc35eb643e8f 80 // pins (e.g., jumper J9 pin 12) and PTE29 (J10 pin 9). Press and hold the
mjr 6:cc35eb643e8f 81 // button for about two seconds - the LED on the KL25Z wlil flash blue while
mjr 6:cc35eb643e8f 82 // you hold the button, and will turn solid blue when you've held it down long
mjr 6:cc35eb643e8f 83 // enough to enter calibration mode. This mode will last about 15 seconds.
mjr 6:cc35eb643e8f 84 // Simply pull the plunger all the way back, hold it for a few moments, and
mjr 6:cc35eb643e8f 85 // gradually return it to the starting position. *Don't* release it - we want
mjr 6:cc35eb643e8f 86 // to measure the maximum retracted position and the rest position, but NOT
mjr 6:cc35eb643e8f 87 // the maximum forward position when the outer barrel spring is compressed.
mjr 6:cc35eb643e8f 88 // After about 15 seconds, the device will save the new calibration settings
mjr 6:cc35eb643e8f 89 // to its flash memory, and the LED will return to the regular "heartbeat"
mjr 6:cc35eb643e8f 90 // flashes. If this is the first time you calibrated, you should observe the
mjr 6:cc35eb643e8f 91 // color of the flashes change from yellow/green to blue/green to indicate
mjr 6:cc35eb643e8f 92 // that the plunger has been calibrated.
mjr 6:cc35eb643e8f 93 //
mjr 6:cc35eb643e8f 94 // Note that while Visual Pinball itself has good native support for analog
mjr 6:cc35eb643e8f 95 // plungers, most of the VP tables in circulation don't implement the necessary
mjr 6:cc35eb643e8f 96 // scripting features to make this work properly. Therefore, you'll have to do
mjr 6:cc35eb643e8f 97 // a little scripting work for each table you download to add the required code
mjr 6:cc35eb643e8f 98 // to that individual table. The work has to be customized for each table, so
mjr 6:cc35eb643e8f 99 // I haven't been able to automate this process, but I have tried to reduce it
mjr 6:cc35eb643e8f 100 // to a relatively simple recipe that I've documented separately.
mjr 5:a70c0bce770d 101 //
mjr 5:a70c0bce770d 102 // - In addition to the CCD sensor, a button should be attached (also described in
mjr 5:a70c0bce770d 103 // the project documentation) to activate calibration mode for the plunger. When
mjr 5:a70c0bce770d 104 // calibration mode is activated, the software reads the plunger position for about
mjr 5:a70c0bce770d 105 // 10 seconds when to note the limits of travel, and uses these limits to ensure
mjr 5:a70c0bce770d 106 // accurate reports to VP that properly report the actual position of the physical
mjr 5:a70c0bce770d 107 // plunger. The calibration is stored in non-volatile memory on the KL25Z, so it's
mjr 5:a70c0bce770d 108 // only necessary to calibrate once - the calibration will survive power cycling
mjr 5:a70c0bce770d 109 // and reboots of the PC. It's only necessary to recalibrate if the CCD sensor or
mjr 5:a70c0bce770d 110 // the plunger are removed and reinstalled, since the relative alignment of the
mjr 5:a70c0bce770d 111 // parts could cahnge slightly when reinstalling.
mjr 5:a70c0bce770d 112 //
mjr 13:72dda449c3c0 113 // - Button input wiring. 24 of the KL25Z's GPIO ports are mapped as digital inputs
mjr 13:72dda449c3c0 114 // for buttons and switches. The software reports these as joystick buttons when
mjr 13:72dda449c3c0 115 // it sends reports to the PC. These can be used to wire physical pinball-style
mjr 13:72dda449c3c0 116 // buttons in the cabinet (e.g., flipper buttons, the Start button) and miscellaneous
mjr 13:72dda449c3c0 117 // switches (such as a tilt bob) to the PC. Visual Pinball can use joystick buttons
mjr 13:72dda449c3c0 118 // for input - you just have to assign a VP function to each button using VP's
mjr 13:72dda449c3c0 119 // keyboard options dialog. To wire a button physically, connect one terminal of
mjr 13:72dda449c3c0 120 // the button switch to the KL25Z ground, and connect the other terminal to the
mjr 13:72dda449c3c0 121 // the GPIO port you wish to assign to the button. See the buttonMap[] array
mjr 13:72dda449c3c0 122 // below for the available GPIO ports and their assigned joystick button numbers.
mjr 13:72dda449c3c0 123 // If you're not using a GPIO port, you can just leave it unconnected - the digital
mjr 13:72dda449c3c0 124 // inputs have built-in pull-up resistors, so an unconnected port is the same as
mjr 13:72dda449c3c0 125 // an open switch (an "off" state for the button).
mjr 13:72dda449c3c0 126 //
mjr 5:a70c0bce770d 127 // - LedWiz emulation. The KL25Z can appear to the PC as an LedWiz device, and will
mjr 5:a70c0bce770d 128 // accept and process LedWiz commands from the host. The software can turn digital
mjr 5:a70c0bce770d 129 // output ports on and off, and can set varying PWM intensitiy levels on a subset
mjr 5:a70c0bce770d 130 // of ports. (The KL25Z can only provide 6 PWM ports. Intensity level settings on
mjr 5:a70c0bce770d 131 // other ports is ignored, so non-PWM ports can only be used for simple on/off
mjr 5:a70c0bce770d 132 // devices such as contactors and solenoids.) The KL25Z can only supply 4mA on its
mjr 5:a70c0bce770d 133 // output ports, so external hardware is required to take advantage of the LedWiz
mjr 5:a70c0bce770d 134 // emulation. Many different hardware designs are possible, but there's a simple
mjr 5:a70c0bce770d 135 // reference design in the documentation that uses a Darlington array IC to
mjr 5:a70c0bce770d 136 // increase the output from each port to 500mA (the same level as the LedWiz),
mjr 5:a70c0bce770d 137 // plus an extended design that adds an optocoupler and MOSFET to provide very
mjr 5:a70c0bce770d 138 // high power handling, up to about 45A or 150W, with voltages up to 100V.
mjr 5:a70c0bce770d 139 // That will handle just about any DC device directly (wtihout relays or other
mjr 5:a70c0bce770d 140 // amplifiers), and switches fast enough to support PWM devices.
mjr 5:a70c0bce770d 141 //
mjr 5:a70c0bce770d 142 // The device can report any desired LedWiz unit number to the host, which makes
mjr 5:a70c0bce770d 143 // it possible to use the LedWiz emulation on a machine that also has one or more
mjr 5:a70c0bce770d 144 // actual LedWiz devices intalled. The LedWiz design allows for up to 16 units
mjr 5:a70c0bce770d 145 // to be installed in one machine - each one is invidually addressable by its
mjr 5:a70c0bce770d 146 // distinct unit number.
mjr 5:a70c0bce770d 147 //
mjr 5:a70c0bce770d 148 // The LedWiz emulation features are of course optional. There's no need to
mjr 5:a70c0bce770d 149 // build any of the external port hardware (or attach anything to the output
mjr 5:a70c0bce770d 150 // ports at all) if the LedWiz features aren't needed. Most people won't have
mjr 5:a70c0bce770d 151 // any use for the LedWiz features. I built them mostly as a learning exercise,
mjr 5:a70c0bce770d 152 // but with a slight practical need for a handful of extra ports (I'm using the
mjr 5:a70c0bce770d 153 // cutting-edge 10-contactor setup, so my real LedWiz is full!).
mjr 6:cc35eb643e8f 154 //
mjr 6:cc35eb643e8f 155 // The on-board LED on the KL25Z flashes to indicate the current device status:
mjr 6:cc35eb643e8f 156 //
mjr 6:cc35eb643e8f 157 // two short red flashes = the device is powered but hasn't successfully
mjr 6:cc35eb643e8f 158 // connected to the host via USB (either it's not physically connected
mjr 6:cc35eb643e8f 159 // to the USB port, or there was a problem with the software handshake
mjr 6:cc35eb643e8f 160 // with the USB device driver on the computer)
mjr 6:cc35eb643e8f 161 //
mjr 6:cc35eb643e8f 162 // short red flash = the host computer is in sleep/suspend mode
mjr 6:cc35eb643e8f 163 //
mjr 6:cc35eb643e8f 164 // long red/green = the LedWiz unti number has been changed, so a reset
mjr 6:cc35eb643e8f 165 // is needed. You can simply unplug the device and plug it back in,
mjr 6:cc35eb643e8f 166 // or presss and hold the reset button on the device for a few seconds.
mjr 6:cc35eb643e8f 167 //
mjr 6:cc35eb643e8f 168 // long yellow/green = everything's working, but the plunger hasn't
mjr 6:cc35eb643e8f 169 // been calibrated; follow the calibration procedure described above.
mjr 6:cc35eb643e8f 170 // This flash mode won't appear if the CCD has been disabled. Note
mjr 6:cc35eb643e8f 171 // that the device can't tell whether a CCD is physically attached,
mjr 6:cc35eb643e8f 172 // so you should use the config command to disable the CCD software
mjr 6:cc35eb643e8f 173 // features if you won't be attaching a CCD.
mjr 6:cc35eb643e8f 174 //
mjr 6:cc35eb643e8f 175 // alternating blue/green = everything's working
mjr 6:cc35eb643e8f 176 //
mjr 6:cc35eb643e8f 177 // Software configuration: you can change option settings by sending special
mjr 6:cc35eb643e8f 178 // USB commands from the PC. I've provided a Windows program for this purpose;
mjr 6:cc35eb643e8f 179 // refer to the documentation for details. For reference, here's the format
mjr 6:cc35eb643e8f 180 // of the USB command for option changes:
mjr 6:cc35eb643e8f 181 //
mjr 6:cc35eb643e8f 182 // length of report = 8 bytes
mjr 6:cc35eb643e8f 183 // byte 0 = 65 (0x41)
mjr 6:cc35eb643e8f 184 // byte 1 = 1 (0x01)
mjr 6:cc35eb643e8f 185 // byte 2 = new LedWiz unit number, 0x01 to 0x0f
mjr 6:cc35eb643e8f 186 // byte 3 = feature enable bit mask:
mjr 6:cc35eb643e8f 187 // 0x01 = enable CCD (default = on)
mjr 9:fd65b0a94720 188 //
mjr 9:fd65b0a94720 189 // Plunger calibration mode: the host can activate plunger calibration mode
mjr 9:fd65b0a94720 190 // by sending this packet. This has the same effect as pressing and holding
mjr 9:fd65b0a94720 191 // the plunger calibration button for two seconds, to allow activating this
mjr 9:fd65b0a94720 192 // mode without attaching a physical button.
mjr 9:fd65b0a94720 193 //
mjr 9:fd65b0a94720 194 // length = 8 bytes
mjr 9:fd65b0a94720 195 // byte 0 = 65 (0x41)
mjr 9:fd65b0a94720 196 // byte 1 = 2 (0x02)
mjr 9:fd65b0a94720 197 //
mjr 10:976666ffa4ef 198 // Exposure reports: the host can request a report of the full set of pixel
mjr 10:976666ffa4ef 199 // values for the next frame by sending this special packet:
mjr 10:976666ffa4ef 200 //
mjr 10:976666ffa4ef 201 // length = 8 bytes
mjr 10:976666ffa4ef 202 // byte 0 = 65 (0x41)
mjr 10:976666ffa4ef 203 // byte 1 = 3 (0x03)
mjr 10:976666ffa4ef 204 //
mjr 10:976666ffa4ef 205 // We'll respond with a series of special reports giving the exposure status.
mjr 10:976666ffa4ef 206 // Each report has the following structure:
mjr 10:976666ffa4ef 207 //
mjr 10:976666ffa4ef 208 // bytes 0:1 = 11-bit index, with high 5 bits set to 10000. For
mjr 10:976666ffa4ef 209 // example, 0x04 0x80 indicates index 4. This is the
mjr 10:976666ffa4ef 210 // starting pixel number in the report. The first report
mjr 10:976666ffa4ef 211 // will be 0x00 0x80 to indicate pixel #0.
mjr 10:976666ffa4ef 212 // bytes 2:3 = 16-bit unsigned int brightness level of pixel at index
mjr 10:976666ffa4ef 213 // bytes 4:5 = brightness of pixel at index+1
mjr 10:976666ffa4ef 214 // etc for the rest of the packet
mjr 10:976666ffa4ef 215 //
mjr 10:976666ffa4ef 216 // This still has the form of a joystick packet at the USB level, but
mjr 10:976666ffa4ef 217 // can be differentiated by the host via the status bits. It would have
mjr 10:976666ffa4ef 218 // been cleaner to use a different Report ID at the USB level, but this
mjr 10:976666ffa4ef 219 // would have necessitated a different container structure in the report
mjr 10:976666ffa4ef 220 // descriptor, which would have broken LedWiz compatibility. Given that
mjr 10:976666ffa4ef 221 // constraint, we have to re-use the joystick report type, making for
mjr 10:976666ffa4ef 222 // this somewhat kludgey approach.
mjr 6:cc35eb643e8f 223
mjr 0:5acbbe3f4cf4 224 #include "mbed.h"
mjr 6:cc35eb643e8f 225 #include "math.h"
mjr 0:5acbbe3f4cf4 226 #include "USBJoystick.h"
mjr 0:5acbbe3f4cf4 227 #include "MMA8451Q.h"
mjr 1:d913e0afb2ac 228 #include "tsl1410r.h"
mjr 1:d913e0afb2ac 229 #include "FreescaleIAP.h"
mjr 2:c174f9ee414a 230 #include "crc32.h"
mjr 2:c174f9ee414a 231
mjr 5:a70c0bce770d 232
mjr 5:a70c0bce770d 233 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 234 //
mjr 5:a70c0bce770d 235 // Configuration details
mjr 5:a70c0bce770d 236 //
mjr 2:c174f9ee414a 237
mjr 5:a70c0bce770d 238 // Our USB device vendor ID, product ID, and version.
mjr 5:a70c0bce770d 239 // We use the vendor ID for the LedWiz, so that the PC-side software can
mjr 5:a70c0bce770d 240 // identify us as capable of performing LedWiz commands. The LedWiz uses
mjr 5:a70c0bce770d 241 // a product ID value from 0xF0 to 0xFF; the last four bits identify the
mjr 5:a70c0bce770d 242 // unit number (e.g., product ID 0xF7 means unit #7). This allows multiple
mjr 5:a70c0bce770d 243 // LedWiz units to be installed in a single PC; the software on the PC side
mjr 5:a70c0bce770d 244 // uses the unit number to route commands to the devices attached to each
mjr 5:a70c0bce770d 245 // unit. On the real LedWiz, the unit number must be set in the firmware
mjr 5:a70c0bce770d 246 // at the factory; it's not configurable by the end user. Most LedWiz's
mjr 5:a70c0bce770d 247 // ship with the unit number set to 0, but the vendor will set different
mjr 5:a70c0bce770d 248 // unit numbers if requested at the time of purchase. So if you have a
mjr 5:a70c0bce770d 249 // single LedWiz already installed in your cabinet, and you didn't ask for
mjr 5:a70c0bce770d 250 // a non-default unit number, your existing LedWiz will be unit 0.
mjr 5:a70c0bce770d 251 //
mjr 5:a70c0bce770d 252 // We use unit #7 by default. There doesn't seem to be a requirement that
mjr 5:a70c0bce770d 253 // unit numbers be contiguous (DirectOutput Framework and other software
mjr 5:a70c0bce770d 254 // seem happy to have units 0 and 7 installed, without 1-6 existing).
mjr 5:a70c0bce770d 255 // Marking this unit as #7 should work for almost everybody out of the box;
mjr 5:a70c0bce770d 256 // the most common case seems to be to have a single LedWiz installed, and
mjr 5:a70c0bce770d 257 // it's probably extremely rare to more than two.
mjr 6:cc35eb643e8f 258 //
mjr 6:cc35eb643e8f 259 // Note that the USB_PRODUCT_ID value set here omits the unit number. We
mjr 6:cc35eb643e8f 260 // take the unit number from the saved configuration. We provide a
mjr 6:cc35eb643e8f 261 // configuration command that can be sent via the USB connection to change
mjr 6:cc35eb643e8f 262 // the unit number, so that users can select the unit number without having
mjr 6:cc35eb643e8f 263 // to install a different version of the software. We'll combine the base
mjr 6:cc35eb643e8f 264 // product ID here with the unit number to get the actual product ID that
mjr 6:cc35eb643e8f 265 // we send to the USB controller.
mjr 5:a70c0bce770d 266 const uint16_t USB_VENDOR_ID = 0xFAFA;
mjr 6:cc35eb643e8f 267 const uint16_t USB_PRODUCT_ID = 0x00F0;
mjr 6:cc35eb643e8f 268 const uint16_t USB_VERSION_NO = 0x0006;
mjr 6:cc35eb643e8f 269 const uint8_t DEFAULT_LEDWIZ_UNIT_NUMBER = 0x07;
mjr 0:5acbbe3f4cf4 270
mjr 9:fd65b0a94720 271 // Number of pixels we read from the sensor on each frame. This can be
mjr 9:fd65b0a94720 272 // less than the physical pixel count if desired; we'll read every nth
mjr 9:fd65b0a94720 273 // piexl if so. E.g., with a 1280-pixel physical sensor, if npix is 320,
mjr 9:fd65b0a94720 274 // we'll read every 4th pixel. It takes time to read each pixel, so the
mjr 9:fd65b0a94720 275 // fewer pixels we read, the higher the refresh rate we can achieve.
mjr 9:fd65b0a94720 276 // It's therefore better not to read more pixels than we have to.
mjr 9:fd65b0a94720 277 //
mjr 9:fd65b0a94720 278 // VP seems to have an internal resolution in the 8-bit range, so there's
mjr 9:fd65b0a94720 279 // no apparent benefit to reading more than 128-256 pixels when using VP.
mjr 9:fd65b0a94720 280 // Empirically, 160 pixels seems about right. The overall travel of a
mjr 9:fd65b0a94720 281 // standard pinball plunger is about 3", so 160 pixels gives us resolution
mjr 9:fd65b0a94720 282 // of about 1/50". This seems to take full advantage of VP's modeling
mjr 9:fd65b0a94720 283 // ability, and is probably also more precise than a human player's
mjr 9:fd65b0a94720 284 // perception of the plunger position.
mjr 9:fd65b0a94720 285 const int npix = 160;
mjr 9:fd65b0a94720 286
mjr 4:02c7cd7b2183 287 // On-board RGB LED elements - we use these for diagnostic displays.
mjr 4:02c7cd7b2183 288 DigitalOut ledR(LED1), ledG(LED2), ledB(LED3);
mjr 0:5acbbe3f4cf4 289
mjr 1:d913e0afb2ac 290 // calibration button - switch input and LED output
mjr 1:d913e0afb2ac 291 DigitalIn calBtn(PTE29);
mjr 1:d913e0afb2ac 292 DigitalOut calBtnLed(PTE23);
mjr 0:5acbbe3f4cf4 293
mjr 11:bd9da7088e6e 294 // Joystick button input pin assignments. You can wire up to
mjr 11:bd9da7088e6e 295 // 32 GPIO ports to buttons (equipped with momentary switches).
mjr 11:bd9da7088e6e 296 // Connect each switch between the desired GPIO port and ground
mjr 11:bd9da7088e6e 297 // (J9 pin 12 or 14). When the button is pressed, we'll tell the
mjr 13:72dda449c3c0 298 // host PC that the corresponding joystick button is pressed. We
mjr 11:bd9da7088e6e 299 // debounce the keystrokes in software, so you can simply wire
mjr 11:bd9da7088e6e 300 // directly to pushbuttons with no additional external hardware.
mjr 11:bd9da7088e6e 301 //
mjr 11:bd9da7088e6e 302 // Note that we assign 24 buttons by default, even though the USB
mjr 11:bd9da7088e6e 303 // joystick interface can handle up to 32 buttons. VP itself only
mjr 11:bd9da7088e6e 304 // allows mapping of up to 24 buttons in the preferences dialog
mjr 11:bd9da7088e6e 305 // (although it can recognize 32 buttons internally). If you want
mjr 11:bd9da7088e6e 306 // more buttons, you can reassign pins that are assigned by default
mjr 11:bd9da7088e6e 307 // as LedWiz outputs. To reassign a pin, find the pin you wish to
mjr 11:bd9da7088e6e 308 // reassign in the LedWizPortMap array below, and change the pin name
mjr 11:bd9da7088e6e 309 // there to NC (for Not Connected). You can then change one of the
mjr 11:bd9da7088e6e 310 // "NC" entries below to the reallocated pin name. The limit is 32
mjr 11:bd9da7088e6e 311 // buttons total.
mjr 11:bd9da7088e6e 312 //
mjr 11:bd9da7088e6e 313 // Note: PTD1 (pin J2-12) should NOT be assigned as a button input,
mjr 11:bd9da7088e6e 314 // as this pin is physically connected on the KL25Z to the on-board
mjr 11:bd9da7088e6e 315 // indicator LED's blue segment. This precludes any other use of
mjr 11:bd9da7088e6e 316 // the pin.
mjr 11:bd9da7088e6e 317 PinName buttonMap[] = {
mjr 11:bd9da7088e6e 318 PTC2, // J10 pin 10, joystick button 1
mjr 11:bd9da7088e6e 319 PTB3, // J10 pin 8, joystick button 2
mjr 11:bd9da7088e6e 320 PTB2, // J10 pin 6, joystick button 3
mjr 11:bd9da7088e6e 321 PTB1, // J10 pin 4, joystick button 4
mjr 11:bd9da7088e6e 322
mjr 11:bd9da7088e6e 323 PTE30, // J10 pin 11, joystick button 5
mjr 11:bd9da7088e6e 324 PTE22, // J10 pin 5, joystick button 6
mjr 11:bd9da7088e6e 325
mjr 11:bd9da7088e6e 326 PTE5, // J9 pin 15, joystick button 7
mjr 11:bd9da7088e6e 327 PTE4, // J9 pin 13, joystick button 8
mjr 11:bd9da7088e6e 328 PTE3, // J9 pin 11, joystick button 9
mjr 11:bd9da7088e6e 329 PTE2, // J9 pin 9, joystick button 10
mjr 11:bd9da7088e6e 330 PTB11, // J9 pin 7, joystick button 11
mjr 11:bd9da7088e6e 331 PTB10, // J9 pin 5, joystick button 12
mjr 11:bd9da7088e6e 332 PTB9, // J9 pin 3, joystick button 13
mjr 11:bd9da7088e6e 333 PTB8, // J9 pin 1, joystick button 14
mjr 11:bd9da7088e6e 334
mjr 11:bd9da7088e6e 335 PTC12, // J2 pin 1, joystick button 15
mjr 11:bd9da7088e6e 336 PTC13, // J2 pin 3, joystick button 16
mjr 11:bd9da7088e6e 337 PTC16, // J2 pin 5, joystick button 17
mjr 11:bd9da7088e6e 338 PTC17, // J2 pin 7, joystick button 18
mjr 11:bd9da7088e6e 339 PTA16, // J2 pin 9, joystick button 19
mjr 11:bd9da7088e6e 340 PTA17, // J2 pin 11, joystick button 20
mjr 11:bd9da7088e6e 341 PTE31, // J2 pin 13, joystick button 21
mjr 11:bd9da7088e6e 342 PTD6, // J2 pin 17, joystick button 22
mjr 11:bd9da7088e6e 343 PTD7, // J2 pin 19, joystick button 23
mjr 11:bd9da7088e6e 344
mjr 11:bd9da7088e6e 345 PTE1, // J2 pin 20, joystick button 24
mjr 11:bd9da7088e6e 346
mjr 11:bd9da7088e6e 347 NC, // not used, joystick button 25
mjr 11:bd9da7088e6e 348 NC, // not used, joystick button 26
mjr 11:bd9da7088e6e 349 NC, // not used, joystick button 27
mjr 11:bd9da7088e6e 350 NC, // not used, joystick button 28
mjr 11:bd9da7088e6e 351 NC, // not used, joystick button 29
mjr 11:bd9da7088e6e 352 NC, // not used, joystick button 30
mjr 11:bd9da7088e6e 353 NC, // not used, joystick button 31
mjr 11:bd9da7088e6e 354 NC // not used, joystick button 32
mjr 11:bd9da7088e6e 355 };
mjr 11:bd9da7088e6e 356
mjr 11:bd9da7088e6e 357 // LED-Wiz emulation output pin assignments.
mjr 6:cc35eb643e8f 358 //
mjr 6:cc35eb643e8f 359 // The LED-Wiz protocol allows setting individual intensity levels
mjr 6:cc35eb643e8f 360 // on all outputs, with 48 levels of intensity. This can be used
mjr 6:cc35eb643e8f 361 // to control lamp brightness and motor speeds, among other things.
mjr 6:cc35eb643e8f 362 // Unfortunately, the KL25Z only has 10 PWM channels, so while we
mjr 6:cc35eb643e8f 363 // can support the full complement of 32 outputs, we can only provide
mjr 6:cc35eb643e8f 364 // PWM dimming/speed control on 10 of them. The remaining outputs
mjr 6:cc35eb643e8f 365 // can only be switched fully on and fully off - we can't support
mjr 6:cc35eb643e8f 366 // dimming on these, so they'll ignore any intensity level setting
mjr 6:cc35eb643e8f 367 // requested by the host. Use these for devices that don't have any
mjr 6:cc35eb643e8f 368 // use for intensity settings anyway, such as contactors and knockers.
mjr 6:cc35eb643e8f 369 //
mjr 11:bd9da7088e6e 370 // Ports with pins assigned as "NC" are not connected. That is,
mjr 11:bd9da7088e6e 371 // there's no physical pin for that LedWiz port number. You can
mjr 11:bd9da7088e6e 372 // send LedWiz commands to turn NC ports on and off, but doing so
mjr 11:bd9da7088e6e 373 // will have no effect. The reason we leave some ports unassigned
mjr 11:bd9da7088e6e 374 // is that we don't have enough physical GPIO pins to fill out the
mjr 11:bd9da7088e6e 375 // full LedWiz complement of 32 ports. Many pins are already taken
mjr 11:bd9da7088e6e 376 // for other purposes, such as button inputs or the plunger CCD
mjr 11:bd9da7088e6e 377 // interface.
mjr 11:bd9da7088e6e 378 //
mjr 6:cc35eb643e8f 379 // The mapping between physical output pins on the KL25Z and the
mjr 6:cc35eb643e8f 380 // assigned LED-Wiz port numbers is essentially arbitrary - you can
mjr 6:cc35eb643e8f 381 // customize this by changing the entries in the array below if you
mjr 6:cc35eb643e8f 382 // wish to rearrange the pins for any reason. Be aware that some
mjr 6:cc35eb643e8f 383 // of the physical outputs are already used for other purposes
mjr 6:cc35eb643e8f 384 // (e.g., some of the GPIO pins on header J10 are used for the
mjr 6:cc35eb643e8f 385 // CCD sensor - but you can of course reassign those as well by
mjr 6:cc35eb643e8f 386 // changing the corresponding declarations elsewhere in this module).
mjr 6:cc35eb643e8f 387 // The assignments we make here have two main objectives: first,
mjr 6:cc35eb643e8f 388 // to group the outputs on headers J1 and J2 (to facilitate neater
mjr 6:cc35eb643e8f 389 // wiring by keeping the output pins together physically), and
mjr 6:cc35eb643e8f 390 // second, to make the physical pin layout match the LED-Wiz port
mjr 6:cc35eb643e8f 391 // numbering order to the extent possible. There's one big wrench
mjr 6:cc35eb643e8f 392 // in the works, though, which is the limited number and discontiguous
mjr 6:cc35eb643e8f 393 // placement of the KL25Z PWM-capable output pins. This prevents
mjr 6:cc35eb643e8f 394 // us from doing the most obvious sequential ordering of the pins,
mjr 6:cc35eb643e8f 395 // so we end up with the outputs arranged into several blocks.
mjr 6:cc35eb643e8f 396 // Hopefully this isn't too confusing; for more detailed rationale,
mjr 6:cc35eb643e8f 397 // read on...
mjr 6:cc35eb643e8f 398 //
mjr 6:cc35eb643e8f 399 // With the LED-Wiz, the host software configuration usually
mjr 6:cc35eb643e8f 400 // assumes that each RGB LED is hooked up to three consecutive ports
mjr 6:cc35eb643e8f 401 // (for the red, green, and blue components, which need to be
mjr 6:cc35eb643e8f 402 // physically wired to separate outputs to allow each color to be
mjr 6:cc35eb643e8f 403 // controlled independently). To facilitate this, we arrange the
mjr 6:cc35eb643e8f 404 // PWM-enabled outputs so that they're grouped together in the
mjr 6:cc35eb643e8f 405 // port numbering scheme. Unfortunately, these outputs aren't
mjr 6:cc35eb643e8f 406 // together in a single group in the physical pin layout, so to
mjr 6:cc35eb643e8f 407 // group them logically in the LED-Wiz port numbering scheme, we
mjr 6:cc35eb643e8f 408 // have to break up the overall numbering scheme into several blocks.
mjr 6:cc35eb643e8f 409 // So our port numbering goes sequentially down each column of
mjr 6:cc35eb643e8f 410 // header pins, but there are several break points where we have
mjr 6:cc35eb643e8f 411 // to interrupt the obvious sequence to keep the PWM pins grouped
mjr 6:cc35eb643e8f 412 // logically.
mjr 6:cc35eb643e8f 413 //
mjr 6:cc35eb643e8f 414 // In the list below, "pin J1-2" refers to pin 2 on header J1 on
mjr 6:cc35eb643e8f 415 // the KL25Z, using the standard pin numbering in the KL25Z
mjr 6:cc35eb643e8f 416 // documentation - this is the physical pin that the port controls.
mjr 6:cc35eb643e8f 417 // "LW port 1" means LED-Wiz port 1 - this is the LED-Wiz port
mjr 6:cc35eb643e8f 418 // number that you use on the PC side (in the DirectOutput config
mjr 6:cc35eb643e8f 419 // file, for example) to address the port. PWM-capable ports are
mjr 6:cc35eb643e8f 420 // marked as such - we group the PWM-capable ports into the first
mjr 6:cc35eb643e8f 421 // 10 LED-Wiz port numbers.
mjr 11:bd9da7088e6e 422 //
mjr 11:bd9da7088e6e 423 // If you wish to reallocate a pin in the array below to some other
mjr 11:bd9da7088e6e 424 // use, such as a button input port, simply change the pin name in
mjr 11:bd9da7088e6e 425 // the entry to NC (for Not Connected). This will disable the given
mjr 11:bd9da7088e6e 426 // logical LedWiz port number and free up the physical pin.
mjr 11:bd9da7088e6e 427 //
mjr 11:bd9da7088e6e 428 // If you wish to reallocate a pin currently assigned to the button
mjr 11:bd9da7088e6e 429 // input array, simply change the entry for the pin in the buttonMap[]
mjr 11:bd9da7088e6e 430 // array above to NC (for "not connected"), and plug the pin name into
mjr 11:bd9da7088e6e 431 // a slot of your choice in the array below.
mjr 11:bd9da7088e6e 432 //
mjr 11:bd9da7088e6e 433 // Note: PTD1 (pin J2-12) should NOT be assigned as an LedWiz output,
mjr 11:bd9da7088e6e 434 // as this pin is physically connected on the KL25Z to the on-board
mjr 11:bd9da7088e6e 435 // indicator LED's blue segment. This precludes any other use of
mjr 11:bd9da7088e6e 436 // the pin.
mjr 6:cc35eb643e8f 437 //
mjr 6:cc35eb643e8f 438 struct {
mjr 6:cc35eb643e8f 439 PinName pin;
mjr 6:cc35eb643e8f 440 bool isPWM;
mjr 6:cc35eb643e8f 441 } ledWizPortMap[32] = {
mjr 6:cc35eb643e8f 442 { PTA1, true }, // pin J1-2, LW port 1 (PWM capable - TPM 2.0 = channel 9)
mjr 6:cc35eb643e8f 443 { PTA2, true }, // pin J1-4, LW port 2 (PWM capable - TPM 2.1 = channel 10)
mjr 6:cc35eb643e8f 444 { PTD4, true }, // pin J1-6, LW port 3 (PWM capable - TPM 0.4 = channel 5)
mjr 6:cc35eb643e8f 445 { PTA12, true }, // pin J1-8, LW port 4 (PWM capable - TPM 1.0 = channel 7)
mjr 6:cc35eb643e8f 446 { PTA4, true }, // pin J1-10, LW port 5 (PWM capable - TPM 0.1 = channel 2)
mjr 6:cc35eb643e8f 447 { PTA5, true }, // pin J1-12, LW port 6 (PWM capable - TPM 0.2 = channel 3)
mjr 6:cc35eb643e8f 448 { PTA13, true }, // pin J2-2, LW port 7 (PWM capable - TPM 1.1 = channel 13)
mjr 6:cc35eb643e8f 449 { PTD5, true }, // pin J2-4, LW port 8 (PWM capable - TPM 0.5 = channel 6)
mjr 6:cc35eb643e8f 450 { PTD0, true }, // pin J2-6, LW port 9 (PWM capable - TPM 0.0 = channel 1)
mjr 6:cc35eb643e8f 451 { PTD3, true }, // pin J2-10, LW port 10 (PWM capable - TPM 0.3 = channel 4)
mjr 15:944bbc29c4dd 452 { PTD2, false }, // pin J2(Althou-8, LW port 11
mjr 9:fd65b0a94720 453 { PTC8, false }, // pin J1-14, LW port 12
mjr 9:fd65b0a94720 454 { PTC9, false }, // pin J1-16, LW port 13
mjr 9:fd65b0a94720 455 { PTC7, false }, // pin J1-1, LW port 14
mjr 9:fd65b0a94720 456 { PTC0, false }, // pin J1-3, LW port 15
mjr 9:fd65b0a94720 457 { PTC3, false }, // pin J1-5, LW port 16
mjr 9:fd65b0a94720 458 { PTC4, false }, // pin J1-7, LW port 17
mjr 9:fd65b0a94720 459 { PTC5, false }, // pin J1-9, LW port 18
mjr 9:fd65b0a94720 460 { PTC6, false }, // pin J1-11, LW port 19
mjr 9:fd65b0a94720 461 { PTC10, false }, // pin J1-13, LW port 20
mjr 9:fd65b0a94720 462 { PTC11, false }, // pin J1-15, LW port 21
mjr 11:bd9da7088e6e 463 { PTE0, false }, // pin J2-18, LW port 22
mjr 11:bd9da7088e6e 464 { NC, false }, // Not used, LW port 23
mjr 11:bd9da7088e6e 465 { NC, false }, // Not used, LW port 24
mjr 11:bd9da7088e6e 466 { NC, false }, // Not used, LW port 25
mjr 11:bd9da7088e6e 467 { NC, false }, // Not used, LW port 26
mjr 11:bd9da7088e6e 468 { NC, false }, // Not used, LW port 27
mjr 11:bd9da7088e6e 469 { NC, false }, // Not used, LW port 28
mjr 11:bd9da7088e6e 470 { NC, false }, // Not used, LW port 29
mjr 11:bd9da7088e6e 471 { NC, false }, // Not used, LW port 30
mjr 11:bd9da7088e6e 472 { NC, false }, // Not used, LW port 31
mjr 11:bd9da7088e6e 473 { NC, false } // Not used, LW port 32
mjr 6:cc35eb643e8f 474 };
mjr 6:cc35eb643e8f 475
mjr 6:cc35eb643e8f 476
mjr 5:a70c0bce770d 477 // I2C address of the accelerometer (this is a constant of the KL25Z)
mjr 5:a70c0bce770d 478 const int MMA8451_I2C_ADDRESS = (0x1d<<1);
mjr 5:a70c0bce770d 479
mjr 5:a70c0bce770d 480 // SCL and SDA pins for the accelerometer (constant for the KL25Z)
mjr 5:a70c0bce770d 481 #define MMA8451_SCL_PIN PTE25
mjr 5:a70c0bce770d 482 #define MMA8451_SDA_PIN PTE24
mjr 5:a70c0bce770d 483
mjr 5:a70c0bce770d 484 // Digital in pin to use for the accelerometer interrupt. For the KL25Z,
mjr 5:a70c0bce770d 485 // this can be either PTA14 or PTA15, since those are the pins physically
mjr 5:a70c0bce770d 486 // wired on this board to the MMA8451 interrupt controller.
mjr 5:a70c0bce770d 487 #define MMA8451_INT_PIN PTA15
mjr 5:a70c0bce770d 488
mjr 6:cc35eb643e8f 489 // Joystick axis report range - we report from -JOYMAX to +JOYMAX
mjr 6:cc35eb643e8f 490 #define JOYMAX 4096
mjr 6:cc35eb643e8f 491
mjr 5:a70c0bce770d 492
mjr 5:a70c0bce770d 493 // ---------------------------------------------------------------------------
mjr 9:fd65b0a94720 494 // utilities
mjr 9:fd65b0a94720 495
mjr 9:fd65b0a94720 496 // number of elements in an array
mjr 9:fd65b0a94720 497 #define countof(x) (sizeof(x)/sizeof((x)[0]))
mjr 9:fd65b0a94720 498
mjr 9:fd65b0a94720 499 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 500 //
mjr 5:a70c0bce770d 501 // LedWiz emulation
mjr 5:a70c0bce770d 502 //
mjr 5:a70c0bce770d 503
mjr 0:5acbbe3f4cf4 504 static int pbaIdx = 0;
mjr 0:5acbbe3f4cf4 505
mjr 6:cc35eb643e8f 506 // LedWiz output pin interface. We create a cover class to virtualize
mjr 6:cc35eb643e8f 507 // digital vs PWM outputs and give them a common interface. The KL25Z
mjr 6:cc35eb643e8f 508 // unfortunately doesn't have enough hardware PWM channels to support
mjr 6:cc35eb643e8f 509 // PWM on all 32 LedWiz outputs, so we provide as many PWM channels as
mjr 6:cc35eb643e8f 510 // we can (10), and fill out the rest of the outputs with plain digital
mjr 6:cc35eb643e8f 511 // outs.
mjr 6:cc35eb643e8f 512 class LwOut
mjr 6:cc35eb643e8f 513 {
mjr 6:cc35eb643e8f 514 public:
mjr 6:cc35eb643e8f 515 virtual void set(float val) = 0;
mjr 6:cc35eb643e8f 516 };
mjr 6:cc35eb643e8f 517 class LwPwmOut: public LwOut
mjr 6:cc35eb643e8f 518 {
mjr 6:cc35eb643e8f 519 public:
mjr 13:72dda449c3c0 520 LwPwmOut(PinName pin) : p(pin) { prv = -1; }
mjr 13:72dda449c3c0 521 virtual void set(float val)
mjr 13:72dda449c3c0 522 {
mjr 13:72dda449c3c0 523 if (val != prv)
mjr 13:72dda449c3c0 524 p.write(prv = val);
mjr 13:72dda449c3c0 525 }
mjr 6:cc35eb643e8f 526 PwmOut p;
mjr 13:72dda449c3c0 527 float prv;
mjr 6:cc35eb643e8f 528 };
mjr 6:cc35eb643e8f 529 class LwDigOut: public LwOut
mjr 6:cc35eb643e8f 530 {
mjr 6:cc35eb643e8f 531 public:
mjr 13:72dda449c3c0 532 LwDigOut(PinName pin) : p(pin) { prv = -1; }
mjr 13:72dda449c3c0 533 virtual void set(float val)
mjr 13:72dda449c3c0 534 {
mjr 13:72dda449c3c0 535 if (val != prv)
mjr 13:72dda449c3c0 536 p.write((prv = val) == 0.0 ? 0 : 1);
mjr 13:72dda449c3c0 537 }
mjr 6:cc35eb643e8f 538 DigitalOut p;
mjr 13:72dda449c3c0 539 float prv;
mjr 6:cc35eb643e8f 540 };
mjr 11:bd9da7088e6e 541 class LwUnusedOut: public LwOut
mjr 11:bd9da7088e6e 542 {
mjr 11:bd9da7088e6e 543 public:
mjr 11:bd9da7088e6e 544 LwUnusedOut() { }
mjr 11:bd9da7088e6e 545 virtual void set(float val) { }
mjr 11:bd9da7088e6e 546 };
mjr 6:cc35eb643e8f 547
mjr 6:cc35eb643e8f 548 // output pin array
mjr 6:cc35eb643e8f 549 static LwOut *lwPin[32];
mjr 6:cc35eb643e8f 550
mjr 6:cc35eb643e8f 551 // initialize the output pin array
mjr 6:cc35eb643e8f 552 void initLwOut()
mjr 6:cc35eb643e8f 553 {
mjr 9:fd65b0a94720 554 for (int i = 0 ; i < countof(lwPin) ; ++i)
mjr 6:cc35eb643e8f 555 {
mjr 11:bd9da7088e6e 556 PinName p = (i < countof(ledWizPortMap) ? ledWizPortMap[i].pin : NC);
mjr 11:bd9da7088e6e 557 if (p == NC)
mjr 11:bd9da7088e6e 558 lwPin[i] = new LwUnusedOut();
mjr 11:bd9da7088e6e 559 else if (ledWizPortMap[i].isPWM)
mjr 11:bd9da7088e6e 560 lwPin[i] = new LwPwmOut(p);
mjr 11:bd9da7088e6e 561 else
mjr 11:bd9da7088e6e 562 lwPin[i] = new LwDigOut(p);
mjr 6:cc35eb643e8f 563 }
mjr 6:cc35eb643e8f 564 }
mjr 6:cc35eb643e8f 565
mjr 0:5acbbe3f4cf4 566 // on/off state for each LedWiz output
mjr 1:d913e0afb2ac 567 static uint8_t wizOn[32];
mjr 0:5acbbe3f4cf4 568
mjr 0:5acbbe3f4cf4 569 // profile (brightness/blink) state for each LedWiz output
mjr 1:d913e0afb2ac 570 static uint8_t wizVal[32] = {
mjr 13:72dda449c3c0 571 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 572 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 573 48, 48, 48, 48, 48, 48, 48, 48,
mjr 13:72dda449c3c0 574 48, 48, 48, 48, 48, 48, 48, 48
mjr 0:5acbbe3f4cf4 575 };
mjr 0:5acbbe3f4cf4 576
mjr 1:d913e0afb2ac 577 static float wizState(int idx)
mjr 0:5acbbe3f4cf4 578 {
mjr 13:72dda449c3c0 579 if (wizOn[idx])
mjr 13:72dda449c3c0 580 {
mjr 0:5acbbe3f4cf4 581 // on - map profile brightness state to PWM level
mjr 1:d913e0afb2ac 582 uint8_t val = wizVal[idx];
mjr 13:72dda449c3c0 583 if (val <= 48)
mjr 13:72dda449c3c0 584 {
mjr 15:944bbc29c4dd 585 // PWM brightness/intensity level. Rescale from the LedWiz
mjr 15:944bbc29c4dd 586 // 0..48 integer range to our internal PwmOut 0..1 float range.
mjr 15:944bbc29c4dd 587 // Note that on the actual LedWiz, level 48 is actually about
mjr 15:944bbc29c4dd 588 // 98% on - contrary to the LedWiz documentation, level 49 is
mjr 15:944bbc29c4dd 589 // the true 100% level. (In the documentation, level 49 is
mjr 15:944bbc29c4dd 590 // simply not a valid setting.) Even so, we treat level 48 as
mjr 15:944bbc29c4dd 591 // 100% on to match the documentation. This won't be perfectly
mjr 15:944bbc29c4dd 592 // ocmpatible with the actual LedWiz, but it makes for such a
mjr 15:944bbc29c4dd 593 // small difference in brightness (if the output device is an
mjr 15:944bbc29c4dd 594 // LED, say) that no one should notice. It seems better to
mjr 15:944bbc29c4dd 595 // err in this direction, because while the difference in
mjr 15:944bbc29c4dd 596 // brightness when attached to an LED won't be noticeable, the
mjr 15:944bbc29c4dd 597 // difference in duty cycle when attached to something like a
mjr 15:944bbc29c4dd 598 // contactor *can* be noticeable - anything less than 100%
mjr 15:944bbc29c4dd 599 // can cause a contactor or relay to chatter. There's almost
mjr 15:944bbc29c4dd 600 // never a situation where you'd want values other than 0% and
mjr 15:944bbc29c4dd 601 // 100% for a contactor or relay, so treating level 48 as 100%
mjr 15:944bbc29c4dd 602 // makes us work properly with software that's expecting the
mjr 15:944bbc29c4dd 603 // documented LedWiz behavior and therefore uses level 48 to
mjr 15:944bbc29c4dd 604 // turn a contactor or relay fully on.
mjr 13:72dda449c3c0 605 return val/48.0;
mjr 13:72dda449c3c0 606 }
mjr 13:72dda449c3c0 607 else if (val == 49)
mjr 13:72dda449c3c0 608 {
mjr 15:944bbc29c4dd 609 // 49 is undefined in the LedWiz documentation, but actually
mjr 15:944bbc29c4dd 610 // means 100% on. The documentation says that levels 1-48 are
mjr 15:944bbc29c4dd 611 // the full PWM range, but empirically it appears that the real
mjr 15:944bbc29c4dd 612 // range implemented in the firmware is 1-49. Some software on
mjr 15:944bbc29c4dd 613 // the PC side (notably DOF) is aware of this and uses level 49
mjr 15:944bbc29c4dd 614 // to mean "100% on". To ensure compatibility with existing
mjr 15:944bbc29c4dd 615 // PC-side software, we need to recognize level 49.
mjr 13:72dda449c3c0 616 return 1.0;
mjr 13:72dda449c3c0 617 }
mjr 0:5acbbe3f4cf4 618 else if (val >= 129 && val <= 132)
mjr 13:72dda449c3c0 619 {
mjr 13:72dda449c3c0 620 // Values of 129-132 select different flashing modes. We don't
mjr 13:72dda449c3c0 621 // support any of these. Instead, simply treat them as fully on.
mjr 13:72dda449c3c0 622 // Note that DOF doesn't ever use modes 129-132, as it implements
mjr 13:72dda449c3c0 623 // all flashing modes itself on the host side, so this limitation
mjr 13:72dda449c3c0 624 // won't have any effect on DOF users. You can observe it using
mjr 13:72dda449c3c0 625 // LedBlinky, though.
mjr 13:72dda449c3c0 626 return 1.0;
mjr 13:72dda449c3c0 627 }
mjr 0:5acbbe3f4cf4 628 else
mjr 13:72dda449c3c0 629 {
mjr 13:72dda449c3c0 630 // Other values are undefined in the LedWiz documentation. Hosts
mjr 13:72dda449c3c0 631 // *should* never send undefined values, since whatever behavior an
mjr 13:72dda449c3c0 632 // LedWiz unit exhibits in response is accidental and could change
mjr 13:72dda449c3c0 633 // in a future version. We'll treat all undefined values as equivalent
mjr 13:72dda449c3c0 634 // to 48 (fully on).
mjr 13:72dda449c3c0 635 //
mjr 13:72dda449c3c0 636 // NB: the 49 and 129-132 cases are broken out above for the sake
mjr 13:72dda449c3c0 637 // of documentation. We end up using 1.0 as the return value for
mjr 13:72dda449c3c0 638 // everything outside of the defined 0-48 range, so we could collapse
mjr 13:72dda449c3c0 639 // this whole thing to a single 'else' branch, but I wanted to call
mjr 13:72dda449c3c0 640 // out the specific reasons for handling the settings above as we do.
mjr 0:5acbbe3f4cf4 641 return 1.0;
mjr 13:72dda449c3c0 642 }
mjr 0:5acbbe3f4cf4 643 }
mjr 13:72dda449c3c0 644 else
mjr 13:72dda449c3c0 645 {
mjr 13:72dda449c3c0 646 // off - show at 0 intensity
mjr 13:72dda449c3c0 647 return 0.0;
mjr 0:5acbbe3f4cf4 648 }
mjr 0:5acbbe3f4cf4 649 }
mjr 0:5acbbe3f4cf4 650
mjr 1:d913e0afb2ac 651 static void updateWizOuts()
mjr 1:d913e0afb2ac 652 {
mjr 6:cc35eb643e8f 653 for (int i = 0 ; i < 32 ; ++i)
mjr 6:cc35eb643e8f 654 lwPin[i]->set(wizState(i));
mjr 1:d913e0afb2ac 655 }
mjr 1:d913e0afb2ac 656
mjr 11:bd9da7088e6e 657
mjr 11:bd9da7088e6e 658 // ---------------------------------------------------------------------------
mjr 11:bd9da7088e6e 659 //
mjr 11:bd9da7088e6e 660 // Button input
mjr 11:bd9da7088e6e 661 //
mjr 11:bd9da7088e6e 662
mjr 11:bd9da7088e6e 663 // button input map array
mjr 11:bd9da7088e6e 664 DigitalIn *buttonDigIn[32];
mjr 11:bd9da7088e6e 665
mjr 12:669df364a565 666 // timer for button reports
mjr 12:669df364a565 667 static Timer buttonTimer;
mjr 12:669df364a565 668
mjr 11:bd9da7088e6e 669 // initialize the button inputs
mjr 11:bd9da7088e6e 670 void initButtons()
mjr 11:bd9da7088e6e 671 {
mjr 11:bd9da7088e6e 672 // create the digital inputs
mjr 11:bd9da7088e6e 673 for (int i = 0 ; i < countof(buttonDigIn) ; ++i)
mjr 11:bd9da7088e6e 674 {
mjr 11:bd9da7088e6e 675 if (i < countof(buttonMap) && buttonMap[i] != NC)
mjr 11:bd9da7088e6e 676 buttonDigIn[i] = new DigitalIn(buttonMap[i]);
mjr 11:bd9da7088e6e 677 else
mjr 11:bd9da7088e6e 678 buttonDigIn[i] = 0;
mjr 11:bd9da7088e6e 679 }
mjr 12:669df364a565 680
mjr 12:669df364a565 681 // start the button timer
mjr 12:669df364a565 682 buttonTimer.start();
mjr 11:bd9da7088e6e 683 }
mjr 11:bd9da7088e6e 684
mjr 11:bd9da7088e6e 685
mjr 11:bd9da7088e6e 686 // read the raw button input state
mjr 11:bd9da7088e6e 687 uint32_t readButtonsRaw()
mjr 11:bd9da7088e6e 688 {
mjr 11:bd9da7088e6e 689 // start with all buttons off
mjr 11:bd9da7088e6e 690 uint32_t buttons = 0;
mjr 11:bd9da7088e6e 691
mjr 11:bd9da7088e6e 692 // scan the button list
mjr 11:bd9da7088e6e 693 uint32_t bit = 1;
mjr 11:bd9da7088e6e 694 for (int i = 0 ; i < countof(buttonDigIn) ; ++i, bit <<= 1)
mjr 11:bd9da7088e6e 695 {
mjr 11:bd9da7088e6e 696 if (buttonDigIn[i] != 0 && !buttonDigIn[i]->read())
mjr 11:bd9da7088e6e 697 buttons |= bit;
mjr 11:bd9da7088e6e 698 }
mjr 11:bd9da7088e6e 699
mjr 11:bd9da7088e6e 700 // return the button list
mjr 11:bd9da7088e6e 701 return buttons;
mjr 11:bd9da7088e6e 702 }
mjr 11:bd9da7088e6e 703
mjr 11:bd9da7088e6e 704 // Read buttons with debouncing. We keep a circular buffer
mjr 11:bd9da7088e6e 705 // of recent input readings. We'll AND together the status of
mjr 11:bd9da7088e6e 706 // each button over the past 50ms. A button that has been on
mjr 11:bd9da7088e6e 707 // continuously for 50ms will be reported as ON. All others
mjr 11:bd9da7088e6e 708 // will be reported as OFF.
mjr 11:bd9da7088e6e 709 uint32_t readButtonsDebounced()
mjr 11:bd9da7088e6e 710 {
mjr 11:bd9da7088e6e 711 struct reading {
mjr 11:bd9da7088e6e 712 int dt; // time since previous reading
mjr 11:bd9da7088e6e 713 uint32_t b; // button state at this reading
mjr 11:bd9da7088e6e 714 };
mjr 11:bd9da7088e6e 715 static reading readings[8]; // circular buffer of readings
mjr 11:bd9da7088e6e 716 static int ri = 0; // reading buffer index (next write position)
mjr 11:bd9da7088e6e 717
mjr 11:bd9da7088e6e 718 // get the write pointer
mjr 11:bd9da7088e6e 719 reading *r = &readings[ri];
mjr 11:bd9da7088e6e 720
mjr 11:bd9da7088e6e 721 // figure the time since the last reading, and read the raw button state
mjr 12:669df364a565 722 r->dt = buttonTimer.read_ms();
mjr 11:bd9da7088e6e 723 uint32_t b = r->b = readButtonsRaw();
mjr 11:bd9da7088e6e 724
mjr 11:bd9da7088e6e 725 // start timing the next interval
mjr 12:669df364a565 726 buttonTimer.reset();
mjr 11:bd9da7088e6e 727
mjr 14:df700b22ca08 728 // AND together readings over 25ms
mjr 11:bd9da7088e6e 729 int ms = 0;
mjr 14:df700b22ca08 730 for (int i = 1 ; i < countof(readings) && ms < 25 ; ++i)
mjr 11:bd9da7088e6e 731 {
mjr 11:bd9da7088e6e 732 // find the next prior reading, wrapping in the circular buffer
mjr 11:bd9da7088e6e 733 int j = ri - i;
mjr 11:bd9da7088e6e 734 if (j < 0)
mjr 11:bd9da7088e6e 735 j = countof(readings) - 1;
mjr 11:bd9da7088e6e 736
mjr 11:bd9da7088e6e 737 reading *rj = &readings[j];
mjr 11:bd9da7088e6e 738
mjr 11:bd9da7088e6e 739 // AND the buttons for this reading
mjr 11:bd9da7088e6e 740 b &= rj->b;
mjr 11:bd9da7088e6e 741
mjr 11:bd9da7088e6e 742 // count the time
mjr 11:bd9da7088e6e 743 ms += rj->dt;
mjr 11:bd9da7088e6e 744 }
mjr 11:bd9da7088e6e 745
mjr 11:bd9da7088e6e 746 // advance the write position for next time
mjr 11:bd9da7088e6e 747 ri += 1;
mjr 12:669df364a565 748 if (ri >= countof(readings))
mjr 11:bd9da7088e6e 749 ri = 0;
mjr 11:bd9da7088e6e 750
mjr 11:bd9da7088e6e 751 // return the debounced result
mjr 11:bd9da7088e6e 752 return b;
mjr 11:bd9da7088e6e 753 }
mjr 11:bd9da7088e6e 754
mjr 5:a70c0bce770d 755 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 756 //
mjr 5:a70c0bce770d 757 // Non-volatile memory (NVM)
mjr 5:a70c0bce770d 758 //
mjr 0:5acbbe3f4cf4 759
mjr 5:a70c0bce770d 760 // Structure defining our NVM storage layout. We store a small
mjr 2:c174f9ee414a 761 // amount of persistent data in flash memory to retain calibration
mjr 5:a70c0bce770d 762 // data when powered off.
mjr 2:c174f9ee414a 763 struct NVM
mjr 2:c174f9ee414a 764 {
mjr 2:c174f9ee414a 765 // checksum - we use this to determine if the flash record
mjr 6:cc35eb643e8f 766 // has been properly initialized
mjr 2:c174f9ee414a 767 uint32_t checksum;
mjr 2:c174f9ee414a 768
mjr 2:c174f9ee414a 769 // signature value
mjr 2:c174f9ee414a 770 static const uint32_t SIGNATURE = 0x4D4A522A;
mjr 6:cc35eb643e8f 771 static const uint16_t VERSION = 0x0003;
mjr 6:cc35eb643e8f 772
mjr 6:cc35eb643e8f 773 // Is the data structure valid? We test the signature and
mjr 6:cc35eb643e8f 774 // checksum to determine if we've been properly stored.
mjr 6:cc35eb643e8f 775 int valid() const
mjr 6:cc35eb643e8f 776 {
mjr 6:cc35eb643e8f 777 return (d.sig == SIGNATURE
mjr 6:cc35eb643e8f 778 && d.vsn == VERSION
mjr 6:cc35eb643e8f 779 && d.sz == sizeof(NVM)
mjr 6:cc35eb643e8f 780 && checksum == CRC32(&d, sizeof(d)));
mjr 6:cc35eb643e8f 781 }
mjr 6:cc35eb643e8f 782
mjr 6:cc35eb643e8f 783 // save to non-volatile memory
mjr 6:cc35eb643e8f 784 void save(FreescaleIAP &iap, int addr)
mjr 6:cc35eb643e8f 785 {
mjr 6:cc35eb643e8f 786 // update the checksum and structure size
mjr 6:cc35eb643e8f 787 checksum = CRC32(&d, sizeof(d));
mjr 6:cc35eb643e8f 788 d.sz = sizeof(NVM);
mjr 6:cc35eb643e8f 789
mjr 6:cc35eb643e8f 790 // erase the sector
mjr 6:cc35eb643e8f 791 iap.erase_sector(addr);
mjr 6:cc35eb643e8f 792
mjr 6:cc35eb643e8f 793 // save the data
mjr 6:cc35eb643e8f 794 iap.program_flash(addr, this, sizeof(*this));
mjr 6:cc35eb643e8f 795 }
mjr 2:c174f9ee414a 796
mjr 9:fd65b0a94720 797 // reset calibration data for calibration mode
mjr 9:fd65b0a94720 798 void resetPlunger()
mjr 9:fd65b0a94720 799 {
mjr 9:fd65b0a94720 800 // set extremes for the calibration data
mjr 9:fd65b0a94720 801 d.plungerMax = 0;
mjr 9:fd65b0a94720 802 d.plungerZero = npix;
mjr 9:fd65b0a94720 803 d.plungerMin = npix;
mjr 9:fd65b0a94720 804 }
mjr 9:fd65b0a94720 805
mjr 2:c174f9ee414a 806 // stored data (excluding the checksum)
mjr 2:c174f9ee414a 807 struct
mjr 2:c174f9ee414a 808 {
mjr 6:cc35eb643e8f 809 // Signature, structure version, and structure size - further verification
mjr 6:cc35eb643e8f 810 // that we have valid initialized data. The size is a simple proxy for a
mjr 6:cc35eb643e8f 811 // structure version, as the most common type of change to the structure as
mjr 6:cc35eb643e8f 812 // the software evolves will be the addition of new elements. We also
mjr 6:cc35eb643e8f 813 // provide an explicit version number that we can update manually if we
mjr 6:cc35eb643e8f 814 // make any changes that don't affect the structure size but would affect
mjr 6:cc35eb643e8f 815 // compatibility with a saved record (e.g., swapping two existing elements).
mjr 2:c174f9ee414a 816 uint32_t sig;
mjr 2:c174f9ee414a 817 uint16_t vsn;
mjr 6:cc35eb643e8f 818 int sz;
mjr 2:c174f9ee414a 819
mjr 6:cc35eb643e8f 820 // has the plunger been manually calibrated?
mjr 6:cc35eb643e8f 821 int plungerCal;
mjr 6:cc35eb643e8f 822
mjr 2:c174f9ee414a 823 // plunger calibration min and max
mjr 2:c174f9ee414a 824 int plungerMin;
mjr 6:cc35eb643e8f 825 int plungerZero;
mjr 2:c174f9ee414a 826 int plungerMax;
mjr 6:cc35eb643e8f 827
mjr 6:cc35eb643e8f 828 // is the CCD enabled?
mjr 6:cc35eb643e8f 829 int ccdEnabled;
mjr 6:cc35eb643e8f 830
mjr 6:cc35eb643e8f 831 // LedWiz unit number
mjr 6:cc35eb643e8f 832 uint8_t ledWizUnitNo;
mjr 2:c174f9ee414a 833 } d;
mjr 2:c174f9ee414a 834 };
mjr 2:c174f9ee414a 835
mjr 5:a70c0bce770d 836
mjr 5:a70c0bce770d 837 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 838 //
mjr 5:a70c0bce770d 839 // Customization joystick subbclass
mjr 5:a70c0bce770d 840 //
mjr 5:a70c0bce770d 841
mjr 5:a70c0bce770d 842 class MyUSBJoystick: public USBJoystick
mjr 5:a70c0bce770d 843 {
mjr 5:a70c0bce770d 844 public:
mjr 5:a70c0bce770d 845 MyUSBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release)
mjr 5:a70c0bce770d 846 : USBJoystick(vendor_id, product_id, product_release, true)
mjr 5:a70c0bce770d 847 {
mjr 5:a70c0bce770d 848 suspended_ = false;
mjr 5:a70c0bce770d 849 }
mjr 5:a70c0bce770d 850
mjr 5:a70c0bce770d 851 // are we connected?
mjr 5:a70c0bce770d 852 int isConnected() { return configured(); }
mjr 5:a70c0bce770d 853
mjr 5:a70c0bce770d 854 // Are we in suspend mode?
mjr 5:a70c0bce770d 855 int isSuspended() const { return suspended_; }
mjr 5:a70c0bce770d 856
mjr 5:a70c0bce770d 857 protected:
mjr 5:a70c0bce770d 858 virtual void suspendStateChanged(unsigned int suspended)
mjr 5:a70c0bce770d 859 { suspended_ = suspended; }
mjr 5:a70c0bce770d 860
mjr 5:a70c0bce770d 861 // are we suspended?
mjr 5:a70c0bce770d 862 int suspended_;
mjr 5:a70c0bce770d 863 };
mjr 5:a70c0bce770d 864
mjr 5:a70c0bce770d 865 // ---------------------------------------------------------------------------
mjr 6:cc35eb643e8f 866 //
mjr 6:cc35eb643e8f 867 // Some simple math service routines
mjr 6:cc35eb643e8f 868 //
mjr 6:cc35eb643e8f 869
mjr 6:cc35eb643e8f 870 inline float square(float x) { return x*x; }
mjr 6:cc35eb643e8f 871 inline float round(float x) { return x > 0 ? floor(x + 0.5) : ceil(x - 0.5); }
mjr 6:cc35eb643e8f 872
mjr 6:cc35eb643e8f 873 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 874 //
mjr 5:a70c0bce770d 875 // Accelerometer (MMA8451Q)
mjr 5:a70c0bce770d 876 //
mjr 5:a70c0bce770d 877
mjr 5:a70c0bce770d 878 // The MMA8451Q is the KL25Z's on-board 3-axis accelerometer.
mjr 5:a70c0bce770d 879 //
mjr 5:a70c0bce770d 880 // This is a custom wrapper for the library code to interface to the
mjr 6:cc35eb643e8f 881 // MMA8451Q. This class encapsulates an interrupt handler and
mjr 6:cc35eb643e8f 882 // automatic calibration.
mjr 5:a70c0bce770d 883 //
mjr 5:a70c0bce770d 884 // We install an interrupt handler on the accelerometer "data ready"
mjr 6:cc35eb643e8f 885 // interrupt to ensure that we fetch each sample immediately when it
mjr 6:cc35eb643e8f 886 // becomes available. The accelerometer data rate is fiarly high
mjr 6:cc35eb643e8f 887 // (800 Hz), so it's not practical to keep up with it by polling.
mjr 6:cc35eb643e8f 888 // Using an interrupt handler lets us respond quickly and read
mjr 6:cc35eb643e8f 889 // every sample.
mjr 5:a70c0bce770d 890 //
mjr 6:cc35eb643e8f 891 // We automatically calibrate the accelerometer so that it's not
mjr 6:cc35eb643e8f 892 // necessary to get it exactly level when installing it, and so
mjr 6:cc35eb643e8f 893 // that it's also not necessary to calibrate it manually. There's
mjr 6:cc35eb643e8f 894 // lots of experience that tells us that manual calibration is a
mjr 6:cc35eb643e8f 895 // terrible solution, mostly because cabinets tend to shift slightly
mjr 6:cc35eb643e8f 896 // during use, requiring frequent recalibration. Instead, we
mjr 6:cc35eb643e8f 897 // calibrate automatically. We continuously monitor the acceleration
mjr 6:cc35eb643e8f 898 // data, watching for periods of constant (or nearly constant) values.
mjr 6:cc35eb643e8f 899 // Any time it appears that the machine has been at rest for a while
mjr 6:cc35eb643e8f 900 // (about 5 seconds), we'll average the readings during that rest
mjr 6:cc35eb643e8f 901 // period and use the result as the level rest position. This is
mjr 6:cc35eb643e8f 902 // is ongoing, so we'll quickly find the center point again if the
mjr 6:cc35eb643e8f 903 // machine is moved during play (by an especially aggressive bout
mjr 6:cc35eb643e8f 904 // of nudging, say).
mjr 5:a70c0bce770d 905 //
mjr 5:a70c0bce770d 906
mjr 6:cc35eb643e8f 907 // accelerometer input history item, for gathering calibration data
mjr 6:cc35eb643e8f 908 struct AccHist
mjr 5:a70c0bce770d 909 {
mjr 6:cc35eb643e8f 910 AccHist() { x = y = d = 0.0; xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 911 void set(float x, float y, AccHist *prv)
mjr 6:cc35eb643e8f 912 {
mjr 6:cc35eb643e8f 913 // save the raw position
mjr 6:cc35eb643e8f 914 this->x = x;
mjr 6:cc35eb643e8f 915 this->y = y;
mjr 6:cc35eb643e8f 916 this->d = distance(prv);
mjr 6:cc35eb643e8f 917 }
mjr 6:cc35eb643e8f 918
mjr 6:cc35eb643e8f 919 // reading for this entry
mjr 5:a70c0bce770d 920 float x, y;
mjr 5:a70c0bce770d 921
mjr 6:cc35eb643e8f 922 // distance from previous entry
mjr 6:cc35eb643e8f 923 float d;
mjr 5:a70c0bce770d 924
mjr 6:cc35eb643e8f 925 // total and count of samples averaged over this period
mjr 6:cc35eb643e8f 926 float xtot, ytot;
mjr 6:cc35eb643e8f 927 int cnt;
mjr 6:cc35eb643e8f 928
mjr 6:cc35eb643e8f 929 void clearAvg() { xtot = ytot = 0.0; cnt = 0; }
mjr 6:cc35eb643e8f 930 void addAvg(float x, float y) { xtot += x; ytot += y; ++cnt; }
mjr 6:cc35eb643e8f 931 float xAvg() const { return xtot/cnt; }
mjr 6:cc35eb643e8f 932 float yAvg() const { return ytot/cnt; }
mjr 5:a70c0bce770d 933
mjr 6:cc35eb643e8f 934 float distance(AccHist *p)
mjr 6:cc35eb643e8f 935 { return sqrt(square(p->x - x) + square(p->y - y)); }
mjr 5:a70c0bce770d 936 };
mjr 5:a70c0bce770d 937
mjr 5:a70c0bce770d 938 // accelerometer wrapper class
mjr 3:3514575d4f86 939 class Accel
mjr 3:3514575d4f86 940 {
mjr 3:3514575d4f86 941 public:
mjr 3:3514575d4f86 942 Accel(PinName sda, PinName scl, int i2cAddr, PinName irqPin)
mjr 3:3514575d4f86 943 : mma_(sda, scl, i2cAddr), intIn_(irqPin)
mjr 3:3514575d4f86 944 {
mjr 5:a70c0bce770d 945 // remember the interrupt pin assignment
mjr 5:a70c0bce770d 946 irqPin_ = irqPin;
mjr 5:a70c0bce770d 947
mjr 5:a70c0bce770d 948 // reset and initialize
mjr 5:a70c0bce770d 949 reset();
mjr 5:a70c0bce770d 950 }
mjr 5:a70c0bce770d 951
mjr 5:a70c0bce770d 952 void reset()
mjr 5:a70c0bce770d 953 {
mjr 6:cc35eb643e8f 954 // clear the center point
mjr 6:cc35eb643e8f 955 cx_ = cy_ = 0.0;
mjr 6:cc35eb643e8f 956
mjr 6:cc35eb643e8f 957 // start the calibration timer
mjr 5:a70c0bce770d 958 tCenter_.start();
mjr 5:a70c0bce770d 959 iAccPrv_ = nAccPrv_ = 0;
mjr 6:cc35eb643e8f 960
mjr 5:a70c0bce770d 961 // reset and initialize the MMA8451Q
mjr 5:a70c0bce770d 962 mma_.init();
mjr 6:cc35eb643e8f 963
mjr 6:cc35eb643e8f 964 // set the initial integrated velocity reading to zero
mjr 6:cc35eb643e8f 965 vx_ = vy_ = 0;
mjr 3:3514575d4f86 966
mjr 6:cc35eb643e8f 967 // set up our accelerometer interrupt handling
mjr 6:cc35eb643e8f 968 intIn_.rise(this, &Accel::isr);
mjr 5:a70c0bce770d 969 mma_.setInterruptMode(irqPin_ == PTA14 ? 1 : 2);
mjr 3:3514575d4f86 970
mjr 3:3514575d4f86 971 // read the current registers to clear the data ready flag
mjr 6:cc35eb643e8f 972 mma_.getAccXYZ(ax_, ay_, az_);
mjr 3:3514575d4f86 973
mjr 3:3514575d4f86 974 // start our timers
mjr 3:3514575d4f86 975 tGet_.start();
mjr 3:3514575d4f86 976 tInt_.start();
mjr 3:3514575d4f86 977 }
mjr 3:3514575d4f86 978
mjr 9:fd65b0a94720 979 void get(int &x, int &y)
mjr 3:3514575d4f86 980 {
mjr 3:3514575d4f86 981 // disable interrupts while manipulating the shared data
mjr 3:3514575d4f86 982 __disable_irq();
mjr 3:3514575d4f86 983
mjr 3:3514575d4f86 984 // read the shared data and store locally for calculations
mjr 6:cc35eb643e8f 985 float ax = ax_, ay = ay_;
mjr 6:cc35eb643e8f 986 float vx = vx_, vy = vy_;
mjr 5:a70c0bce770d 987
mjr 6:cc35eb643e8f 988 // reset the velocity sum for the next run
mjr 6:cc35eb643e8f 989 vx_ = vy_ = 0;
mjr 3:3514575d4f86 990
mjr 3:3514575d4f86 991 // get the time since the last get() sample
mjr 3:3514575d4f86 992 float dt = tGet_.read_us()/1.0e6;
mjr 3:3514575d4f86 993 tGet_.reset();
mjr 3:3514575d4f86 994
mjr 3:3514575d4f86 995 // done manipulating the shared data
mjr 3:3514575d4f86 996 __enable_irq();
mjr 3:3514575d4f86 997
mjr 6:cc35eb643e8f 998 // adjust the readings for the integration time
mjr 6:cc35eb643e8f 999 vx /= dt;
mjr 6:cc35eb643e8f 1000 vy /= dt;
mjr 6:cc35eb643e8f 1001
mjr 6:cc35eb643e8f 1002 // add this sample to the current calibration interval's running total
mjr 6:cc35eb643e8f 1003 AccHist *p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 1004 p->addAvg(ax, ay);
mjr 6:cc35eb643e8f 1005
mjr 5:a70c0bce770d 1006 // check for auto-centering every so often
mjr 5:a70c0bce770d 1007 if (tCenter_.read_ms() > 1000)
mjr 5:a70c0bce770d 1008 {
mjr 5:a70c0bce770d 1009 // add the latest raw sample to the history list
mjr 6:cc35eb643e8f 1010 AccHist *prv = p;
mjr 5:a70c0bce770d 1011 iAccPrv_ = (iAccPrv_ + 1) % maxAccPrv;
mjr 6:cc35eb643e8f 1012 p = accPrv_ + iAccPrv_;
mjr 6:cc35eb643e8f 1013 p->set(ax, ay, prv);
mjr 5:a70c0bce770d 1014
mjr 5:a70c0bce770d 1015 // if we have a full complement, check for stability
mjr 5:a70c0bce770d 1016 if (nAccPrv_ >= maxAccPrv)
mjr 5:a70c0bce770d 1017 {
mjr 5:a70c0bce770d 1018 // check if we've been stable for all recent samples
mjr 6:cc35eb643e8f 1019 static const float accTol = .01;
mjr 6:cc35eb643e8f 1020 AccHist *p0 = accPrv_;
mjr 6:cc35eb643e8f 1021 if (p0[0].d < accTol
mjr 6:cc35eb643e8f 1022 && p0[1].d < accTol
mjr 6:cc35eb643e8f 1023 && p0[2].d < accTol
mjr 6:cc35eb643e8f 1024 && p0[3].d < accTol
mjr 6:cc35eb643e8f 1025 && p0[4].d < accTol)
mjr 5:a70c0bce770d 1026 {
mjr 6:cc35eb643e8f 1027 // Figure the new calibration point as the average of
mjr 6:cc35eb643e8f 1028 // the samples over the rest period
mjr 6:cc35eb643e8f 1029 cx_ = (p0[0].xAvg() + p0[1].xAvg() + p0[2].xAvg() + p0[3].xAvg() + p0[4].xAvg())/5.0;
mjr 6:cc35eb643e8f 1030 cy_ = (p0[0].yAvg() + p0[1].yAvg() + p0[2].yAvg() + p0[3].yAvg() + p0[4].yAvg())/5.0;
mjr 5:a70c0bce770d 1031 }
mjr 5:a70c0bce770d 1032 }
mjr 5:a70c0bce770d 1033 else
mjr 5:a70c0bce770d 1034 {
mjr 5:a70c0bce770d 1035 // not enough samples yet; just up the count
mjr 5:a70c0bce770d 1036 ++nAccPrv_;
mjr 5:a70c0bce770d 1037 }
mjr 6:cc35eb643e8f 1038
mjr 6:cc35eb643e8f 1039 // clear the new item's running totals
mjr 6:cc35eb643e8f 1040 p->clearAvg();
mjr 5:a70c0bce770d 1041
mjr 5:a70c0bce770d 1042 // reset the timer
mjr 5:a70c0bce770d 1043 tCenter_.reset();
mjr 5:a70c0bce770d 1044 }
mjr 5:a70c0bce770d 1045
mjr 6:cc35eb643e8f 1046 // report our integrated velocity reading in x,y
mjr 6:cc35eb643e8f 1047 x = rawToReport(vx);
mjr 6:cc35eb643e8f 1048 y = rawToReport(vy);
mjr 5:a70c0bce770d 1049
mjr 6:cc35eb643e8f 1050 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 1051 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 1052 printf("%f %f %d %d %f\r\n", vx, vy, x, y, dt);
mjr 6:cc35eb643e8f 1053 #endif
mjr 3:3514575d4f86 1054 }
mjr 3:3514575d4f86 1055
mjr 3:3514575d4f86 1056 private:
mjr 6:cc35eb643e8f 1057 // adjust a raw acceleration figure to a usb report value
mjr 6:cc35eb643e8f 1058 int rawToReport(float v)
mjr 5:a70c0bce770d 1059 {
mjr 6:cc35eb643e8f 1060 // scale to the joystick report range and round to integer
mjr 6:cc35eb643e8f 1061 int i = int(round(v*JOYMAX));
mjr 5:a70c0bce770d 1062
mjr 6:cc35eb643e8f 1063 // if it's near the center, scale it roughly as 20*(i/20)^2,
mjr 6:cc35eb643e8f 1064 // to suppress noise near the rest position
mjr 6:cc35eb643e8f 1065 static const int filter[] = {
mjr 6:cc35eb643e8f 1066 -18, -16, -14, -13, -11, -10, -8, -7, -6, -5, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0,
mjr 6:cc35eb643e8f 1067 0,
mjr 6:cc35eb643e8f 1068 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 16, 18
mjr 6:cc35eb643e8f 1069 };
mjr 6:cc35eb643e8f 1070 return (i > 20 || i < -20 ? i : filter[i+20]);
mjr 5:a70c0bce770d 1071 }
mjr 5:a70c0bce770d 1072
mjr 3:3514575d4f86 1073 // interrupt handler
mjr 3:3514575d4f86 1074 void isr()
mjr 3:3514575d4f86 1075 {
mjr 3:3514575d4f86 1076 // Read the axes. Note that we have to read all three axes
mjr 3:3514575d4f86 1077 // (even though we only really use x and y) in order to clear
mjr 3:3514575d4f86 1078 // the "data ready" status bit in the accelerometer. The
mjr 3:3514575d4f86 1079 // interrupt only occurs when the "ready" bit transitions from
mjr 3:3514575d4f86 1080 // off to on, so we have to make sure it's off.
mjr 5:a70c0bce770d 1081 float x, y, z;
mjr 5:a70c0bce770d 1082 mma_.getAccXYZ(x, y, z);
mjr 3:3514575d4f86 1083
mjr 3:3514575d4f86 1084 // calculate the time since the last interrupt
mjr 3:3514575d4f86 1085 float dt = tInt_.read_us()/1.0e6;
mjr 3:3514575d4f86 1086 tInt_.reset();
mjr 6:cc35eb643e8f 1087
mjr 6:cc35eb643e8f 1088 // integrate the time slice from the previous reading to this reading
mjr 6:cc35eb643e8f 1089 vx_ += (x + ax_ - 2*cx_)*dt/2;
mjr 6:cc35eb643e8f 1090 vy_ += (y + ay_ - 2*cy_)*dt/2;
mjr 3:3514575d4f86 1091
mjr 6:cc35eb643e8f 1092 // store the updates
mjr 6:cc35eb643e8f 1093 ax_ = x;
mjr 6:cc35eb643e8f 1094 ay_ = y;
mjr 6:cc35eb643e8f 1095 az_ = z;
mjr 3:3514575d4f86 1096 }
mjr 3:3514575d4f86 1097
mjr 3:3514575d4f86 1098 // underlying accelerometer object
mjr 3:3514575d4f86 1099 MMA8451Q mma_;
mjr 3:3514575d4f86 1100
mjr 5:a70c0bce770d 1101 // last raw acceleration readings
mjr 6:cc35eb643e8f 1102 float ax_, ay_, az_;
mjr 5:a70c0bce770d 1103
mjr 6:cc35eb643e8f 1104 // integrated velocity reading since last get()
mjr 6:cc35eb643e8f 1105 float vx_, vy_;
mjr 6:cc35eb643e8f 1106
mjr 3:3514575d4f86 1107 // timer for measuring time between get() samples
mjr 3:3514575d4f86 1108 Timer tGet_;
mjr 3:3514575d4f86 1109
mjr 3:3514575d4f86 1110 // timer for measuring time between interrupts
mjr 3:3514575d4f86 1111 Timer tInt_;
mjr 5:a70c0bce770d 1112
mjr 6:cc35eb643e8f 1113 // Calibration reference point for accelerometer. This is the
mjr 6:cc35eb643e8f 1114 // average reading on the accelerometer when in the neutral position
mjr 6:cc35eb643e8f 1115 // at rest.
mjr 6:cc35eb643e8f 1116 float cx_, cy_;
mjr 5:a70c0bce770d 1117
mjr 5:a70c0bce770d 1118 // timer for atuo-centering
mjr 5:a70c0bce770d 1119 Timer tCenter_;
mjr 6:cc35eb643e8f 1120
mjr 6:cc35eb643e8f 1121 // Auto-centering history. This is a separate history list that
mjr 6:cc35eb643e8f 1122 // records results spaced out sparesely over time, so that we can
mjr 6:cc35eb643e8f 1123 // watch for long-lasting periods of rest. When we observe nearly
mjr 6:cc35eb643e8f 1124 // no motion for an extended period (on the order of 5 seconds), we
mjr 6:cc35eb643e8f 1125 // take this to mean that the cabinet is at rest in its neutral
mjr 6:cc35eb643e8f 1126 // position, so we take this as the calibration zero point for the
mjr 6:cc35eb643e8f 1127 // accelerometer. We update this history continuously, which allows
mjr 6:cc35eb643e8f 1128 // us to continuously re-calibrate the accelerometer. This ensures
mjr 6:cc35eb643e8f 1129 // that we'll automatically adjust to any actual changes in the
mjr 6:cc35eb643e8f 1130 // cabinet's orientation (e.g., if it gets moved slightly by an
mjr 6:cc35eb643e8f 1131 // especially strong nudge) as well as any systematic drift in the
mjr 6:cc35eb643e8f 1132 // accelerometer measurement bias (e.g., from temperature changes).
mjr 5:a70c0bce770d 1133 int iAccPrv_, nAccPrv_;
mjr 5:a70c0bce770d 1134 static const int maxAccPrv = 5;
mjr 6:cc35eb643e8f 1135 AccHist accPrv_[maxAccPrv];
mjr 6:cc35eb643e8f 1136
mjr 5:a70c0bce770d 1137 // interurupt pin name
mjr 5:a70c0bce770d 1138 PinName irqPin_;
mjr 5:a70c0bce770d 1139
mjr 5:a70c0bce770d 1140 // interrupt router
mjr 5:a70c0bce770d 1141 InterruptIn intIn_;
mjr 3:3514575d4f86 1142 };
mjr 3:3514575d4f86 1143
mjr 5:a70c0bce770d 1144
mjr 5:a70c0bce770d 1145 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 1146 //
mjr 14:df700b22ca08 1147 // Clear the I2C bus for the MMA8451Q. This seems necessary some of the time
mjr 5:a70c0bce770d 1148 // for reasons that aren't clear to me. Doing a hard power cycle has the same
mjr 5:a70c0bce770d 1149 // effect, but when we do a soft reset, the hardware sometimes seems to leave
mjr 5:a70c0bce770d 1150 // the MMA's SDA line stuck low. Forcing a series of 9 clock pulses through
mjr 14:df700b22ca08 1151 // the SCL line is supposed to clear this condition. I'm not convinced this
mjr 14:df700b22ca08 1152 // actually works with the way this component is wired on the KL25Z, but it
mjr 14:df700b22ca08 1153 // seems harmless, so we'll do it on reset in case it does some good. What
mjr 14:df700b22ca08 1154 // we really seem to need is a way to power cycle the MMA8451Q if it ever
mjr 14:df700b22ca08 1155 // gets stuck, but this is simply not possible in software on the KL25Z.
mjr 14:df700b22ca08 1156 //
mjr 14:df700b22ca08 1157 // If the accelerometer does get stuck, and a software reboot doesn't reset
mjr 14:df700b22ca08 1158 // it, the only workaround is to manually power cycle the whole KL25Z by
mjr 14:df700b22ca08 1159 // unplugging both of its USB connections.
mjr 5:a70c0bce770d 1160 //
mjr 5:a70c0bce770d 1161 void clear_i2c()
mjr 5:a70c0bce770d 1162 {
mjr 5:a70c0bce770d 1163 // assume a general-purpose output pin to the I2C clock
mjr 5:a70c0bce770d 1164 DigitalOut scl(MMA8451_SCL_PIN);
mjr 5:a70c0bce770d 1165 DigitalIn sda(MMA8451_SDA_PIN);
mjr 5:a70c0bce770d 1166
mjr 5:a70c0bce770d 1167 // clock the SCL 9 times
mjr 5:a70c0bce770d 1168 for (int i = 0 ; i < 9 ; ++i)
mjr 5:a70c0bce770d 1169 {
mjr 5:a70c0bce770d 1170 scl = 1;
mjr 5:a70c0bce770d 1171 wait_us(20);
mjr 5:a70c0bce770d 1172 scl = 0;
mjr 5:a70c0bce770d 1173 wait_us(20);
mjr 5:a70c0bce770d 1174 }
mjr 5:a70c0bce770d 1175 }
mjr 14:df700b22ca08 1176
mjr 14:df700b22ca08 1177 // ---------------------------------------------------------------------------
mjr 14:df700b22ca08 1178 //
mjr 14:df700b22ca08 1179 // CCD read interval callback. When reading the CCD, we'll call this
mjr 14:df700b22ca08 1180 // several times over the course of the read loop to refresh the button
mjr 14:df700b22ca08 1181 // states. This allows us to debounce the buttons while the long CCD
mjr 14:df700b22ca08 1182 // read cycle is taking place, so that we can reliably report button
mjr 14:df700b22ca08 1183 // states after each CCD read cycle. (The read cycle takes about 30ms,
mjr 14:df700b22ca08 1184 // which should be enough time to reliably debounce the buttons.)
mjr 14:df700b22ca08 1185 //
mjr 14:df700b22ca08 1186 void ccdReadCB(void *)
mjr 14:df700b22ca08 1187 {
mjr 14:df700b22ca08 1188 // read the keyboard
mjr 14:df700b22ca08 1189 readButtonsDebounced();
mjr 14:df700b22ca08 1190 }
mjr 5:a70c0bce770d 1191
mjr 5:a70c0bce770d 1192 // ---------------------------------------------------------------------------
mjr 5:a70c0bce770d 1193 //
mjr 5:a70c0bce770d 1194 // Main program loop. This is invoked on startup and runs forever. Our
mjr 5:a70c0bce770d 1195 // main work is to read our devices (the accelerometer and the CCD), process
mjr 5:a70c0bce770d 1196 // the readings into nudge and plunger position data, and send the results
mjr 5:a70c0bce770d 1197 // to the host computer via the USB joystick interface. We also monitor
mjr 5:a70c0bce770d 1198 // the USB connection for incoming LedWiz commands and process those into
mjr 5:a70c0bce770d 1199 // port outputs.
mjr 5:a70c0bce770d 1200 //
mjr 0:5acbbe3f4cf4 1201 int main(void)
mjr 0:5acbbe3f4cf4 1202 {
mjr 1:d913e0afb2ac 1203 // turn off our on-board indicator LED
mjr 4:02c7cd7b2183 1204 ledR = 1;
mjr 4:02c7cd7b2183 1205 ledG = 1;
mjr 4:02c7cd7b2183 1206 ledB = 1;
mjr 1:d913e0afb2ac 1207
mjr 6:cc35eb643e8f 1208 // initialize the LedWiz ports
mjr 6:cc35eb643e8f 1209 initLwOut();
mjr 6:cc35eb643e8f 1210
mjr 11:bd9da7088e6e 1211 // initialize the button input ports
mjr 11:bd9da7088e6e 1212 initButtons();
mjr 11:bd9da7088e6e 1213
mjr 6:cc35eb643e8f 1214 // we don't need a reset yet
mjr 6:cc35eb643e8f 1215 bool needReset = false;
mjr 6:cc35eb643e8f 1216
mjr 5:a70c0bce770d 1217 // clear the I2C bus for the accelerometer
mjr 5:a70c0bce770d 1218 clear_i2c();
mjr 5:a70c0bce770d 1219
mjr 2:c174f9ee414a 1220 // set up a flash memory controller
mjr 2:c174f9ee414a 1221 FreescaleIAP iap;
mjr 2:c174f9ee414a 1222
mjr 2:c174f9ee414a 1223 // use the last sector of flash for our non-volatile memory structure
mjr 2:c174f9ee414a 1224 int flash_addr = (iap.flash_size() - SECTOR_SIZE);
mjr 2:c174f9ee414a 1225 NVM *flash = (NVM *)flash_addr;
mjr 2:c174f9ee414a 1226 NVM cfg;
mjr 2:c174f9ee414a 1227
mjr 2:c174f9ee414a 1228 // check for valid flash
mjr 6:cc35eb643e8f 1229 bool flash_valid = flash->valid();
mjr 2:c174f9ee414a 1230
mjr 2:c174f9ee414a 1231 // if the flash is valid, load it; otherwise initialize to defaults
mjr 2:c174f9ee414a 1232 if (flash_valid) {
mjr 2:c174f9ee414a 1233 memcpy(&cfg, flash, sizeof(cfg));
mjr 6:cc35eb643e8f 1234 printf("Flash restored: plunger cal=%d, min=%d, zero=%d, max=%d\r\n",
mjr 6:cc35eb643e8f 1235 cfg.d.plungerCal, cfg.d.plungerMin, cfg.d.plungerZero, cfg.d.plungerMax);
mjr 2:c174f9ee414a 1236 }
mjr 2:c174f9ee414a 1237 else {
mjr 2:c174f9ee414a 1238 printf("Factory reset\r\n");
mjr 2:c174f9ee414a 1239 cfg.d.sig = cfg.SIGNATURE;
mjr 2:c174f9ee414a 1240 cfg.d.vsn = cfg.VERSION;
mjr 6:cc35eb643e8f 1241 cfg.d.plungerCal = 0;
mjr 6:cc35eb643e8f 1242 cfg.d.plungerZero = 0;
mjr 2:c174f9ee414a 1243 cfg.d.plungerMin = 0;
mjr 2:c174f9ee414a 1244 cfg.d.plungerMax = npix;
mjr 6:cc35eb643e8f 1245 cfg.d.ledWizUnitNo = DEFAULT_LEDWIZ_UNIT_NUMBER;
mjr 6:cc35eb643e8f 1246 cfg.d.ccdEnabled = true;
mjr 2:c174f9ee414a 1247 }
mjr 1:d913e0afb2ac 1248
mjr 6:cc35eb643e8f 1249 // Create the joystick USB client. Note that we use the LedWiz unit
mjr 6:cc35eb643e8f 1250 // number from the saved configuration.
mjr 6:cc35eb643e8f 1251 MyUSBJoystick js(
mjr 6:cc35eb643e8f 1252 USB_VENDOR_ID,
mjr 6:cc35eb643e8f 1253 USB_PRODUCT_ID | cfg.d.ledWizUnitNo,
mjr 6:cc35eb643e8f 1254 USB_VERSION_NO);
mjr 6:cc35eb643e8f 1255
mjr 1:d913e0afb2ac 1256 // plunger calibration button debounce timer
mjr 1:d913e0afb2ac 1257 Timer calBtnTimer;
mjr 1:d913e0afb2ac 1258 calBtnTimer.start();
mjr 1:d913e0afb2ac 1259 int calBtnLit = false;
mjr 1:d913e0afb2ac 1260
mjr 1:d913e0afb2ac 1261 // Calibration button state:
mjr 1:d913e0afb2ac 1262 // 0 = not pushed
mjr 1:d913e0afb2ac 1263 // 1 = pushed, not yet debounced
mjr 1:d913e0afb2ac 1264 // 2 = pushed, debounced, waiting for hold time
mjr 1:d913e0afb2ac 1265 // 3 = pushed, hold time completed - in calibration mode
mjr 1:d913e0afb2ac 1266 int calBtnState = 0;
mjr 1:d913e0afb2ac 1267
mjr 1:d913e0afb2ac 1268 // set up a timer for our heartbeat indicator
mjr 1:d913e0afb2ac 1269 Timer hbTimer;
mjr 1:d913e0afb2ac 1270 hbTimer.start();
mjr 1:d913e0afb2ac 1271 int hb = 0;
mjr 5:a70c0bce770d 1272 uint16_t hbcnt = 0;
mjr 1:d913e0afb2ac 1273
mjr 1:d913e0afb2ac 1274 // set a timer for accelerometer auto-centering
mjr 1:d913e0afb2ac 1275 Timer acTimer;
mjr 1:d913e0afb2ac 1276 acTimer.start();
mjr 1:d913e0afb2ac 1277
mjr 0:5acbbe3f4cf4 1278 // create the accelerometer object
mjr 5:a70c0bce770d 1279 Accel accel(MMA8451_SCL_PIN, MMA8451_SDA_PIN, MMA8451_I2C_ADDRESS, MMA8451_INT_PIN);
mjr 0:5acbbe3f4cf4 1280
mjr 0:5acbbe3f4cf4 1281 // create the CCD array object
mjr 1:d913e0afb2ac 1282 TSL1410R ccd(PTE20, PTE21, PTB0);
mjr 2:c174f9ee414a 1283
mjr 1:d913e0afb2ac 1284 // last accelerometer report, in mouse coordinates
mjr 6:cc35eb643e8f 1285 int x = 0, y = 0, z = 0;
mjr 6:cc35eb643e8f 1286
mjr 6:cc35eb643e8f 1287 // previous two plunger readings, for "debouncing" the results (z0 is
mjr 6:cc35eb643e8f 1288 // the most recent, z1 is the one before that)
mjr 6:cc35eb643e8f 1289 int z0 = 0, z1 = 0, z2 = 0;
mjr 6:cc35eb643e8f 1290
mjr 6:cc35eb643e8f 1291 // Firing in progress: we set this when we detect the start of rapid
mjr 6:cc35eb643e8f 1292 // plunger movement from a retracted position towards the rest position.
mjr 6:cc35eb643e8f 1293 // The actual plunger spring return speed seems to be too slow for VP,
mjr 6:cc35eb643e8f 1294 // so when we detect the start of this motion, we immediately tell VP
mjr 6:cc35eb643e8f 1295 // to return the plunger to rest, then we monitor the real plunger
mjr 6:cc35eb643e8f 1296 // until it atcually stops.
mjr 9:fd65b0a94720 1297 int firing = 0;
mjr 2:c174f9ee414a 1298
mjr 2:c174f9ee414a 1299 // start the first CCD integration cycle
mjr 2:c174f9ee414a 1300 ccd.clear();
mjr 9:fd65b0a94720 1301
mjr 9:fd65b0a94720 1302 // Device status. We report this on each update so that the host config
mjr 9:fd65b0a94720 1303 // tool can detect our current settings. This is a bit mask consisting
mjr 9:fd65b0a94720 1304 // of these bits:
mjr 9:fd65b0a94720 1305 // 0x01 -> plunger sensor enabled
mjr 9:fd65b0a94720 1306 uint16_t statusFlags = (cfg.d.ccdEnabled ? 0x01 : 0x00);
mjr 10:976666ffa4ef 1307
mjr 10:976666ffa4ef 1308 // flag: send a pixel dump after the next read
mjr 10:976666ffa4ef 1309 bool reportPix = false;
mjr 1:d913e0afb2ac 1310
mjr 1:d913e0afb2ac 1311 // we're all set up - now just loop, processing sensor reports and
mjr 1:d913e0afb2ac 1312 // host requests
mjr 0:5acbbe3f4cf4 1313 for (;;)
mjr 0:5acbbe3f4cf4 1314 {
mjr 0:5acbbe3f4cf4 1315 // Look for an incoming report. Continue processing input as
mjr 0:5acbbe3f4cf4 1316 // long as there's anything pending - this ensures that we
mjr 0:5acbbe3f4cf4 1317 // handle input in as timely a fashion as possible by deferring
mjr 0:5acbbe3f4cf4 1318 // output tasks as long as there's input to process.
mjr 0:5acbbe3f4cf4 1319 HID_REPORT report;
mjr 6:cc35eb643e8f 1320 while (js.readNB(&report))
mjr 0:5acbbe3f4cf4 1321 {
mjr 6:cc35eb643e8f 1322 // all Led-Wiz reports are 8 bytes exactly
mjr 6:cc35eb643e8f 1323 if (report.length == 8)
mjr 1:d913e0afb2ac 1324 {
mjr 6:cc35eb643e8f 1325 uint8_t *data = report.data;
mjr 6:cc35eb643e8f 1326 if (data[0] == 64)
mjr 0:5acbbe3f4cf4 1327 {
mjr 6:cc35eb643e8f 1328 // LWZ-SBA - first four bytes are bit-packed on/off flags
mjr 6:cc35eb643e8f 1329 // for the outputs; 5th byte is the pulse speed (0-7)
mjr 6:cc35eb643e8f 1330 //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n",
mjr 6:cc35eb643e8f 1331 // data[1], data[2], data[3], data[4], data[5]);
mjr 0:5acbbe3f4cf4 1332
mjr 6:cc35eb643e8f 1333 // update all on/off states
mjr 6:cc35eb643e8f 1334 for (int i = 0, bit = 1, ri = 1 ; i < 32 ; ++i, bit <<= 1)
mjr 6:cc35eb643e8f 1335 {
mjr 6:cc35eb643e8f 1336 if (bit == 0x100) {
mjr 6:cc35eb643e8f 1337 bit = 1;
mjr 6:cc35eb643e8f 1338 ++ri;
mjr 6:cc35eb643e8f 1339 }
mjr 6:cc35eb643e8f 1340 wizOn[i] = ((data[ri] & bit) != 0);
mjr 6:cc35eb643e8f 1341 }
mjr 6:cc35eb643e8f 1342
mjr 6:cc35eb643e8f 1343 // update the physical outputs
mjr 1:d913e0afb2ac 1344 updateWizOuts();
mjr 6:cc35eb643e8f 1345
mjr 6:cc35eb643e8f 1346 // reset the PBA counter
mjr 6:cc35eb643e8f 1347 pbaIdx = 0;
mjr 6:cc35eb643e8f 1348 }
mjr 6:cc35eb643e8f 1349 else if (data[0] == 65)
mjr 6:cc35eb643e8f 1350 {
mjr 6:cc35eb643e8f 1351 // Private control message. This isn't an LedWiz message - it's
mjr 6:cc35eb643e8f 1352 // an extension for this device. 65 is an invalid PBA setting,
mjr 6:cc35eb643e8f 1353 // and isn't used for any other LedWiz message, so we appropriate
mjr 6:cc35eb643e8f 1354 // it for our own private use. The first byte specifies the
mjr 6:cc35eb643e8f 1355 // message type.
mjr 6:cc35eb643e8f 1356 if (data[1] == 1)
mjr 6:cc35eb643e8f 1357 {
mjr 9:fd65b0a94720 1358 // 1 = Set Configuration:
mjr 6:cc35eb643e8f 1359 // data[2] = LedWiz unit number (0x00 to 0x0f)
mjr 6:cc35eb643e8f 1360 // data[3] = feature enable bit mask:
mjr 6:cc35eb643e8f 1361 // 0x01 = enable CCD
mjr 6:cc35eb643e8f 1362
mjr 6:cc35eb643e8f 1363 // we'll need a reset if the LedWiz unit number is changing
mjr 6:cc35eb643e8f 1364 uint8_t newUnitNo = data[2] & 0x0f;
mjr 6:cc35eb643e8f 1365 needReset |= (newUnitNo != cfg.d.ledWizUnitNo);
mjr 6:cc35eb643e8f 1366
mjr 6:cc35eb643e8f 1367 // set the configuration parameters from the message
mjr 6:cc35eb643e8f 1368 cfg.d.ledWizUnitNo = newUnitNo;
mjr 6:cc35eb643e8f 1369 cfg.d.ccdEnabled = data[3] & 0x01;
mjr 6:cc35eb643e8f 1370
mjr 9:fd65b0a94720 1371 // update the status flags
mjr 9:fd65b0a94720 1372 statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01);
mjr 9:fd65b0a94720 1373
mjr 9:fd65b0a94720 1374 // if the ccd is no longer enabled, use 0 for z reports
mjr 9:fd65b0a94720 1375 if (!cfg.d.ccdEnabled)
mjr 9:fd65b0a94720 1376 z = 0;
mjr 9:fd65b0a94720 1377
mjr 6:cc35eb643e8f 1378 // save the configuration
mjr 6:cc35eb643e8f 1379 cfg.save(iap, flash_addr);
mjr 6:cc35eb643e8f 1380 }
mjr 9:fd65b0a94720 1381 else if (data[1] == 2)
mjr 9:fd65b0a94720 1382 {
mjr 9:fd65b0a94720 1383 // 2 = Calibrate plunger
mjr 9:fd65b0a94720 1384 // (No parameters)
mjr 9:fd65b0a94720 1385
mjr 9:fd65b0a94720 1386 // enter calibration mode
mjr 9:fd65b0a94720 1387 calBtnState = 3;
mjr 9:fd65b0a94720 1388 calBtnTimer.reset();
mjr 9:fd65b0a94720 1389 cfg.resetPlunger();
mjr 9:fd65b0a94720 1390 }
mjr 10:976666ffa4ef 1391 else if (data[1] == 3)
mjr 10:976666ffa4ef 1392 {
mjr 10:976666ffa4ef 1393 // 3 = pixel dump
mjr 10:976666ffa4ef 1394 // (No parameters)
mjr 10:976666ffa4ef 1395 reportPix = true;
mjr 10:976666ffa4ef 1396
mjr 10:976666ffa4ef 1397 // show purple until we finish sending the report
mjr 10:976666ffa4ef 1398 ledR = 0;
mjr 10:976666ffa4ef 1399 ledB = 0;
mjr 10:976666ffa4ef 1400 ledG = 1;
mjr 10:976666ffa4ef 1401 }
mjr 6:cc35eb643e8f 1402 }
mjr 6:cc35eb643e8f 1403 else
mjr 6:cc35eb643e8f 1404 {
mjr 6:cc35eb643e8f 1405 // LWZ-PBA - full state dump; each byte is one output
mjr 6:cc35eb643e8f 1406 // in the current bank. pbaIdx keeps track of the bank;
mjr 6:cc35eb643e8f 1407 // this is incremented implicitly by each PBA message.
mjr 6:cc35eb643e8f 1408 //printf("LWZ-PBA[%d] %02x %02x %02x %02x %02x %02x %02x %02x\r\n",
mjr 6:cc35eb643e8f 1409 // pbaIdx, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
mjr 6:cc35eb643e8f 1410
mjr 6:cc35eb643e8f 1411 // update all output profile settings
mjr 6:cc35eb643e8f 1412 for (int i = 0 ; i < 8 ; ++i)
mjr 6:cc35eb643e8f 1413 wizVal[pbaIdx + i] = data[i];
mjr 6:cc35eb643e8f 1414
mjr 6:cc35eb643e8f 1415 // update the physical LED state if this is the last bank
mjr 6:cc35eb643e8f 1416 if (pbaIdx == 24)
mjr 13:72dda449c3c0 1417 {
mjr 6:cc35eb643e8f 1418 updateWizOuts();
mjr 13:72dda449c3c0 1419 pbaIdx = 0;
mjr 13:72dda449c3c0 1420 }
mjr 13:72dda449c3c0 1421 else
mjr 13:72dda449c3c0 1422 pbaIdx += 8;
mjr 6:cc35eb643e8f 1423 }
mjr 0:5acbbe3f4cf4 1424 }
mjr 0:5acbbe3f4cf4 1425 }
mjr 1:d913e0afb2ac 1426
mjr 1:d913e0afb2ac 1427 // check for plunger calibration
mjr 1:d913e0afb2ac 1428 if (!calBtn)
mjr 0:5acbbe3f4cf4 1429 {
mjr 1:d913e0afb2ac 1430 // check the state
mjr 1:d913e0afb2ac 1431 switch (calBtnState)
mjr 0:5acbbe3f4cf4 1432 {
mjr 1:d913e0afb2ac 1433 case 0:
mjr 1:d913e0afb2ac 1434 // button not yet pushed - start debouncing
mjr 1:d913e0afb2ac 1435 calBtnTimer.reset();
mjr 1:d913e0afb2ac 1436 calBtnState = 1;
mjr 1:d913e0afb2ac 1437 break;
mjr 1:d913e0afb2ac 1438
mjr 1:d913e0afb2ac 1439 case 1:
mjr 1:d913e0afb2ac 1440 // pushed, not yet debounced - if the debounce time has
mjr 1:d913e0afb2ac 1441 // passed, start the hold period
mjr 9:fd65b0a94720 1442 if (calBtnTimer.read_ms() > 50)
mjr 1:d913e0afb2ac 1443 calBtnState = 2;
mjr 1:d913e0afb2ac 1444 break;
mjr 1:d913e0afb2ac 1445
mjr 1:d913e0afb2ac 1446 case 2:
mjr 1:d913e0afb2ac 1447 // in the hold period - if the button has been held down
mjr 1:d913e0afb2ac 1448 // for the entire hold period, move to calibration mode
mjr 9:fd65b0a94720 1449 if (calBtnTimer.read_ms() > 2050)
mjr 1:d913e0afb2ac 1450 {
mjr 1:d913e0afb2ac 1451 // enter calibration mode
mjr 1:d913e0afb2ac 1452 calBtnState = 3;
mjr 9:fd65b0a94720 1453 calBtnTimer.reset();
mjr 9:fd65b0a94720 1454 cfg.resetPlunger();
mjr 1:d913e0afb2ac 1455 }
mjr 1:d913e0afb2ac 1456 break;
mjr 2:c174f9ee414a 1457
mjr 2:c174f9ee414a 1458 case 3:
mjr 9:fd65b0a94720 1459 // Already in calibration mode - pushing the button here
mjr 9:fd65b0a94720 1460 // doesn't change the current state, but we won't leave this
mjr 9:fd65b0a94720 1461 // state as long as it's held down. So nothing changes here.
mjr 2:c174f9ee414a 1462 break;
mjr 0:5acbbe3f4cf4 1463 }
mjr 0:5acbbe3f4cf4 1464 }
mjr 1:d913e0afb2ac 1465 else
mjr 1:d913e0afb2ac 1466 {
mjr 2:c174f9ee414a 1467 // Button released. If we're in calibration mode, and
mjr 2:c174f9ee414a 1468 // the calibration time has elapsed, end the calibration
mjr 2:c174f9ee414a 1469 // and save the results to flash.
mjr 2:c174f9ee414a 1470 //
mjr 2:c174f9ee414a 1471 // Otherwise, return to the base state without saving anything.
mjr 2:c174f9ee414a 1472 // If the button is released before we make it to calibration
mjr 2:c174f9ee414a 1473 // mode, it simply cancels the attempt.
mjr 9:fd65b0a94720 1474 if (calBtnState == 3 && calBtnTimer.read_ms() > 15000)
mjr 2:c174f9ee414a 1475 {
mjr 2:c174f9ee414a 1476 // exit calibration mode
mjr 1:d913e0afb2ac 1477 calBtnState = 0;
mjr 2:c174f9ee414a 1478
mjr 6:cc35eb643e8f 1479 // save the updated configuration
mjr 6:cc35eb643e8f 1480 cfg.d.plungerCal = 1;
mjr 6:cc35eb643e8f 1481 cfg.save(iap, flash_addr);
mjr 2:c174f9ee414a 1482
mjr 2:c174f9ee414a 1483 // the flash state is now valid
mjr 2:c174f9ee414a 1484 flash_valid = true;
mjr 2:c174f9ee414a 1485 }
mjr 2:c174f9ee414a 1486 else if (calBtnState != 3)
mjr 2:c174f9ee414a 1487 {
mjr 2:c174f9ee414a 1488 // didn't make it to calibration mode - cancel the operation
mjr 1:d913e0afb2ac 1489 calBtnState = 0;
mjr 2:c174f9ee414a 1490 }
mjr 1:d913e0afb2ac 1491 }
mjr 1:d913e0afb2ac 1492
mjr 1:d913e0afb2ac 1493 // light/flash the calibration button light, if applicable
mjr 1:d913e0afb2ac 1494 int newCalBtnLit = calBtnLit;
mjr 1:d913e0afb2ac 1495 switch (calBtnState)
mjr 0:5acbbe3f4cf4 1496 {
mjr 1:d913e0afb2ac 1497 case 2:
mjr 1:d913e0afb2ac 1498 // in the hold period - flash the light
mjr 9:fd65b0a94720 1499 newCalBtnLit = ((calBtnTimer.read_ms()/250) & 1);
mjr 1:d913e0afb2ac 1500 break;
mjr 1:d913e0afb2ac 1501
mjr 1:d913e0afb2ac 1502 case 3:
mjr 1:d913e0afb2ac 1503 // calibration mode - show steady on
mjr 1:d913e0afb2ac 1504 newCalBtnLit = true;
mjr 1:d913e0afb2ac 1505 break;
mjr 1:d913e0afb2ac 1506
mjr 1:d913e0afb2ac 1507 default:
mjr 1:d913e0afb2ac 1508 // not calibrating/holding - show steady off
mjr 1:d913e0afb2ac 1509 newCalBtnLit = false;
mjr 1:d913e0afb2ac 1510 break;
mjr 1:d913e0afb2ac 1511 }
mjr 3:3514575d4f86 1512
mjr 3:3514575d4f86 1513 // light or flash the external calibration button LED, and
mjr 3:3514575d4f86 1514 // do the same with the on-board blue LED
mjr 1:d913e0afb2ac 1515 if (calBtnLit != newCalBtnLit)
mjr 1:d913e0afb2ac 1516 {
mjr 1:d913e0afb2ac 1517 calBtnLit = newCalBtnLit;
mjr 2:c174f9ee414a 1518 if (calBtnLit) {
mjr 2:c174f9ee414a 1519 calBtnLed = 1;
mjr 4:02c7cd7b2183 1520 ledR = 1;
mjr 4:02c7cd7b2183 1521 ledG = 1;
mjr 9:fd65b0a94720 1522 ledB = 0;
mjr 2:c174f9ee414a 1523 }
mjr 2:c174f9ee414a 1524 else {
mjr 2:c174f9ee414a 1525 calBtnLed = 0;
mjr 4:02c7cd7b2183 1526 ledR = 1;
mjr 4:02c7cd7b2183 1527 ledG = 1;
mjr 9:fd65b0a94720 1528 ledB = 1;
mjr 2:c174f9ee414a 1529 }
mjr 1:d913e0afb2ac 1530 }
mjr 1:d913e0afb2ac 1531
mjr 6:cc35eb643e8f 1532 // read the plunger sensor, if it's enabled
mjr 10:976666ffa4ef 1533 uint16_t pix[npix];
mjr 6:cc35eb643e8f 1534 if (cfg.d.ccdEnabled)
mjr 6:cc35eb643e8f 1535 {
mjr 6:cc35eb643e8f 1536 // start with the previous reading, in case we don't have a
mjr 6:cc35eb643e8f 1537 // clear result on this frame
mjr 6:cc35eb643e8f 1538 int znew = z;
mjr 2:c174f9ee414a 1539
mjr 6:cc35eb643e8f 1540 // read the array
mjr 14:df700b22ca08 1541 ccd.read(pix, npix, ccdReadCB, 0, 3);
mjr 6:cc35eb643e8f 1542
mjr 6:cc35eb643e8f 1543 // get the average brightness at each end of the sensor
mjr 6:cc35eb643e8f 1544 long avg1 = (long(pix[0]) + long(pix[1]) + long(pix[2]) + long(pix[3]) + long(pix[4]))/5;
mjr 6:cc35eb643e8f 1545 long avg2 = (long(pix[npix-1]) + long(pix[npix-2]) + long(pix[npix-3]) + long(pix[npix-4]) + long(pix[npix-5]))/5;
mjr 6:cc35eb643e8f 1546
mjr 6:cc35eb643e8f 1547 // figure the midpoint in the brightness; multiply by 3 so that we can
mjr 6:cc35eb643e8f 1548 // compare sums of three pixels at a time to smooth out noise
mjr 6:cc35eb643e8f 1549 long midpt = (avg1 + avg2)/2 * 3;
mjr 6:cc35eb643e8f 1550
mjr 6:cc35eb643e8f 1551 // Work from the bright end to the dark end. VP interprets the
mjr 6:cc35eb643e8f 1552 // Z axis value as the amount the plunger is pulled: zero is the
mjr 6:cc35eb643e8f 1553 // rest position, and the axis maximum is fully pulled. So we
mjr 6:cc35eb643e8f 1554 // essentially want to report how much of the sensor is lit,
mjr 6:cc35eb643e8f 1555 // since this increases as the plunger is pulled back.
mjr 6:cc35eb643e8f 1556 int si = 1, di = 1;
mjr 6:cc35eb643e8f 1557 if (avg1 < avg2)
mjr 6:cc35eb643e8f 1558 si = npix - 2, di = -1;
mjr 6:cc35eb643e8f 1559
mjr 6:cc35eb643e8f 1560 // If the bright end and dark end don't differ by enough, skip this
mjr 6:cc35eb643e8f 1561 // reading entirely - we must have an overexposed or underexposed frame.
mjr 6:cc35eb643e8f 1562 // Otherwise proceed with the scan.
mjr 6:cc35eb643e8f 1563 if (labs(avg1 - avg2) > 0x1000)
mjr 6:cc35eb643e8f 1564 {
mjr 6:cc35eb643e8f 1565 uint16_t *pixp = pix + si;
mjr 6:cc35eb643e8f 1566 for (int n = 1 ; n < npix - 1 ; ++n, pixp += di)
mjr 6:cc35eb643e8f 1567 {
mjr 6:cc35eb643e8f 1568 // if we've crossed the midpoint, report this position
mjr 6:cc35eb643e8f 1569 if (long(pixp[-1]) + long(pixp[0]) + long(pixp[1]) < midpt)
mjr 6:cc35eb643e8f 1570 {
mjr 6:cc35eb643e8f 1571 // note the new position
mjr 6:cc35eb643e8f 1572 int pos = n;
mjr 6:cc35eb643e8f 1573
mjr 6:cc35eb643e8f 1574 // Calibrate, or apply calibration, depending on the mode.
mjr 6:cc35eb643e8f 1575 // In either case, normalize to our range. VP appears to
mjr 6:cc35eb643e8f 1576 // ignore negative Z axis values.
mjr 6:cc35eb643e8f 1577 if (calBtnState == 3)
mjr 6:cc35eb643e8f 1578 {
mjr 6:cc35eb643e8f 1579 // calibrating - note if we're expanding the calibration envelope
mjr 6:cc35eb643e8f 1580 if (pos < cfg.d.plungerMin)
mjr 6:cc35eb643e8f 1581 cfg.d.plungerMin = pos;
mjr 6:cc35eb643e8f 1582 if (pos < cfg.d.plungerZero)
mjr 6:cc35eb643e8f 1583 cfg.d.plungerZero = pos;
mjr 6:cc35eb643e8f 1584 if (pos > cfg.d.plungerMax)
mjr 6:cc35eb643e8f 1585 cfg.d.plungerMax = pos;
mjr 6:cc35eb643e8f 1586
mjr 6:cc35eb643e8f 1587 // normalize to the full physical range while calibrating
mjr 6:cc35eb643e8f 1588 znew = int(round(float(pos)/npix * JOYMAX));
mjr 6:cc35eb643e8f 1589 }
mjr 6:cc35eb643e8f 1590 else
mjr 6:cc35eb643e8f 1591 {
mjr 6:cc35eb643e8f 1592 // Running normally - normalize to the calibration range. Note
mjr 6:cc35eb643e8f 1593 // that values below the zero point are allowed - the zero point
mjr 6:cc35eb643e8f 1594 // represents the park position, where the plunger sits when at
mjr 6:cc35eb643e8f 1595 // rest, but a mechanical plunger has a smmall amount of travel
mjr 6:cc35eb643e8f 1596 // in the "push" direction. We represent forward travel with
mjr 6:cc35eb643e8f 1597 // negative z values.
mjr 6:cc35eb643e8f 1598 if (pos > cfg.d.plungerMax)
mjr 6:cc35eb643e8f 1599 pos = cfg.d.plungerMax;
mjr 6:cc35eb643e8f 1600 znew = int(round(float(pos - cfg.d.plungerZero)
mjr 6:cc35eb643e8f 1601 / (cfg.d.plungerMax - cfg.d.plungerZero + 1) * JOYMAX));
mjr 6:cc35eb643e8f 1602 }
mjr 6:cc35eb643e8f 1603
mjr 6:cc35eb643e8f 1604 // done
mjr 6:cc35eb643e8f 1605 break;
mjr 6:cc35eb643e8f 1606 }
mjr 6:cc35eb643e8f 1607 }
mjr 6:cc35eb643e8f 1608 }
mjr 7:100a25f8bf56 1609
mjr 7:100a25f8bf56 1610 // Determine if the plunger is being fired - i.e., if the player
mjr 7:100a25f8bf56 1611 // has just released the plunger from a retracted position.
mjr 6:cc35eb643e8f 1612 //
mjr 7:100a25f8bf56 1613 // We treat firing as an event. That is, we tell VP when the
mjr 7:100a25f8bf56 1614 // plunger is fired, and then stop sending data until the firing
mjr 7:100a25f8bf56 1615 // is complete, allowing VP to carry out the firing motion using
mjr 7:100a25f8bf56 1616 // its internal model plunger rather than trying to track the
mjr 7:100a25f8bf56 1617 // intermediate positions of the mechanical plunger throughout
mjr 9:fd65b0a94720 1618 // the firing motion. This is essential because the firing
mjr 9:fd65b0a94720 1619 // motion is too fast for us to track - in the time it takes us
mjr 9:fd65b0a94720 1620 // to read one frame, the plunger can make it all the way to the
mjr 9:fd65b0a94720 1621 // zero position and bounce back halfway. Fortunately, the range
mjr 9:fd65b0a94720 1622 // of motions for the plunger is limited, so if we see any rapid
mjr 9:fd65b0a94720 1623 // change of position toward the rest position, it's reasonably
mjr 9:fd65b0a94720 1624 // safe to interpret it as a firing event.
mjr 9:fd65b0a94720 1625 //
mjr 9:fd65b0a94720 1626 // This isn't foolproof. The user can trick us by doing a
mjr 9:fd65b0a94720 1627 // controlled rapid forward push but stopping short of the rest
mjr 9:fd65b0a94720 1628 // position. We'll misinterpret that as a firing event. But
mjr 9:fd65b0a94720 1629 // that's not a natural motion that a user would make with a
mjr 9:fd65b0a94720 1630 // plunger, so it's probably an acceptable false positive.
mjr 9:fd65b0a94720 1631 //
mjr 9:fd65b0a94720 1632 // Possible future enhancement: we could add a second physical
mjr 9:fd65b0a94720 1633 // sensor that detects when the plunger reaches the zero position
mjr 9:fd65b0a94720 1634 // and asserts an interrupt. In the interrupt handler, set a
mjr 9:fd65b0a94720 1635 // flag indicating the zero position signal. On each scan of
mjr 9:fd65b0a94720 1636 // the CCD, also check that flag; if it's set, enter firing
mjr 9:fd65b0a94720 1637 // event mode just as we do now. The key here is that the
mjr 9:fd65b0a94720 1638 // secondary sensor would have to be something much faster
mjr 9:fd65b0a94720 1639 // than our CCD scan - it would have to react on, say, the
mjr 9:fd65b0a94720 1640 // millisecond time scale. A simple mechanical switch or a
mjr 9:fd65b0a94720 1641 // proximity sensor could work. This would let us detect
mjr 9:fd65b0a94720 1642 // with certainty when the plunger physically fires, eliminating
mjr 9:fd65b0a94720 1643 // the case where the use can fool us with motion that's fast
mjr 9:fd65b0a94720 1644 // enough to look like a release but doesn't actually reach the
mjr 9:fd65b0a94720 1645 // starting position.
mjr 6:cc35eb643e8f 1646 //
mjr 7:100a25f8bf56 1647 // To detremine when a firing even occurs, we watch for rapid
mjr 7:100a25f8bf56 1648 // motion from a retracted position towards the rest position -
mjr 7:100a25f8bf56 1649 // that is, large position changes in the negative direction over
mjr 7:100a25f8bf56 1650 // a couple of consecutive readings. When we see a rapid move
mjr 7:100a25f8bf56 1651 // toward zero, we set our internal 'firing' flag, immediately
mjr 7:100a25f8bf56 1652 // report to VP that the plunger has returned to the zero
mjr 7:100a25f8bf56 1653 // position, and then suspend reports until the mechanical
mjr 7:100a25f8bf56 1654 // readings indicate that the plunger has come to rest (indicated
mjr 7:100a25f8bf56 1655 // by several readings in a row at roughly the same position).
mjr 9:fd65b0a94720 1656 //
mjr 9:fd65b0a94720 1657 // Tolerance for firing is 1/3 of the current pull distance, or
mjr 9:fd65b0a94720 1658 // about 1/2", whichever is greater. Making this value too small
mjr 9:fd65b0a94720 1659 // makes for too many false positives. Empirically, 1/4" is too
mjr 9:fd65b0a94720 1660 // twitchy, so set a floor at about 1/2". But we can be less
mjr 9:fd65b0a94720 1661 // sensitive the further back the plunger is pulled, since even
mjr 9:fd65b0a94720 1662 // a long pull will snap back quickly. Note that JOYMAX always
mjr 9:fd65b0a94720 1663 // corresponds to about 3", no matter how many pixels we're
mjr 9:fd65b0a94720 1664 // reading, since the physical sensor is about 3" long; so we
mjr 9:fd65b0a94720 1665 // factor out the pixel count calculate (approximate) physical
mjr 9:fd65b0a94720 1666 // distances based on the normalized axis range.
mjr 9:fd65b0a94720 1667 //
mjr 9:fd65b0a94720 1668 // Firing pattern: when firing, don't simply report a solid 0,
mjr 9:fd65b0a94720 1669 // but instead report a series of pseudo-bouces. This looks
mjr 9:fd65b0a94720 1670 // more realistic, beacause the real plunger is also bouncing
mjr 9:fd65b0a94720 1671 // around during this time. To get maximum firing power in
mjr 9:fd65b0a94720 1672 // the simulation, though, our pseudo-bounces are tiny cmopared
mjr 9:fd65b0a94720 1673 // to the real thing.
mjr 9:fd65b0a94720 1674 const int restTol = JOYMAX/24;
mjr 9:fd65b0a94720 1675 int fireTol = z/3 > JOYMAX/6 ? z/3 : JOYMAX/6;
mjr 9:fd65b0a94720 1676 static const int firePattern[] = {
mjr 9:fd65b0a94720 1677 -JOYMAX/12, -JOYMAX/12, -JOYMAX/12,
mjr 9:fd65b0a94720 1678 };
mjr 9:fd65b0a94720 1679 if (firing != 0)
mjr 6:cc35eb643e8f 1680 {
mjr 6:cc35eb643e8f 1681 // Firing in progress - we've already told VP to send its
mjr 6:cc35eb643e8f 1682 // model plunger all the way back to the rest position, so
mjr 6:cc35eb643e8f 1683 // send no further reports until the mechanical plunger
mjr 6:cc35eb643e8f 1684 // actually comes to rest somewhere.
mjr 6:cc35eb643e8f 1685 if (abs(z0 - z2) < restTol && abs(znew - z2) < restTol)
mjr 6:cc35eb643e8f 1686 {
mjr 6:cc35eb643e8f 1687 // the plunger is back at rest - firing is done
mjr 9:fd65b0a94720 1688 firing = 0;
mjr 6:cc35eb643e8f 1689
mjr 6:cc35eb643e8f 1690 // resume normal reporting
mjr 6:cc35eb643e8f 1691 z = z2;
mjr 6:cc35eb643e8f 1692 }
mjr 9:fd65b0a94720 1693 else if (firing < countof(firePattern))
mjr 9:fd65b0a94720 1694 {
mjr 9:fd65b0a94720 1695 // firing - report the next position in the pseudo-bounce
mjr 9:fd65b0a94720 1696 // pattern
mjr 9:fd65b0a94720 1697 z = firePattern[firing++];
mjr 9:fd65b0a94720 1698 }
mjr 9:fd65b0a94720 1699 else
mjr 9:fd65b0a94720 1700 {
mjr 9:fd65b0a94720 1701 // firing, out of pseudo-bounce items - just report the
mjr 9:fd65b0a94720 1702 // rest position
mjr 9:fd65b0a94720 1703 z = 0;
mjr 9:fd65b0a94720 1704 }
mjr 6:cc35eb643e8f 1705 }
mjr 6:cc35eb643e8f 1706 else if (z0 < z2 && z1 < z2 && znew < z2
mjr 6:cc35eb643e8f 1707 && (z0 < z2 - fireTol
mjr 6:cc35eb643e8f 1708 || z1 < z2 - fireTol
mjr 6:cc35eb643e8f 1709 || znew < z2 - fireTol))
mjr 6:cc35eb643e8f 1710 {
mjr 6:cc35eb643e8f 1711 // Big jumps toward rest position in last two readings -
mjr 6:cc35eb643e8f 1712 // firing has begun. Report an immediate return to the
mjr 6:cc35eb643e8f 1713 // rest position, and send no further reports until the
mjr 6:cc35eb643e8f 1714 // physical plunger has come to rest. This effectively
mjr 6:cc35eb643e8f 1715 // detaches VP's model plunger from the real world for
mjr 6:cc35eb643e8f 1716 // the duration of the spring return, letting VP evolve
mjr 6:cc35eb643e8f 1717 // its model without trying to synchronize with the
mjr 6:cc35eb643e8f 1718 // mechanical version. The release motion is too fast
mjr 6:cc35eb643e8f 1719 // for that to work well; we can't take samples quickly
mjr 6:cc35eb643e8f 1720 // enough to get prcise velocity or acceleration
mjr 6:cc35eb643e8f 1721 // readings. It's better to let VP figure the speed
mjr 6:cc35eb643e8f 1722 // and acceleration through modeling. Plus, that lets
mjr 6:cc35eb643e8f 1723 // each virtual table set the desired parameters for its
mjr 6:cc35eb643e8f 1724 // virtual plunger, rather than imposing the actual
mjr 6:cc35eb643e8f 1725 // mechanical charateristics of the physical plunger on
mjr 6:cc35eb643e8f 1726 // every table.
mjr 9:fd65b0a94720 1727 firing = 1;
mjr 9:fd65b0a94720 1728
mjr 9:fd65b0a94720 1729 // report the first firing pattern position
mjr 9:fd65b0a94720 1730 z = firePattern[0];
mjr 6:cc35eb643e8f 1731 }
mjr 6:cc35eb643e8f 1732 else
mjr 6:cc35eb643e8f 1733 {
mjr 6:cc35eb643e8f 1734 // everything normal; report the 3rd recent position on
mjr 6:cc35eb643e8f 1735 // tape delay
mjr 6:cc35eb643e8f 1736 z = z2;
mjr 6:cc35eb643e8f 1737 }
mjr 6:cc35eb643e8f 1738
mjr 6:cc35eb643e8f 1739 // shift in the new reading
mjr 6:cc35eb643e8f 1740 z2 = z1;
mjr 6:cc35eb643e8f 1741 z1 = z0;
mjr 6:cc35eb643e8f 1742 z0 = znew;
mjr 2:c174f9ee414a 1743 }
mjr 9:fd65b0a94720 1744 else
mjr 9:fd65b0a94720 1745 {
mjr 9:fd65b0a94720 1746 // plunger disabled - pause 10ms to throttle updates to a
mjr 9:fd65b0a94720 1747 // reasonable pace
mjr 9:fd65b0a94720 1748 wait_ms(10);
mjr 9:fd65b0a94720 1749 }
mjr 6:cc35eb643e8f 1750
mjr 1:d913e0afb2ac 1751 // read the accelerometer
mjr 9:fd65b0a94720 1752 int xa, ya;
mjr 9:fd65b0a94720 1753 accel.get(xa, ya);
mjr 1:d913e0afb2ac 1754
mjr 6:cc35eb643e8f 1755 // confine the results to our joystick axis range
mjr 6:cc35eb643e8f 1756 if (xa < -JOYMAX) xa = -JOYMAX;
mjr 6:cc35eb643e8f 1757 if (xa > JOYMAX) xa = JOYMAX;
mjr 6:cc35eb643e8f 1758 if (ya < -JOYMAX) ya = -JOYMAX;
mjr 6:cc35eb643e8f 1759 if (ya > JOYMAX) ya = JOYMAX;
mjr 1:d913e0afb2ac 1760
mjr 6:cc35eb643e8f 1761 // store the updated accelerometer coordinates
mjr 6:cc35eb643e8f 1762 x = xa;
mjr 6:cc35eb643e8f 1763 y = ya;
mjr 6:cc35eb643e8f 1764
mjr 11:bd9da7088e6e 1765 // update the buttons
mjr 11:bd9da7088e6e 1766 uint32_t buttons = readButtonsDebounced();
mjr 11:bd9da7088e6e 1767
mjr 8:c732e279ee29 1768 // Send the status report. Note that the nominal x and y axes
mjr 8:c732e279ee29 1769 // are reversed - this makes it more intuitive to set up in VP.
mjr 8:c732e279ee29 1770 // If we mount the Freesale card flat on the floor of the cabinet
mjr 8:c732e279ee29 1771 // with the USB connectors facing the front of the cabinet, this
mjr 8:c732e279ee29 1772 // arrangement of our nominal axes aligns with VP's standard
mjr 8:c732e279ee29 1773 // setting, so that we can configure VP with X Axis = X on the
mjr 8:c732e279ee29 1774 // joystick and Y Axis = Y on the joystick.
mjr 11:bd9da7088e6e 1775 js.update(y, x, z, buttons, statusFlags);
mjr 1:d913e0afb2ac 1776
mjr 10:976666ffa4ef 1777 // If we're in pixel dump mode, report all pixel exposure values
mjr 10:976666ffa4ef 1778 if (reportPix)
mjr 10:976666ffa4ef 1779 {
mjr 10:976666ffa4ef 1780 // we have satisfied this request
mjr 10:976666ffa4ef 1781 reportPix = false;
mjr 10:976666ffa4ef 1782
mjr 10:976666ffa4ef 1783 // send reports for all pixels
mjr 10:976666ffa4ef 1784 int idx = 0;
mjr 10:976666ffa4ef 1785 while (idx < npix)
mjr 10:976666ffa4ef 1786 js.updateExposure(idx, npix, pix);
mjr 10:976666ffa4ef 1787
mjr 10:976666ffa4ef 1788 // The pixel dump requires many USB reports, since each report
mjr 10:976666ffa4ef 1789 // can only send a few pixel values. An integration cycle has
mjr 10:976666ffa4ef 1790 // been running all this time, since each read starts a new
mjr 10:976666ffa4ef 1791 // cycle. Our timing is longer than usual on this round, so
mjr 10:976666ffa4ef 1792 // the integration won't be comparable to a normal cycle. Throw
mjr 10:976666ffa4ef 1793 // this one away by doing a read now, and throwing it away - that
mjr 10:976666ffa4ef 1794 // will get the timing of the *next* cycle roughly back to normal.
mjr 10:976666ffa4ef 1795 ccd.read(pix, npix);
mjr 10:976666ffa4ef 1796 }
mjr 10:976666ffa4ef 1797
mjr 6:cc35eb643e8f 1798 #ifdef DEBUG_PRINTF
mjr 6:cc35eb643e8f 1799 if (x != 0 || y != 0)
mjr 6:cc35eb643e8f 1800 printf("%d,%d\r\n", x, y);
mjr 6:cc35eb643e8f 1801 #endif
mjr 6:cc35eb643e8f 1802
mjr 6:cc35eb643e8f 1803 // provide a visual status indication on the on-board LED
mjr 5:a70c0bce770d 1804 if (calBtnState < 2 && hbTimer.read_ms() > 1000)
mjr 1:d913e0afb2ac 1805 {
mjr 5:a70c0bce770d 1806 if (js.isSuspended() || !js.isConnected())
mjr 2:c174f9ee414a 1807 {
mjr 5:a70c0bce770d 1808 // suspended - turn off the LED
mjr 4:02c7cd7b2183 1809 ledR = 1;
mjr 4:02c7cd7b2183 1810 ledG = 1;
mjr 4:02c7cd7b2183 1811 ledB = 1;
mjr 5:a70c0bce770d 1812
mjr 5:a70c0bce770d 1813 // show a status flash every so often
mjr 5:a70c0bce770d 1814 if (hbcnt % 3 == 0)
mjr 5:a70c0bce770d 1815 {
mjr 6:cc35eb643e8f 1816 // disconnected = red/red flash; suspended = red
mjr 5:a70c0bce770d 1817 for (int n = js.isConnected() ? 1 : 2 ; n > 0 ; --n)
mjr 5:a70c0bce770d 1818 {
mjr 5:a70c0bce770d 1819 ledR = 0;
mjr 5:a70c0bce770d 1820 wait(0.05);
mjr 5:a70c0bce770d 1821 ledR = 1;
mjr 5:a70c0bce770d 1822 wait(0.25);
mjr 5:a70c0bce770d 1823 }
mjr 5:a70c0bce770d 1824 }
mjr 2:c174f9ee414a 1825 }
mjr 6:cc35eb643e8f 1826 else if (needReset)
mjr 2:c174f9ee414a 1827 {
mjr 6:cc35eb643e8f 1828 // connected, need to reset due to changes in config parameters -
mjr 6:cc35eb643e8f 1829 // flash red/green
mjr 6:cc35eb643e8f 1830 hb = !hb;
mjr 6:cc35eb643e8f 1831 ledR = (hb ? 0 : 1);
mjr 6:cc35eb643e8f 1832 ledG = (hb ? 1 : 0);
mjr 6:cc35eb643e8f 1833 ledB = 0;
mjr 6:cc35eb643e8f 1834 }
mjr 6:cc35eb643e8f 1835 else if (cfg.d.ccdEnabled && !cfg.d.plungerCal)
mjr 6:cc35eb643e8f 1836 {
mjr 6:cc35eb643e8f 1837 // connected, plunger calibration needed - flash yellow/green
mjr 6:cc35eb643e8f 1838 hb = !hb;
mjr 6:cc35eb643e8f 1839 ledR = (hb ? 0 : 1);
mjr 6:cc35eb643e8f 1840 ledG = 0;
mjr 6:cc35eb643e8f 1841 ledB = 1;
mjr 6:cc35eb643e8f 1842 }
mjr 6:cc35eb643e8f 1843 else
mjr 6:cc35eb643e8f 1844 {
mjr 6:cc35eb643e8f 1845 // connected - flash blue/green
mjr 2:c174f9ee414a 1846 hb = !hb;
mjr 4:02c7cd7b2183 1847 ledR = 1;
mjr 4:02c7cd7b2183 1848 ledG = (hb ? 0 : 1);
mjr 4:02c7cd7b2183 1849 ledB = (hb ? 1 : 0);
mjr 2:c174f9ee414a 1850 }
mjr 1:d913e0afb2ac 1851
mjr 1:d913e0afb2ac 1852 // reset the heartbeat timer
mjr 1:d913e0afb2ac 1853 hbTimer.reset();
mjr 5:a70c0bce770d 1854 ++hbcnt;
mjr 1:d913e0afb2ac 1855 }
mjr 1:d913e0afb2ac 1856 }
mjr 0:5acbbe3f4cf4 1857 }