Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

Committer:
mjr
Date:
Thu Dec 24 01:37:40 2015 +0000
Revision:
37:ed52738445fc
Parent:
36:b9747461331e
Child:
38:091e511ce8a0
Bug fixes to USB HAL

Who changed what in which revision?

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