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 Feb 15 23:03:55 2016 +0000
Revision:
68:edfecf67a931
Parent:
34:6b981a2afab7
Child:
35:e959ffba78fd
Finalize USB library updates to fix compatibility problems introduced in USBHAL_KL25Z overhaul.

Who changed what in which revision?

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