An input/output controller for virtual pinball machines, with plunger position tracking, accelerometer-based nudge sensing, button input encoding, and feedback device control.
Dependencies: USBDevice mbed FastAnalogIn FastIO FastPWM SimpleDMA
main.cpp
00001 /* Copyright 2014 M J Roberts, MIT License 00002 * 00003 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 00004 * and associated documentation files (the "Software"), to deal in the Software without 00005 * restriction, including without limitation the rights to use, copy, modify, merge, publish, 00006 * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 00007 * Software is furnished to do so, subject to the following conditions: 00008 * 00009 * The above copyright notice and this permission notice shall be included in all copies or 00010 * substantial portions of the Software. 00011 * 00012 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 00013 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 00014 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 00015 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 00016 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 00017 */ 00018 00019 // 00020 // Pinscape Controller 00021 // 00022 // "Pinscape" is the name of my custom-built virtual pinball cabinet, so I call this 00023 // software the Pinscape Controller. I wrote it to handle several tasks that I needed 00024 // for my cabinet. It runs on a Freescale KL25Z microcontroller, which is a small and 00025 // inexpensive device that attaches to the cabinet PC via a USB cable, and can attach 00026 // via custom wiring to sensors, buttons, and other devices in the cabinet. 00027 // 00028 // I designed the software and hardware in this project especially for my own 00029 // cabinet, but it uses standard interfaces in Windows and Visual Pinball, so it should 00030 // work in any VP-based cabinet, as long as you're using the usual VP software suite. 00031 // I've tried to document the hardware in enough detail for anyone else to duplicate 00032 // the entire project, and the full software is open source. 00033 // 00034 // The Freescale board appears to the host PC as a standard USB joystick. This works 00035 // with the built-in Windows joystick device drivers, so there's no need to install any 00036 // new drivers or other software on the PC. Windows should recognize the Freescale 00037 // as a joystick when you plug it into the USB port, and Windows shouldn't ask you to 00038 // install any drivers. If you bring up the Windows control panel for USB Game 00039 // Controllers, this device will appear as "Pinscape Controller". *Don't* do any 00040 // calibration with the Windows control panel or third-part calibration tools. The 00041 // software calibrates the accelerometer portion automatically, and has its own special 00042 // calibration procedure for the plunger sensor, if you're using that (see below). 00043 // 00044 // This software provides a whole bunch of separate features. You can use any of these 00045 // features individually or all together. If you're not using a particular feature, you 00046 // can simply omit the extra wiring and/or hardware for that feature. You can use 00047 // the nudging feature by itself without any extra hardware attached, since the 00048 // accelerometer is built in to the KL25Z board. 00049 // 00050 // - Nudge sensing via the KL25Z's on-board accelerometer. Nudging the cabinet 00051 // causes small accelerations that the accelerometer can detect; these are sent to 00052 // Visual Pinball via the joystick interface so that VP can simulate the effect 00053 // of the real physical nudges on its simulated ball. VP has native handling for 00054 // this type of input, so all you have to do is set some preferences in VP to tell 00055 // it that an accelerometer is attached. 00056 // 00057 // - Plunger position sensing via an attached TAOS TSL 1410R CCD linear array sensor. 00058 // To use this feature, you need to buy the TAOS device (it's not built in to the 00059 // KL25Z, obviously), wire it to the KL25Z (5 wire connections between the two 00060 // devices are required), and mount the TAOS sensor in your cabinet so that it's 00061 // positioned properly to capture images of the physical plunger shooter rod. 00062 // 00063 // The physical mounting and wiring details are desribed in the project 00064 // documentation. 00065 // 00066 // If the CCD is attached, the software constantly captures images from the CCD 00067 // and analyzes them to determine how far back the plunger is pulled. It reports 00068 // this to Visual Pinball via the joystick interface. This allows VP to make the 00069 // simulated on-screen plunger track the motion of the physical plunger in real 00070 // time. As with the nudge data, VP has native handling for the plunger input, 00071 // so you just need to set the VP preferences to tell it that an analog plunger 00072 // device is attached. One caveat, though: although VP itself has built-in 00073 // support for an analog plunger, not all existing tables take advantage of it. 00074 // Many existing tables have their own custom plunger scripting that doesn't 00075 // cooperate with the VP plunger input. All tables *can* be made to work with 00076 // the plunger, and in most cases it only requires some simple script editing, 00077 // but in some cases it requires some more extensive surgery. 00078 // 00079 // For best results, the plunger sensor should be calibrated. The calibration 00080 // is stored in non-volatile memory on board the KL25Z, so it's only necessary 00081 // to do the calibration once, when you first install everything. (You might 00082 // also want to re-calibrate if you physically remove and reinstall the CCD 00083 // sensor or the mechanical plunger, since their alignment shift change slightly 00084 // when you put everything back together.) You can optionally install a 00085 // dedicated momentary switch or pushbutton to activate the calibration mode; 00086 // this is describe in the project documentation. If you don't want to bother 00087 // with the extra button, you can also trigger calibration using the Windows 00088 // setup software, which you can find on the Pinscape project page. 00089 // 00090 // The calibration procedure is described in the project documentation. Briefly, 00091 // when you trigger calibration mode, the software will scan the CCD for about 00092 // 15 seconds, during which you should simply pull the physical plunger back 00093 // all the way, hold it for a moment, and then slowly return it to the rest 00094 // position. (DON'T just release it from the retracted position, since that 00095 // let it shoot forward too far. We want to measure the range from the park 00096 // position to the fully retracted position only.) 00097 // 00098 // - Button input wiring. 24 of the KL25Z's GPIO ports are mapped as digital inputs 00099 // for buttons and switches. The software reports these as joystick buttons when 00100 // it sends reports to the PC. These can be used to wire physical pinball-style 00101 // buttons in the cabinet (e.g., flipper buttons, the Start button) and miscellaneous 00102 // switches (such as a tilt bob) to the PC. Visual Pinball can use joystick buttons 00103 // for input - you just have to assign a VP function to each button using VP's 00104 // keyboard options dialog. To wire a button physically, connect one terminal of 00105 // the button switch to the KL25Z ground, and connect the other terminal to the 00106 // the GPIO port you wish to assign to the button. See the buttonMap[] array 00107 // below for the available GPIO ports and their assigned joystick button numbers. 00108 // If you're not using a GPIO port, you can just leave it unconnected - the digital 00109 // inputs have built-in pull-up resistors, so an unconnected port is the same as 00110 // an open switch (an "off" state for the button). 00111 // 00112 // - LedWiz emulation. The KL25Z can appear to the PC as an LedWiz device, and will 00113 // accept and process LedWiz commands from the host. The software can turn digital 00114 // output ports on and off, and can set varying PWM intensitiy levels on a subset 00115 // of ports. (The KL25Z can only provide 6 PWM ports. Intensity level settings on 00116 // other ports is ignored, so non-PWM ports can only be used for simple on/off 00117 // devices such as contactors and solenoids.) The KL25Z can only supply 4mA on its 00118 // output ports, so external hardware is required to take advantage of the LedWiz 00119 // emulation. Many different hardware designs are possible, but there's a simple 00120 // reference design in the documentation that uses a Darlington array IC to 00121 // increase the output from each port to 500mA (the same level as the LedWiz), 00122 // plus an extended design that adds an optocoupler and MOSFET to provide very 00123 // high power handling, up to about 45A or 150W, with voltages up to 100V. 00124 // That will handle just about any DC device directly (wtihout relays or other 00125 // amplifiers), and switches fast enough to support PWM devices. 00126 // 00127 // The device can report any desired LedWiz unit number to the host, which makes 00128 // it possible to use the LedWiz emulation on a machine that also has one or more 00129 // actual LedWiz devices intalled. The LedWiz design allows for up to 16 units 00130 // to be installed in one machine - each one is invidually addressable by its 00131 // distinct unit number. 00132 // 00133 // The LedWiz emulation features are of course optional. There's no need to 00134 // build any of the external port hardware (or attach anything to the output 00135 // ports at all) if the LedWiz features aren't needed. Most people won't have 00136 // any use for the LedWiz features. I built them mostly as a learning exercise, 00137 // but with a slight practical need for a handful of extra ports (I'm using the 00138 // cutting-edge 10-contactor setup, so my real LedWiz is full!). 00139 // 00140 // - Enhanced LedWiz emulation with TLC5940 PWM controller chips. You can attach 00141 // external PWM controller chips for controlling device outputs, instead of using 00142 // the limited LedWiz emulation through the on-board GPIO ports as described above. 00143 // The software can control a set of daisy-chained TLC5940 chips, which provide 00144 // 16 PWM outputs per chip. Two of these chips give you the full complement 00145 // of 32 output ports of an actual LedWiz, and four give you 64 ports, which 00146 // should be plenty for nearly any virtual pinball project. A private, extended 00147 // version of the LedWiz protocol lets the host control the extra outputs, up to 00148 // 128 outputs per KL25Z (8 TLC5940s). To take advantage of the extra outputs 00149 // on the PC side, you need software that knows about the protocol extensions, 00150 // which means you need the latest version of DirectOutput Framework (DOF). VP 00151 // uses DOF for its output, so VP will be able to use the added ports without any 00152 // extra work on your part. Older software (e.g., Future Pinball) that doesn't 00153 // use DOF will still be able to use the LedWiz-compatible protocol, so it'll be 00154 // able to control your first 32 ports (numbered 1-32 in the LedWiz scheme), but 00155 // older software won't be able to address higher-numbered ports. That shouldn't 00156 // be a problem because older software wouldn't know what to do with the extra 00157 // devices anyway - FP, for example, is limited to a pre-defined set of outputs. 00158 // As long as you put the most common devices on the first 32 outputs, and use 00159 // higher numbered ports for the less common devices that older software can't 00160 // use anyway, you'll get maximum functionality out of software new and old. 00161 // 00162 // STATUS LIGHTS: The on-board LED on the KL25Z flashes to indicate the current 00163 // device status. The flash patterns are: 00164 // 00165 // two short red flashes = the device is powered but hasn't successfully 00166 // connected to the host via USB (either it's not physically connected 00167 // to the USB port, or there was a problem with the software handshake 00168 // with the USB device driver on the computer) 00169 // 00170 // short red flash = the host computer is in sleep/suspend mode 00171 // 00172 // long red/green = the LedWiz unti number has been changed, so a reset 00173 // is needed. You can simply unplug the device and plug it back in, 00174 // or presss and hold the reset button on the device for a few seconds. 00175 // 00176 // long yellow/green = everything's working, but the plunger hasn't 00177 // been calibrated; follow the calibration procedure described above. 00178 // This flash mode won't appear if the CCD has been disabled. Note 00179 // that the device can't tell whether a CCD is physically attached; 00180 // if you don't have a CCD attached, you can set the appropriate option 00181 // in config.h or use the Windows config tool to disable the CCD 00182 // software features. 00183 // 00184 // alternating blue/green = everything's working 00185 // 00186 // Software configuration: you can some change option settings by sending special 00187 // USB commands from the PC. I've provided a Windows program for this purpose; 00188 // refer to the documentation for details. For reference, here's the format 00189 // of the USB command for option changes: 00190 // 00191 // length of report = 8 bytes 00192 // byte 0 = 65 (0x41) 00193 // byte 1 = 1 (0x01) 00194 // byte 2 = new LedWiz unit number, 0x01 to 0x0f 00195 // byte 3 = feature enable bit mask: 00196 // 0x01 = enable CCD (default = on) 00197 // 00198 // Plunger calibration mode: the host can activate plunger calibration mode 00199 // by sending this packet. This has the same effect as pressing and holding 00200 // the plunger calibration button for two seconds, to allow activating this 00201 // mode without attaching a physical button. 00202 // 00203 // length = 8 bytes 00204 // byte 0 = 65 (0x41) 00205 // byte 1 = 2 (0x02) 00206 // 00207 // Exposure reports: the host can request a report of the full set of pixel 00208 // values for the next frame by sending this special packet: 00209 // 00210 // length = 8 bytes 00211 // byte 0 = 65 (0x41) 00212 // byte 1 = 3 (0x03) 00213 // 00214 // We'll respond with a series of special reports giving the exposure status. 00215 // Each report has the following structure: 00216 // 00217 // bytes 0:1 = 11-bit index, with high 5 bits set to 10000. For 00218 // example, 0x04 0x80 indicates index 4. This is the 00219 // starting pixel number in the report. The first report 00220 // will be 0x00 0x80 to indicate pixel #0. 00221 // bytes 2:3 = 16-bit unsigned int brightness level of pixel at index 00222 // bytes 4:5 = brightness of pixel at index+1 00223 // etc for the rest of the packet 00224 // 00225 // This still has the form of a joystick packet at the USB level, but 00226 // can be differentiated by the host via the status bits. It would have 00227 // been cleaner to use a different Report ID at the USB level, but this 00228 // would have necessitated a different container structure in the report 00229 // descriptor, which would have broken LedWiz compatibility. Given that 00230 // constraint, we have to re-use the joystick report type, making for 00231 // this somewhat kludgey approach. 00232 // 00233 // Configuration query: the host can request a full report of our hardware 00234 // configuration with this message. 00235 // 00236 // length = 8 bytes 00237 // byte 0 = 65 (0x41) 00238 // byte 1 = 4 (0x04) 00239 // 00240 // We'll response with one report containing the configuration status: 00241 // 00242 // bytes 0:1 = 0x8800. This has the bit pattern 10001 in the high 00243 // 5 bits, which distinguishes it from regular joystick 00244 // reports and from exposure status reports. 00245 // bytes 2:3 = number of outputs 00246 // remaining bytes = reserved for future use; set to 0 in current version 00247 // 00248 // Turn off all outputs: this message tells the device to turn off all 00249 // outputs and restore power-up LedWiz defaults. This sets outputs #1-32 00250 // to profile 48 (full brightness) and switch state Off, sets all extended 00251 // outputs (#33 and above) to brightness 0, and sets the LedWiz flash rate 00252 // to 2. 00253 // 00254 // length = 8 bytes 00255 // byte 0 = 65 (0x41) 00256 // byte 1 = 5 (0x05) 00257 00258 00259 #include "mbed.h" 00260 #include "math.h" 00261 #include "USBJoystick.h" 00262 #include "MMA8451Q.h" 00263 #include "tsl1410r.h" 00264 #include "FreescaleIAP.h" 00265 #include "crc32.h" 00266 #include "TLC5940.h" 00267 #include "74HC595.h" 00268 00269 #define DECL_EXTERNS 00270 #include "config.h" 00271 00272 00273 // --------------------------------------------------------------------------- 00274 // utilities 00275 00276 // number of elements in an array 00277 #define countof(x) (sizeof(x)/sizeof((x)[0])) 00278 00279 // floating point square of a number 00280 inline float square(float x) { return x*x; } 00281 00282 // floating point rounding 00283 inline float round(float x) { return x > 0 ? floor(x + 0.5) : ceil(x - 0.5); } 00284 00285 00286 // -------------------------------------------------------------------------- 00287 // 00288 // USB product version number 00289 // 00290 const uint16_t USB_VERSION_NO = 0x0007; 00291 00292 00293 // 00294 // Build the full USB product ID. If we're using the LedWiz compatible 00295 // vendor ID, the full product ID is the combination of the LedWiz base 00296 // product ID (0x00F0) and the 0-based unit number (0-15). If we're not 00297 // trying to be LedWiz compatible, we just use the exact product ID 00298 // specified in config.h. 00299 #define MAKE_USB_PRODUCT_ID(vid, pidbase, unit) \ 00300 ((vid) == 0xFAFA && (pidbase) == 0x00F0 ? (pidbase) | (unit) : (pidbase)) 00301 00302 00303 // -------------------------------------------------------------------------- 00304 // 00305 // Joystick axis report range - we report from -JOYMAX to +JOYMAX 00306 // 00307 #define JOYMAX 4096 00308 00309 // -------------------------------------------------------------------------- 00310 // 00311 // Set up mappings for the joystick X and Y reports based on the mounting 00312 // orientation of the KL25Z in the cabinet. Visual Pinball and other 00313 // pinball software effectively use video coordinates to define the axes: 00314 // positive X is to the right of the table, negative Y to the left, positive 00315 // Y toward the front of the table, negative Y toward the back. The KL25Z 00316 // accelerometer is mounted on the board with positive Y toward the USB 00317 // ports and positive X toward the right side of the board with the USB 00318 // ports pointing up. It's a simple matter to remap the KL25Z coordinate 00319 // system to match VP's coordinate system for mounting orientations at 00320 // 90-degree increments... 00321 // 00322 #if defined(ORIENTATION_PORTS_AT_FRONT) 00323 # define JOY_X(x, y) (y) 00324 # define JOY_Y(x, y) (x) 00325 #elif defined(ORIENTATION_PORTS_AT_LEFT) 00326 # define JOY_X(x, y) (-(x)) 00327 # define JOY_Y(x, y) (y) 00328 #elif defined(ORIENTATION_PORTS_AT_RIGHT) 00329 # define JOY_X(x, y) (x) 00330 # define JOY_Y(x, y) (-(y)) 00331 #elif defined(ORIENTATION_PORTS_AT_REAR) 00332 # define JOY_X(x, y) (-(y)) 00333 # define JOY_Y(x, y) (-(x)) 00334 #else 00335 # error Please define one of the ORIENTATION_PORTS_AT_xxx macros to establish the accelerometer orientation in your cabinet 00336 #endif 00337 00338 00339 00340 // -------------------------------------------------------------------------- 00341 // 00342 // Define a symbol to tell us whether any sort of plunger sensor code 00343 // is enabled in this build. Note that this doesn't tell us that a 00344 // plunger device is actually attached or *currently* enabled; it just 00345 // tells us whether or not the code for plunger sensing is enabled in 00346 // the software build. This lets us leave out some unnecessary code 00347 // on installations where no physical plunger is attached. 00348 // 00349 const int PLUNGER_CODE_ENABLED = 00350 #if defined(ENABLE_CCD_SENSOR) || defined(ENABLE_POT_SENSOR) 00351 1; 00352 #else 00353 0; 00354 #endif 00355 00356 // --------------------------------------------------------------------------- 00357 // 00358 // On-board RGB LED elements - we use these for diagnostic displays. 00359 // 00360 // Note that LED3 (the blue segment) is hard-wired on the KL25Z to PTD1, 00361 // so PTD1 shouldn't be used for any other purpose (e.g., as a keyboard 00362 // input or a device output). (This is kind of unfortunate in that it's 00363 // one of only two ports exposed on the jumper pins that can be muxed to 00364 // SPI0 SCLK. This effectively limits us to PTC5 if we want to use the 00365 // SPI capability.) 00366 // 00367 DigitalOut ledR(LED1), ledG(LED2), ledB(LED3); 00368 00369 00370 // --------------------------------------------------------------------------- 00371 // 00372 // LedWiz emulation, and enhanced TLC5940 output controller 00373 // 00374 // There are two modes for this feature. The default mode uses the on-board 00375 // GPIO ports to implement device outputs - each LedWiz software port is 00376 // connected to a physical GPIO pin on the KL25Z. The KL25Z only has 10 00377 // PWM channels, so in this mode only 10 LedWiz ports will be dimmable; the 00378 // rest are strictly on/off. The KL25Z also has a limited number of GPIO 00379 // ports overall - not enough for the full complement of 32 LedWiz ports 00380 // and 24 VP joystick inputs, so it's necessary to trade one against the 00381 // other if both features are to be used. 00382 // 00383 // The alternative, enhanced mode uses external TLC5940 PWM controller 00384 // chips to control device outputs. In this mode, each LedWiz software 00385 // port is mapped to an output on one of the external TLC5940 chips. 00386 // Two 5940s is enough for the full set of 32 LedWiz ports, and we can 00387 // support even more chips for even more outputs (although doing so requires 00388 // breaking LedWiz compatibility, since the LedWiz USB protocol is hardwired 00389 // for 32 outputs). Every port in this mode has full PWM support. 00390 // 00391 00392 00393 // Current starting output index for "PBA" messages from the PC (using 00394 // the LedWiz USB protocol). Each PBA message implicitly uses the 00395 // current index as the starting point for the ports referenced in 00396 // the message, and increases it (by 8) for the next call. 00397 static int pbaIdx = 0; 00398 00399 // Generic LedWiz output port interface. We create a cover class to 00400 // virtualize digital vs PWM outputs, and on-board KL25Z GPIO vs external 00401 // TLC5940 outputs, and give them all a common interface. 00402 class LwOut 00403 { 00404 public: 00405 // Set the output intensity. 'val' is 0.0 for fully off, 1.0 for 00406 // fully on, and fractional values for intermediate intensities. 00407 virtual void set(float val) = 0; 00408 }; 00409 00410 // LwOut class for unmapped ports. The LedWiz protocol is hardwired 00411 // for 32 ports, but we might not want to assign all 32 software ports 00412 // to physical output pins - the KL25Z has a limited number of GPIO 00413 // ports, so we might not have enough available GPIOs to fill out the 00414 // full LedWiz complement after assigning GPIOs for other functions. 00415 // This class is used to populate the LedWiz mapping array for ports 00416 // that aren't connected to physical outputs; it simply ignores value 00417 // changes. 00418 class LwUnusedOut: public LwOut 00419 { 00420 public: 00421 LwUnusedOut() { } 00422 virtual void set(float val) { } 00423 }; 00424 00425 // Active Low out. For any output marked as active low, we layer this 00426 // on top of the physical pin interface. This simply inverts the value of 00427 // the output value, so that 1.0 means fully off and 0.0 means fully on. 00428 class LwInvertedOut: public LwOut 00429 { 00430 public: 00431 LwInvertedOut(LwOut *o) : out(o) { } 00432 virtual void set(float val) { out->set(1.0 - val); } 00433 00434 private: 00435 LwOut *out; 00436 }; 00437 00438 00439 #if TLC5940_NCHIPS 00440 // 00441 // The TLC5940 interface object. Set this up with the port assignments 00442 // set in config.h. 00443 // 00444 TLC5940 tlc5940(TLC5940_SCLK, TLC5940_SIN, TLC5940_GSCLK, TLC5940_BLANK, 00445 TLC5940_XLAT, TLC5940_NCHIPS); 00446 00447 // LwOut class for TLC5940 outputs. These are fully PWM capable. 00448 // The 'idx' value in the constructor is the output index in the 00449 // daisy-chained TLC5940 array. 0 is output #0 on the first chip, 00450 // 1 is #1 on the first chip, 15 is #15 on the first chip, 16 is 00451 // #0 on the second chip, 32 is #0 on the third chip, etc. 00452 class Lw5940Out: public LwOut 00453 { 00454 public: 00455 Lw5940Out(int idx) : idx(idx) { prv = -1; } 00456 virtual void set(float val) 00457 { 00458 if (val != prv) 00459 tlc5940.set(idx, (int)((prv = val) * 4095)); 00460 } 00461 int idx; 00462 float prv; 00463 }; 00464 00465 #else 00466 // No TLC5940 chips are attached, so we shouldn't encounter any ports 00467 // in the map marked for TLC5940 outputs. If we do, treat them as unused. 00468 class Lw5940Out: public LwUnusedOut 00469 { 00470 public: 00471 Lw5940Out(int idx) { } 00472 }; 00473 00474 // dummy tlc5940 interface 00475 class Dummy5940 00476 { 00477 public: 00478 void start() { } 00479 }; 00480 Dummy5940 tlc5940; 00481 00482 #endif // TLC5940_NCHIPS 00483 00484 #if HC595_NCHIPS 00485 // 74HC595 interface object. Set this up with the port assignments in 00486 // config.h. 00487 HC595 hc595(HC595_NCHIPS, HC595_SIN, HC595_SCLK, HC595_LATCH, HC595_ENA); 00488 00489 // LwOut class for 74HC595 outputs. These are simple digial outs. 00490 // The 'idx' value in the constructor is the output index in the 00491 // daisy-chained 74HC595 array. 0 is output #0 on the first chip, 00492 // 1 is #1 on the first chip, 7 is #7 on the first chip, 8 is 00493 // #0 on the second chip, etc. 00494 class Lw595Out: public LwOut 00495 { 00496 public: 00497 Lw595Out(int idx) : idx(idx) { prv = -1; } 00498 virtual void set(float val) 00499 { 00500 if (val != prv) 00501 hc595.set(idx, (prv = val) == 0.0 ? 0 : 1); 00502 } 00503 int idx; 00504 float prv; 00505 }; 00506 00507 #else // HC595_NCHIPS 00508 // No 74HC595 chips are attached, so we shouldn't encounter any ports 00509 // in the map marked for these outputs. If we do, treat them as unused. 00510 class Lw595Out: public LwUnusedOut 00511 { 00512 public: 00513 Lw595Out(int idx) { } 00514 }; 00515 00516 // dummy placeholder class 00517 class DummyHC595 00518 { 00519 public: 00520 void init() { } 00521 void update() { } 00522 }; 00523 DummyHC595 hc595; 00524 00525 #endif // HC595_NCHIPS 00526 00527 // 00528 // Default LedWiz mode - using on-board GPIO ports. In this mode, we 00529 // assign a KL25Z GPIO port to each LedWiz output. We have to use a 00530 // mix of PWM-capable and Digital-Only ports in this configuration, 00531 // since the KL25Z hardware only has 10 PWM channels, which isn't 00532 // enough to fill out the full complement of 32 LedWiz outputs. 00533 // 00534 00535 // LwOut class for a PWM-capable GPIO port 00536 class LwPwmOut: public LwOut 00537 { 00538 public: 00539 LwPwmOut(PinName pin) : p(pin) { prv = -1; } 00540 virtual void set(float val) 00541 { 00542 if (val != prv) 00543 p.write(prv = val); 00544 } 00545 PwmOut p; 00546 float prv; 00547 }; 00548 00549 // LwOut class for a Digital-Only (Non-PWM) GPIO port 00550 class LwDigOut: public LwOut 00551 { 00552 public: 00553 LwDigOut(PinName pin) : p(pin) { prv = -1; } 00554 virtual void set(float val) 00555 { 00556 if (val != prv) 00557 p.write((prv = val) == 0.0 ? 0 : 1); 00558 } 00559 DigitalOut p; 00560 float prv; 00561 }; 00562 00563 // Array of output physical pin assignments. This array is indexed 00564 // by LedWiz logical port number - lwPin[n] is the maping for LedWiz 00565 // port n (0-based). If we're using GPIO ports to implement outputs, 00566 // we initialize the array at start-up to map each logical port to the 00567 // physical GPIO pin for the port specified in the ledWizPortMap[] 00568 // array in config.h. If we're using TLC5940 chips for the outputs, 00569 // we map each logical port to the corresponding TLC5940 output. 00570 static int numOutputs; 00571 static LwOut **lwPin; 00572 00573 // Current absolute brightness level for an output. This is a float 00574 // value from 0.0 for fully off to 1.0 for fully on. This is the final 00575 // derived value for the port. For outputs set by LedWiz messages, 00576 // this is derived from the LedWiz state, and is updated on each pulse 00577 // timer interrupt for lights in flashing states. For outputs set by 00578 // extended protocol messages, this is simply the brightness last set. 00579 static float *outLevel; 00580 00581 // initialize the output pin array 00582 void initLwOut() 00583 { 00584 // Figure out how many outputs we have. We always have at least 00585 // 32 outputs, since that's the number fixed by the original LedWiz 00586 // protocol. If we're using TLC5940 chips, each chip provides 16 00587 // outputs. Likewise, each 74HC595 provides 8 outputs. 00588 00589 // start with 16 ports per TLC5940 and 8 per 74HC595 00590 numOutputs = TLC5940_NCHIPS*16 + HC595_NCHIPS*8; 00591 00592 // add outputs explicitly assigned to GPIO pins or not connected 00593 int i; 00594 for (i = 0 ; i < countof(ledWizPortMap) ; ++i) 00595 { 00596 switch (ledWizPortMap[i].typ) 00597 { 00598 case DIG_GPIO: 00599 case PWM_GPIO: 00600 case NO_PORT: 00601 // count an explicitly GPIO port 00602 ++numOutputs; 00603 break; 00604 00605 default: 00606 // DON'T count TLC5940 or 74HC595 ports, as we've already 00607 // counted all of these above 00608 break; 00609 } 00610 } 00611 00612 // always set up at least 32 outputs, so that we don't have to 00613 // check bounds on commands from the basic LedWiz protocol 00614 if (numOutputs < 32) 00615 numOutputs = 32; 00616 00617 // allocate the pin array 00618 lwPin = new LwOut*[numOutputs]; 00619 00620 // allocate the current brightness array 00621 outLevel = new float[numOutputs]; 00622 00623 // allocate a temporary array to keep track of which physical 00624 // TLC5940 ports we've assigned so far 00625 char *tlcasi = new char[TLC5940_NCHIPS*16+1]; 00626 memset(tlcasi, 0, TLC5940_NCHIPS*16); 00627 00628 // likewise for the 74HC595 ports 00629 char *hcasi = new char[HC595_NCHIPS*8+1]; 00630 memset(hcasi, 0, HC595_NCHIPS*8); 00631 00632 // assign all pins from the explicit port map in config.h 00633 for (i = 0 ; i < countof(ledWizPortMap) ; ++i) 00634 { 00635 int pin = ledWizPortMap[i].pin; 00636 LWPortType typ = ledWizPortMap[i].typ; 00637 int flags = ledWizPortMap[i].flags; 00638 int activeLow = flags & PORT_ACTIVE_LOW; 00639 switch (typ) 00640 { 00641 case DIG_GPIO: 00642 lwPin[i] = new LwDigOut((PinName)pin); 00643 break; 00644 00645 case PWM_GPIO: 00646 // PWM GPIO port 00647 lwPin[i] = new LwPwmOut((PinName)pin); 00648 break; 00649 00650 case TLC_PORT: 00651 // TLC5940 port (note that the nominal pin in the map is 1-based, so we 00652 // have to decrement it to get the real pin index) 00653 lwPin[i] = new Lw5940Out(pin-1); 00654 tlcasi[pin-1] = 1; 00655 break; 00656 00657 case HC595_PORT: 00658 // 74HC595 port (the pin in the map is 1-based, so decrement it to get the 00659 // real pin index) 00660 lwPin[i] = new Lw595Out(pin-1); 00661 hcasi[pin-1] = 1; 00662 break; 00663 00664 default: 00665 lwPin[i] = new LwUnusedOut(); 00666 break; 00667 } 00668 00669 // if it's Active Low, layer an inverter 00670 if (activeLow) 00671 lwPin[i] = new LwInvertedOut(lwPin[i]); 00672 00673 // turn it off initially 00674 lwPin[i]->set(0); 00675 } 00676 00677 // If we haven't assigned all of the LedWiz ports to physical pins, 00678 // fill out the unassigned LedWiz ports with any unassigned TLC5940 00679 // pins, then with any unassigned 74HC595 ports. 00680 int tlcnxt, hcnxt; 00681 for (tlcnxt = 0 ; tlcnxt < TLC5940_NCHIPS*16 && tlcasi[tlcnxt] ; ++tlcnxt) ; 00682 for (hcnxt = 0 ; hcnxt < HC595_NCHIPS*8 && hcasi[hcnxt] ; ++hcnxt) ; 00683 for ( ; i < numOutputs ; ++i) 00684 { 00685 // If we have any more unassigned TLC5940 outputs, assign this LedWiz 00686 // port to the next available TLC5940 output, or the next 74HC595 output 00687 // if we're out of TLC5940 outputs. Leave it unassigned if there are 00688 // no more unassigned ports of any type. 00689 if (tlcnxt < TLC5940_NCHIPS*16) 00690 { 00691 // assign this available TLC5940 pin, and find the next unused one 00692 lwPin[i] = new Lw5940Out(tlcnxt); 00693 for (++tlcnxt ; tlcnxt < TLC5940_NCHIPS*16 && tlcasi[tlcnxt] ; ++tlcnxt) ; 00694 } 00695 else if (hcnxt < HC595_NCHIPS*8) 00696 { 00697 // assign this available 74HC595 pin, and find the next unused one 00698 lwPin[i] = new Lw595Out(hcnxt); 00699 for (++hcnxt ; hcnxt < HC595_NCHIPS*8 && hcasi[hcnxt] ; ++hcnxt) ; 00700 } 00701 else 00702 { 00703 // no more ports available - set up this port as unconnected 00704 lwPin[i] = new LwUnusedOut(); 00705 } 00706 } 00707 00708 // done with the temporary TLC5940 and 74HC595 port assignment lists 00709 delete [] tlcasi; 00710 delete [] hcasi; 00711 } 00712 00713 // LedWiz output states. 00714 // 00715 // The LedWiz protocol has two separate control axes for each output. 00716 // One axis is its on/off state; the other is its "profile" state, which 00717 // is either a fixed brightness or a blinking pattern for the light. 00718 // The two axes are independent. 00719 // 00720 // Note that the LedWiz protocol can only address 32 outputs, so the 00721 // wizOn and wizVal arrays have fixed sizes of 32 elements no matter 00722 // how many physical outputs we're using. 00723 00724 // on/off state for each LedWiz output 00725 static uint8_t wizOn[32]; 00726 00727 // Profile (brightness/blink) state for each LedWiz output. If the 00728 // output was last updated through an LedWiz protocol message, it 00729 // will have one of these values: 00730 // 00731 // 0-48 = fixed brightness 0% to 100% 00732 // 129 = ramp up / ramp down 00733 // 130 = flash on / off 00734 // 131 = on / ramp down 00735 // 132 = ramp up / on 00736 // 00737 // Special value 255: If the output was updated through the 00738 // extended protocol, we'll set the wizVal entry to 255, which has 00739 // no meaning in the LedWiz protocol. This tells us that the value 00740 // in outLevel[] was set directly from the extended protocol, so it 00741 // shouldn't be derived from wizVal[]. 00742 // 00743 static uint8_t wizVal[32] = { 00744 48, 48, 48, 48, 48, 48, 48, 48, 00745 48, 48, 48, 48, 48, 48, 48, 48, 00746 48, 48, 48, 48, 48, 48, 48, 48, 00747 48, 48, 48, 48, 48, 48, 48, 48 00748 }; 00749 00750 // LedWiz flash speed. This is a value from 1 to 7 giving the pulse 00751 // rate for lights in blinking states. 00752 static uint8_t wizSpeed = 2; 00753 00754 // Current LedWiz flash cycle counter. 00755 static uint8_t wizFlashCounter = 0; 00756 00757 // Get the current brightness level for an LedWiz output. 00758 static float wizState(int idx) 00759 { 00760 // if the output was last set with an extended protocol message, 00761 // use the value set there, ignoring the output's LedWiz state 00762 if (wizVal[idx] == 255) 00763 return outLevel[idx]; 00764 00765 // if it's off, show at zero intensity 00766 if (!wizOn[idx]) 00767 return 0; 00768 00769 // check the state 00770 uint8_t val = wizVal[idx]; 00771 if (val <= 48) 00772 { 00773 // PWM brightness/intensity level. Rescale from the LedWiz 00774 // 0..48 integer range to our internal PwmOut 0..1 float range. 00775 // Note that on the actual LedWiz, level 48 is actually about 00776 // 98% on - contrary to the LedWiz documentation, level 49 is 00777 // the true 100% level. (In the documentation, level 49 is 00778 // simply not a valid setting.) Even so, we treat level 48 as 00779 // 100% on to match the documentation. This won't be perfectly 00780 // ocmpatible with the actual LedWiz, but it makes for such a 00781 // small difference in brightness (if the output device is an 00782 // LED, say) that no one should notice. It seems better to 00783 // err in this direction, because while the difference in 00784 // brightness when attached to an LED won't be noticeable, the 00785 // difference in duty cycle when attached to something like a 00786 // contactor *can* be noticeable - anything less than 100% 00787 // can cause a contactor or relay to chatter. There's almost 00788 // never a situation where you'd want values other than 0% and 00789 // 100% for a contactor or relay, so treating level 48 as 100% 00790 // makes us work properly with software that's expecting the 00791 // documented LedWiz behavior and therefore uses level 48 to 00792 // turn a contactor or relay fully on. 00793 return val/48.0; 00794 } 00795 else if (val == 49) 00796 { 00797 // 49 is undefined in the LedWiz documentation, but actually 00798 // means 100% on. The documentation says that levels 1-48 are 00799 // the full PWM range, but empirically it appears that the real 00800 // range implemented in the firmware is 1-49. Some software on 00801 // the PC side (notably DOF) is aware of this and uses level 49 00802 // to mean "100% on". To ensure compatibility with existing 00803 // PC-side software, we need to recognize level 49. 00804 return 1.0; 00805 } 00806 else if (val == 129) 00807 { 00808 // 129 = ramp up / ramp down 00809 return wizFlashCounter < 128 00810 ? wizFlashCounter/128.0 00811 : (256 - wizFlashCounter)/128.0; 00812 } 00813 else if (val == 130) 00814 { 00815 // 130 = flash on / off 00816 return wizFlashCounter < 128 ? 1.0 : 0.0; 00817 } 00818 else if (val == 131) 00819 { 00820 // 131 = on / ramp down 00821 return wizFlashCounter < 128 ? 1.0 : (255 - wizFlashCounter)/128.0; 00822 } 00823 else if (val == 132) 00824 { 00825 // 132 = ramp up / on 00826 return wizFlashCounter < 128 ? wizFlashCounter/128.0 : 1.0; 00827 } 00828 else 00829 { 00830 // Other values are undefined in the LedWiz documentation. Hosts 00831 // *should* never send undefined values, since whatever behavior an 00832 // LedWiz unit exhibits in response is accidental and could change 00833 // in a future version. We'll treat all undefined values as equivalent 00834 // to 48 (fully on). 00835 return 1.0; 00836 } 00837 } 00838 00839 // LedWiz flash timer pulse. This fires periodically to update 00840 // LedWiz flashing outputs. At the slowest pulse speed set via 00841 // the SBA command, each waveform cycle has 256 steps, so we 00842 // choose the pulse time base so that the slowest cycle completes 00843 // in 2 seconds. This seems to roughly match the real LedWiz 00844 // behavior. We run the pulse timer at the same rate regardless 00845 // of the pulse speed; at higher pulse speeds, we simply use 00846 // larger steps through the cycle on each interrupt. Running 00847 // every 1/127 of a second = 8ms seems to be a pretty light load. 00848 Timeout wizPulseTimer; 00849 #define WIZ_PULSE_TIME_BASE (1.0/127.0) 00850 static void wizPulse() 00851 { 00852 // increase the counter by the speed increment, and wrap at 256 00853 wizFlashCounter += wizSpeed; 00854 wizFlashCounter &= 0xff; 00855 00856 // if we have any flashing lights, update them 00857 int ena = false; 00858 for (int i = 0 ; i < 32 ; ++i) 00859 { 00860 if (wizOn[i]) 00861 { 00862 uint8_t s = wizVal[i]; 00863 if (s >= 129 && s <= 132) 00864 { 00865 lwPin[i]->set(wizState(i)); 00866 ena = true; 00867 } 00868 } 00869 } 00870 00871 // Set up the next timer pulse only if we found anything flashing. 00872 // To minimize overhead from this feature, we only enable the interrupt 00873 // when we need it. This eliminates any performance penalty to other 00874 // features when the host software doesn't care about the flashing 00875 // modes. For example, DOF never uses these modes, so there's no 00876 // need for them when running Visual Pinball. 00877 if (ena) 00878 wizPulseTimer.attach(wizPulse, WIZ_PULSE_TIME_BASE); 00879 } 00880 00881 // Update the physical outputs connected to the LedWiz ports. This is 00882 // called after any update from an LedWiz protocol message. 00883 static void updateWizOuts() 00884 { 00885 // update each output 00886 int pulse = false; 00887 for (int i = 0 ; i < 32 ; ++i) 00888 { 00889 pulse |= (wizVal[i] >= 129 && wizVal[i] <= 132); 00890 lwPin[i]->set(wizState(i)); 00891 } 00892 00893 // if any outputs are set to flashing mode, and the pulse timer 00894 // isn't running, turn it on 00895 if (pulse) 00896 wizPulseTimer.attach(wizPulse, WIZ_PULSE_TIME_BASE); 00897 00898 // flush changes to 74HC595 chips, if attached 00899 hc595.update(); 00900 } 00901 00902 // --------------------------------------------------------------------------- 00903 // 00904 // Button input 00905 // 00906 00907 // button input map array 00908 DigitalIn *buttonDigIn[32]; 00909 00910 // button state 00911 struct ButtonState 00912 { 00913 // current on/off state 00914 int pressed; 00915 00916 // Sticky time remaining for current state. When a 00917 // state transition occurs, we set this to a debounce 00918 // period. Future state transitions will be ignored 00919 // until the debounce time elapses. 00920 int t; 00921 } buttonState[32]; 00922 00923 // timer for button reports 00924 static Timer buttonTimer; 00925 00926 // initialize the button inputs 00927 void initButtons() 00928 { 00929 // create the digital inputs 00930 for (int i = 0 ; i < countof(buttonDigIn) ; ++i) 00931 { 00932 if (i < countof(buttonMap) && buttonMap[i] != NC) 00933 buttonDigIn[i] = new DigitalIn(buttonMap[i]); 00934 else 00935 buttonDigIn[i] = 0; 00936 } 00937 00938 // start the button timer 00939 buttonTimer.start(); 00940 } 00941 00942 00943 // read the button input state 00944 uint32_t readButtons() 00945 { 00946 // start with all buttons off 00947 uint32_t buttons = 0; 00948 00949 // figure the time elapsed since the last scan 00950 int dt = buttonTimer.read_ms(); 00951 00952 // reset the timef for the next scan 00953 buttonTimer.reset(); 00954 00955 // scan the button list 00956 uint32_t bit = 1; 00957 DigitalIn **di = buttonDigIn; 00958 ButtonState *bs = buttonState; 00959 for (int i = 0 ; i < countof(buttonDigIn) ; ++i, ++di, ++bs, bit <<= 1) 00960 { 00961 // read this button 00962 if (*di != 0) 00963 { 00964 // deduct the elapsed time since the last update 00965 // from the button's remaining sticky time 00966 bs->t -= dt; 00967 if (bs->t < 0) 00968 bs->t = 0; 00969 00970 // If the sticky time has elapsed, note the new physical 00971 // state of the button. If we still have sticky time 00972 // remaining, ignore the physical state; the last state 00973 // change persists until the sticky time elapses so that 00974 // we smooth out any "bounce" (electrical transients that 00975 // occur when the switch contact is opened or closed). 00976 if (bs->t == 0) 00977 { 00978 // get the new physical state 00979 int pressed = !(*di)->read(); 00980 00981 // update the button's logical state if this is a change 00982 if (pressed != bs->pressed) 00983 { 00984 // store the new state 00985 bs->pressed = pressed; 00986 00987 // start a new sticky period for debouncing this 00988 // state change 00989 bs->t = 25; 00990 } 00991 } 00992 00993 // if it's pressed, OR its bit into the state 00994 if (bs->pressed) 00995 buttons |= bit; 00996 } 00997 } 00998 00999 // return the new button list 01000 return buttons; 01001 } 01002 01003 // --------------------------------------------------------------------------- 01004 // 01005 // Customization joystick subbclass 01006 // 01007 01008 class MyUSBJoystick: public USBJoystick 01009 { 01010 public: 01011 MyUSBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release) 01012 : USBJoystick(vendor_id, product_id, product_release, true) 01013 { 01014 suspended_ = false; 01015 } 01016 01017 // are we connected? 01018 int isConnected() { return configured(); } 01019 01020 // Are we in suspend mode? 01021 int isSuspended() const { return suspended_; } 01022 01023 protected: 01024 virtual void suspendStateChanged(unsigned int suspended) 01025 { suspended_ = suspended; } 01026 01027 // are we suspended? 01028 int suspended_; 01029 }; 01030 01031 // --------------------------------------------------------------------------- 01032 // 01033 // Accelerometer (MMA8451Q) 01034 // 01035 01036 // The MMA8451Q is the KL25Z's on-board 3-axis accelerometer. 01037 // 01038 // This is a custom wrapper for the library code to interface to the 01039 // MMA8451Q. This class encapsulates an interrupt handler and 01040 // automatic calibration. 01041 // 01042 // We install an interrupt handler on the accelerometer "data ready" 01043 // interrupt to ensure that we fetch each sample immediately when it 01044 // becomes available. The accelerometer data rate is fiarly high 01045 // (800 Hz), so it's not practical to keep up with it by polling. 01046 // Using an interrupt handler lets us respond quickly and read 01047 // every sample. 01048 // 01049 // We automatically calibrate the accelerometer so that it's not 01050 // necessary to get it exactly level when installing it, and so 01051 // that it's also not necessary to calibrate it manually. There's 01052 // lots of experience that tells us that manual calibration is a 01053 // terrible solution, mostly because cabinets tend to shift slightly 01054 // during use, requiring frequent recalibration. Instead, we 01055 // calibrate automatically. We continuously monitor the acceleration 01056 // data, watching for periods of constant (or nearly constant) values. 01057 // Any time it appears that the machine has been at rest for a while 01058 // (about 5 seconds), we'll average the readings during that rest 01059 // period and use the result as the level rest position. This is 01060 // is ongoing, so we'll quickly find the center point again if the 01061 // machine is moved during play (by an especially aggressive bout 01062 // of nudging, say). 01063 // 01064 01065 // I2C address of the accelerometer (this is a constant of the KL25Z) 01066 const int MMA8451_I2C_ADDRESS = (0x1d<<1); 01067 01068 // SCL and SDA pins for the accelerometer (constant for the KL25Z) 01069 #define MMA8451_SCL_PIN PTE25 01070 #define MMA8451_SDA_PIN PTE24 01071 01072 // Digital in pin to use for the accelerometer interrupt. For the KL25Z, 01073 // this can be either PTA14 or PTA15, since those are the pins physically 01074 // wired on this board to the MMA8451 interrupt controller. 01075 #define MMA8451_INT_PIN PTA15 01076 01077 01078 // accelerometer input history item, for gathering calibration data 01079 struct AccHist 01080 { 01081 AccHist() { x = y = d = 0.0; xtot = ytot = 0.0; cnt = 0; } 01082 void set(float x, float y, AccHist *prv) 01083 { 01084 // save the raw position 01085 this->x = x; 01086 this->y = y; 01087 this->d = distance(prv); 01088 } 01089 01090 // reading for this entry 01091 float x, y; 01092 01093 // distance from previous entry 01094 float d; 01095 01096 // total and count of samples averaged over this period 01097 float xtot, ytot; 01098 int cnt; 01099 01100 void clearAvg() { xtot = ytot = 0.0; cnt = 0; } 01101 void addAvg(float x, float y) { xtot += x; ytot += y; ++cnt; } 01102 float xAvg() const { return xtot/cnt; } 01103 float yAvg() const { return ytot/cnt; } 01104 01105 float distance(AccHist *p) 01106 { return sqrt(square(p->x - x) + square(p->y - y)); } 01107 }; 01108 01109 // accelerometer wrapper class 01110 class Accel 01111 { 01112 public: 01113 Accel(PinName sda, PinName scl, int i2cAddr, PinName irqPin) 01114 : mma_(sda, scl, i2cAddr), intIn_(irqPin) 01115 { 01116 // remember the interrupt pin assignment 01117 irqPin_ = irqPin; 01118 01119 // reset and initialize 01120 reset(); 01121 } 01122 01123 void reset() 01124 { 01125 // clear the center point 01126 cx_ = cy_ = 0.0; 01127 01128 // start the calibration timer 01129 tCenter_.start(); 01130 iAccPrv_ = nAccPrv_ = 0; 01131 01132 // reset and initialize the MMA8451Q 01133 mma_.init(); 01134 01135 // set the initial integrated velocity reading to zero 01136 vx_ = vy_ = 0; 01137 01138 // set up our accelerometer interrupt handling 01139 intIn_.rise(this, &Accel::isr); 01140 mma_.setInterruptMode(irqPin_ == PTA14 ? 1 : 2); 01141 01142 // read the current registers to clear the data ready flag 01143 mma_.getAccXYZ(ax_, ay_, az_); 01144 01145 // start our timers 01146 tGet_.start(); 01147 tInt_.start(); 01148 } 01149 01150 void get(int &x, int &y) 01151 { 01152 // disable interrupts while manipulating the shared data 01153 __disable_irq(); 01154 01155 // read the shared data and store locally for calculations 01156 float ax = ax_, ay = ay_; 01157 float vx = vx_, vy = vy_; 01158 01159 // reset the velocity sum for the next run 01160 vx_ = vy_ = 0; 01161 01162 // get the time since the last get() sample 01163 float dt = tGet_.read_us()/1.0e6; 01164 tGet_.reset(); 01165 01166 // done manipulating the shared data 01167 __enable_irq(); 01168 01169 // adjust the readings for the integration time 01170 vx /= dt; 01171 vy /= dt; 01172 01173 // add this sample to the current calibration interval's running total 01174 AccHist *p = accPrv_ + iAccPrv_; 01175 p->addAvg(ax, ay); 01176 01177 // check for auto-centering every so often 01178 if (tCenter_.read_ms() > 1000) 01179 { 01180 // add the latest raw sample to the history list 01181 AccHist *prv = p; 01182 iAccPrv_ = (iAccPrv_ + 1) % maxAccPrv; 01183 p = accPrv_ + iAccPrv_; 01184 p->set(ax, ay, prv); 01185 01186 // if we have a full complement, check for stability 01187 if (nAccPrv_ >= maxAccPrv) 01188 { 01189 // check if we've been stable for all recent samples 01190 static const float accTol = .01; 01191 AccHist *p0 = accPrv_; 01192 if (p0[0].d < accTol 01193 && p0[1].d < accTol 01194 && p0[2].d < accTol 01195 && p0[3].d < accTol 01196 && p0[4].d < accTol) 01197 { 01198 // Figure the new calibration point as the average of 01199 // the samples over the rest period 01200 cx_ = (p0[0].xAvg() + p0[1].xAvg() + p0[2].xAvg() + p0[3].xAvg() + p0[4].xAvg())/5.0; 01201 cy_ = (p0[0].yAvg() + p0[1].yAvg() + p0[2].yAvg() + p0[3].yAvg() + p0[4].yAvg())/5.0; 01202 } 01203 } 01204 else 01205 { 01206 // not enough samples yet; just up the count 01207 ++nAccPrv_; 01208 } 01209 01210 // clear the new item's running totals 01211 p->clearAvg(); 01212 01213 // reset the timer 01214 tCenter_.reset(); 01215 } 01216 01217 // report our integrated velocity reading in x,y 01218 x = rawToReport(vx); 01219 y = rawToReport(vy); 01220 01221 #ifdef DEBUG_PRINTF 01222 if (x != 0 || y != 0) 01223 printf("%f %f %d %d %f\r\n", vx, vy, x, y, dt); 01224 #endif 01225 } 01226 01227 private: 01228 // adjust a raw acceleration figure to a usb report value 01229 int rawToReport(float v) 01230 { 01231 // scale to the joystick report range and round to integer 01232 int i = int(round(v*JOYMAX)); 01233 01234 // if it's near the center, scale it roughly as 20*(i/20)^2, 01235 // to suppress noise near the rest position 01236 static const int filter[] = { 01237 -18, -16, -14, -13, -11, -10, -8, -7, -6, -5, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0, 01238 0, 01239 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 16, 18 01240 }; 01241 return (i > 20 || i < -20 ? i : filter[i+20]); 01242 } 01243 01244 // interrupt handler 01245 void isr() 01246 { 01247 // Read the axes. Note that we have to read all three axes 01248 // (even though we only really use x and y) in order to clear 01249 // the "data ready" status bit in the accelerometer. The 01250 // interrupt only occurs when the "ready" bit transitions from 01251 // off to on, so we have to make sure it's off. 01252 float x, y, z; 01253 mma_.getAccXYZ(x, y, z); 01254 01255 // calculate the time since the last interrupt 01256 float dt = tInt_.read_us()/1.0e6; 01257 tInt_.reset(); 01258 01259 // integrate the time slice from the previous reading to this reading 01260 vx_ += (x + ax_ - 2*cx_)*dt/2; 01261 vy_ += (y + ay_ - 2*cy_)*dt/2; 01262 01263 // store the updates 01264 ax_ = x; 01265 ay_ = y; 01266 az_ = z; 01267 } 01268 01269 // underlying accelerometer object 01270 MMA8451Q mma_; 01271 01272 // last raw acceleration readings 01273 float ax_, ay_, az_; 01274 01275 // integrated velocity reading since last get() 01276 float vx_, vy_; 01277 01278 // timer for measuring time between get() samples 01279 Timer tGet_; 01280 01281 // timer for measuring time between interrupts 01282 Timer tInt_; 01283 01284 // Calibration reference point for accelerometer. This is the 01285 // average reading on the accelerometer when in the neutral position 01286 // at rest. 01287 float cx_, cy_; 01288 01289 // timer for atuo-centering 01290 Timer tCenter_; 01291 01292 // Auto-centering history. This is a separate history list that 01293 // records results spaced out sparesely over time, so that we can 01294 // watch for long-lasting periods of rest. When we observe nearly 01295 // no motion for an extended period (on the order of 5 seconds), we 01296 // take this to mean that the cabinet is at rest in its neutral 01297 // position, so we take this as the calibration zero point for the 01298 // accelerometer. We update this history continuously, which allows 01299 // us to continuously re-calibrate the accelerometer. This ensures 01300 // that we'll automatically adjust to any actual changes in the 01301 // cabinet's orientation (e.g., if it gets moved slightly by an 01302 // especially strong nudge) as well as any systematic drift in the 01303 // accelerometer measurement bias (e.g., from temperature changes). 01304 int iAccPrv_, nAccPrv_; 01305 static const int maxAccPrv = 5; 01306 AccHist accPrv_[maxAccPrv]; 01307 01308 // interurupt pin name 01309 PinName irqPin_; 01310 01311 // interrupt router 01312 InterruptIn intIn_; 01313 }; 01314 01315 01316 // --------------------------------------------------------------------------- 01317 // 01318 // Clear the I2C bus for the MMA8451Q. This seems necessary some of the time 01319 // for reasons that aren't clear to me. Doing a hard power cycle has the same 01320 // effect, but when we do a soft reset, the hardware sometimes seems to leave 01321 // the MMA's SDA line stuck low. Forcing a series of 9 clock pulses through 01322 // the SCL line is supposed to clear this condition. I'm not convinced this 01323 // actually works with the way this component is wired on the KL25Z, but it 01324 // seems harmless, so we'll do it on reset in case it does some good. What 01325 // we really seem to need is a way to power cycle the MMA8451Q if it ever 01326 // gets stuck, but this is simply not possible in software on the KL25Z. 01327 // 01328 // If the accelerometer does get stuck, and a software reboot doesn't reset 01329 // it, the only workaround is to manually power cycle the whole KL25Z by 01330 // unplugging both of its USB connections. 01331 // 01332 void clear_i2c() 01333 { 01334 // assume a general-purpose output pin to the I2C clock 01335 DigitalOut scl(MMA8451_SCL_PIN); 01336 DigitalIn sda(MMA8451_SDA_PIN); 01337 01338 // clock the SCL 9 times 01339 for (int i = 0 ; i < 9 ; ++i) 01340 { 01341 scl = 1; 01342 wait_us(20); 01343 scl = 0; 01344 wait_us(20); 01345 } 01346 } 01347 01348 // --------------------------------------------------------------------------- 01349 // 01350 // Include the appropriate plunger sensor definition. This will define a 01351 // class called PlungerSensor, with a standard interface that we use in 01352 // the main loop below. This is *kind of* like a virtual class interface, 01353 // but it actually defines the methods statically, which is a little more 01354 // efficient at run-time. There's no need for a true virtual interface 01355 // because we don't need to be able to change sensor types on the fly. 01356 // 01357 01358 #if defined(ENABLE_CCD_SENSOR) 01359 #include "ccdSensor.h" 01360 #elif defined(ENABLE_POT_SENSOR) 01361 #include "potSensor.h" 01362 #else 01363 #include "nullSensor.h" 01364 #endif 01365 01366 01367 // --------------------------------------------------------------------------- 01368 // 01369 // Non-volatile memory (NVM) 01370 // 01371 01372 // Structure defining our NVM storage layout. We store a small 01373 // amount of persistent data in flash memory to retain calibration 01374 // data when powered off. 01375 struct NVM 01376 { 01377 // checksum - we use this to determine if the flash record 01378 // has been properly initialized 01379 uint32_t checksum; 01380 01381 // signature and version, to verify that we saved the config 01382 // data to flash on a past run (as opposed to uninitialized 01383 // data from a firmware update) 01384 static const uint32_t SIGNATURE = 0x4D4A522A; 01385 static const uint16_t VERSION = 0x0003; 01386 01387 // Is the data structure valid? We test the signature and 01388 // checksum to determine if we've been properly stored. 01389 int valid() const 01390 { 01391 return (d.sig == SIGNATURE 01392 && d.vsn == VERSION 01393 && d.sz == sizeof(NVM) 01394 && checksum == CRC32(&d, sizeof(d))); 01395 } 01396 01397 // save to non-volatile memory 01398 void save(FreescaleIAP &iap, int addr) 01399 { 01400 // update the checksum and structure size 01401 d.sig = SIGNATURE; 01402 d.vsn = VERSION; 01403 d.sz = sizeof(NVM); 01404 checksum = CRC32(&d, sizeof(d)); 01405 01406 // erase the sector 01407 iap.erase_sector(addr); 01408 01409 // save the data 01410 iap.program_flash(addr, this, sizeof(*this)); 01411 } 01412 01413 // reset calibration data for calibration mode 01414 void resetPlunger() 01415 { 01416 // set extremes for the calibration data 01417 d.plungerMax = 0; 01418 d.plungerZero = npix; 01419 d.plungerMin = npix; 01420 } 01421 01422 // stored data (excluding the checksum) 01423 struct 01424 { 01425 // Signature, structure version, and structure size - further verification 01426 // that we have valid initialized data. The size is a simple proxy for a 01427 // structure version, as the most common type of change to the structure as 01428 // the software evolves will be the addition of new elements. We also 01429 // provide an explicit version number that we can update manually if we 01430 // make any changes that don't affect the structure size but would affect 01431 // compatibility with a saved record (e.g., swapping two existing elements). 01432 uint32_t sig; 01433 uint16_t vsn; 01434 int sz; 01435 01436 // has the plunger been manually calibrated? 01437 int plungerCal; 01438 01439 // Plunger calibration min, zero, and max. The zero point is the 01440 // rest position (aka park position), where it's in equilibrium between 01441 // the main spring and the barrel spring. It can travel a small distance 01442 // forward of the rest position, because the barrel spring can be 01443 // compressed by the user pushing on the plunger or by the momentum 01444 // of a release motion. The minimum is the maximum forward point where 01445 // the barrel spring can't be compressed any further. 01446 int plungerMin; 01447 int plungerZero; 01448 int plungerMax; 01449 01450 // is the plunger sensor enabled? 01451 int plungerEnabled; 01452 01453 // LedWiz unit number 01454 uint8_t ledWizUnitNo; 01455 } d; 01456 }; 01457 01458 // --------------------------------------------------------------------------- 01459 // 01460 // Simple binary (on/off) input debouncer. Requires an input to be stable 01461 // for a given interval before allowing an update. 01462 // 01463 class Debouncer 01464 { 01465 public: 01466 Debouncer(bool initVal, float tmin) 01467 { 01468 t.start(); 01469 this->stable = this->prv = initVal; 01470 this->tmin = tmin; 01471 } 01472 01473 // Get the current stable value 01474 bool val() const { return stable; } 01475 01476 // Apply a new sample. This tells us the new raw reading from the 01477 // input device. 01478 void sampleIn(bool val) 01479 { 01480 // If the new raw reading is different from the previous 01481 // raw reading, we've detected an edge - start the clock 01482 // on the sample reader. 01483 if (val != prv) 01484 { 01485 // we have an edge - reset the sample clock 01486 t.reset(); 01487 01488 // this is now the previous raw sample for nxt time 01489 prv = val; 01490 } 01491 else if (val != stable) 01492 { 01493 // The new raw sample is the same as the last raw sample, 01494 // and different from the stable value. This means that 01495 // the sample value has been the same for the time currently 01496 // indicated by our timer. If enough time has elapsed to 01497 // consider the value stable, apply the new value. 01498 if (t.read() > tmin) 01499 stable = val; 01500 } 01501 } 01502 01503 private: 01504 // current stable value 01505 bool stable; 01506 01507 // last raw sample value 01508 bool prv; 01509 01510 // elapsed time since last raw input change 01511 Timer t; 01512 01513 // Minimum time interval for stability, in seconds. Input readings 01514 // must be stable for this long before the stable value is updated. 01515 float tmin; 01516 }; 01517 01518 01519 // --------------------------------------------------------------------------- 01520 // 01521 // Turn off all outputs and restore everything to the default LedWiz 01522 // state. This sets outputs #1-32 to LedWiz profile value 48 (full 01523 // brightness) and switch state Off, sets all extended outputs (#33 01524 // and above) to zero brightness, and sets the LedWiz flash rate to 2. 01525 // This effectively restores the power-on conditions. 01526 // 01527 void allOutputsOff() 01528 { 01529 // reset all LedWiz outputs to OFF/48 01530 for (int i = 0 ; i < 32 ; ++i) 01531 { 01532 outLevel[i] = 0; 01533 wizOn[i] = 0; 01534 wizVal[i] = 48; 01535 lwPin[i]->set(0); 01536 } 01537 01538 // reset all extended outputs (ports >32) to full off (brightness 0) 01539 for (int i = 32 ; i < numOutputs ; ++i) 01540 { 01541 outLevel[i] = 0; 01542 lwPin[i]->set(0); 01543 } 01544 01545 // restore default LedWiz flash rate 01546 wizSpeed = 2; 01547 01548 // flush changes to hc595, if applicable 01549 hc595.update(); 01550 } 01551 01552 // --------------------------------------------------------------------------- 01553 // 01554 // TV ON timer. If this feature is enabled, we toggle a TV power switch 01555 // relay (connected to a GPIO pin) to turn on the cab's TV monitors shortly 01556 // after the system is powered. This is useful for TVs that don't remember 01557 // their power state and don't turn back on automatically after being 01558 // unplugged and plugged in again. This feature requires external 01559 // circuitry, which is built in to the expansion board and can also be 01560 // built separately - see the Build Guide for the circuit plan. 01561 // 01562 // Theory of operation: to use this feature, the cabinet must have a 01563 // secondary PC-style power supply (PSU2) for the feedback devices, and 01564 // this secondary supply must be plugged in to the same power strip or 01565 // switched outlet that controls power to the TVs. This lets us use PSU2 01566 // as a proxy for the TV power state - when PSU2 is on, the TV outlet is 01567 // powered, and when PSU2 is off, the TV outlet is off. We use a little 01568 // latch circuit powered by PSU2 to monitor the status. The latch has a 01569 // current state, ON or OFF, that we can read via a GPIO input pin, and 01570 // we can set the state to ON by pulsing a separate GPIO output pin. As 01571 // long as PSU2 is powered off, the latch stays in the OFF state, even if 01572 // we try to set it by pulsing the SET pin. When PSU2 is turned on after 01573 // being off, the latch starts receiving power but stays in the OFF state, 01574 // since this is the initial condition when the power first comes on. So 01575 // if our latch state pin is reading OFF, we know that PSU2 is either off 01576 // now or *was* off some time since we last checked. We use a timer to 01577 // check the state periodically. Each time we see the state is OFF, we 01578 // try pulsing the SET pin. If the state still reads as OFF, we know 01579 // that PSU2 is currently off; if the state changes to ON, though, we 01580 // know that PSU2 has gone from OFF to ON some time between now and the 01581 // previous check. When we see this condition, we start a countdown 01582 // timer, and pulse the TV switch relay when the countdown ends. 01583 // 01584 // This scheme might seem a little convoluted, but it neatly handles 01585 // all of the different cases that can occur: 01586 // 01587 // - Most cabinets systems are set up with "soft" PC power switches, 01588 // so that the PC goes into "Soft Off" mode (ACPI state S5, in Windows 01589 // parlance) when the user turns off the cabinet. In this state, the 01590 // motherboard supplies power to USB devices, so the KL25Z continues 01591 // running without interruption. The latch system lets us monitor 01592 // the power state even when we're never rebooted, since the latch 01593 // will turn off when PSU2 is off regardless of what the KL25Z is doing. 01594 // 01595 // - Some cabinet builders might prefer to use "hard" power switches, 01596 // cutting all power to the cabinet, including the PC motherboard (and 01597 // thus the KL25Z) every time the machine is turned off. This also 01598 // applies to the "soft" switch case above when the cabinet is unplugged, 01599 // a power outage occurs, etc. In these cases, the KL25Z will do a cold 01600 // boot when the PC is turned on. We don't know whether the KL25Z 01601 // will power up before or after PSU2, so it's not good enough to 01602 // observe the *current* state of PSU2 when we first check - if PSU2 01603 // were to come on first, checking the current state alone would fool 01604 // us into thinking that no action is required, because we would never 01605 // have known that PSU2 was ever off. The latch handles this case by 01606 // letting us see that PSU2 *was* off before we checked. 01607 // 01608 // - If the KL25Z is rebooted while the main system is running, or the 01609 // KL25Z is unplugged and plugged back in, we will correctly leave the 01610 // TVs as they are. The latch state is independent of the KL25Z's 01611 // power or software state, so it's won't affect the latch state when 01612 // the KL25Z is unplugged or rebooted; when we boot, we'll see that 01613 // the latch is already on and that we don't have to turn on the TVs. 01614 // This is important because TV ON buttons are usually on/off toggles, 01615 // so we don't want to push the button on a TV that's already on. 01616 // 01617 // 01618 #ifdef ENABLE_TV_TIMER 01619 01620 // Current PSU2 state: 01621 // 1 -> default: latch was on at last check, or we haven't checked yet 01622 // 2 -> latch was off at last check, SET pulsed high 01623 // 3 -> SET pulsed low, ready to check status 01624 // 4 -> TV timer countdown in progress 01625 // 5 -> TV relay on 01626 // 01627 int psu2_state = 1; 01628 DigitalIn psu2_status_sense(PSU2_STATUS_SENSE); 01629 DigitalOut psu2_status_set(PSU2_STATUS_SET); 01630 DigitalOut tv_relay(TV_RELAY_PIN); 01631 Timer tv_timer; 01632 void TVTimerInt() 01633 { 01634 // Check our internal state 01635 switch (psu2_state) 01636 { 01637 case 1: 01638 // Default state. This means that the latch was on last 01639 // time we checked or that this is the first check. In 01640 // either case, if the latch is off, switch to state 2 and 01641 // try pulsing the latch. Next time we check, if the latch 01642 // stuck, it means that PSU2 is now on after being off. 01643 if (!psu2_status_sense) 01644 { 01645 // switch to OFF state 01646 psu2_state = 2; 01647 01648 // try setting the latch 01649 psu2_status_set = 1; 01650 } 01651 break; 01652 01653 case 2: 01654 // PSU2 was off last time we checked, and we tried setting 01655 // the latch. Drop the SET signal and go to CHECK state. 01656 psu2_status_set = 0; 01657 psu2_state = 3; 01658 break; 01659 01660 case 3: 01661 // CHECK state: we pulsed SET, and we're now ready to see 01662 // if that stuck. If the latch is now on, PSU2 has transitioned 01663 // from OFF to ON, so start the TV countdown. If the latch is 01664 // off, our SET command didn't stick, so PSU2 is still off. 01665 if (psu2_status_sense) 01666 { 01667 // The latch stuck, so PSU2 has transitioned from OFF 01668 // to ON. Start the TV countdown timer. 01669 tv_timer.reset(); 01670 tv_timer.start(); 01671 psu2_state = 4; 01672 } 01673 else 01674 { 01675 // The latch didn't stick, so PSU2 was still off at 01676 // our last check. Try pulsing it again in case PSU2 01677 // was turned on since the last check. 01678 psu2_status_set = 1; 01679 psu2_state = 2; 01680 } 01681 break; 01682 01683 case 4: 01684 // TV timer countdown in progress. If we've reached the 01685 // delay time, pulse the relay. 01686 if (tv_timer.read() >= TV_DELAY_TIME) 01687 { 01688 // turn on the relay for one timer interval 01689 tv_relay = 1; 01690 psu2_state = 5; 01691 } 01692 break; 01693 01694 case 5: 01695 // TV timer relay on. We pulse this for one interval, so 01696 // it's now time to turn it off and return to the default state. 01697 tv_relay = 0; 01698 psu2_state = 1; 01699 break; 01700 } 01701 } 01702 01703 Ticker tv_ticker; 01704 void startTVTimer() 01705 { 01706 // Set up our time routine to run every 1/4 second. 01707 tv_ticker.attach(&TVTimerInt, 0.25); 01708 } 01709 01710 01711 #else // ENABLE_TV_TIMER 01712 // 01713 // TV timer not used - just provide a dummy startup function 01714 void startTVTimer() { } 01715 // 01716 #endif // ENABLE_TV_TIMER 01717 01718 01719 // --------------------------------------------------------------------------- 01720 // 01721 // Main program loop. This is invoked on startup and runs forever. Our 01722 // main work is to read our devices (the accelerometer and the CCD), process 01723 // the readings into nudge and plunger position data, and send the results 01724 // to the host computer via the USB joystick interface. We also monitor 01725 // the USB connection for incoming LedWiz commands and process those into 01726 // port outputs. 01727 // 01728 int main(void) 01729 { 01730 // turn off our on-board indicator LED 01731 ledR = 1; 01732 ledG = 1; 01733 ledB = 1; 01734 01735 // start the TV timer, if applicable 01736 startTVTimer(); 01737 01738 // we're not connected/awake yet 01739 bool connected = false; 01740 time_t connectChangeTime = time(0); 01741 01742 // initialize the LedWiz ports 01743 initLwOut(); 01744 01745 // initialize the button input ports 01746 initButtons(); 01747 01748 // start the TLC5940 clock, if present 01749 tlc5940.start(); 01750 01751 // enable the 74HC595 chips, if present 01752 hc595.init(); 01753 hc595.update(); 01754 01755 // we don't need a reset yet 01756 bool needReset = false; 01757 01758 // clear the I2C bus for the accelerometer 01759 clear_i2c(); 01760 01761 // set up a flash memory controller 01762 FreescaleIAP iap; 01763 01764 // use the last sector of flash for our non-volatile memory structure 01765 int flash_addr = (iap.flash_size() - SECTOR_SIZE); 01766 NVM *flash = (NVM *)flash_addr; 01767 NVM cfg; 01768 01769 // if the flash is valid, load it; otherwise initialize to defaults 01770 if (flash->valid()) { 01771 memcpy(&cfg, flash, sizeof(cfg)); 01772 printf("Flash restored: plunger cal=%d, min=%d, zero=%d, max=%d\r\n", 01773 cfg.d.plungerCal, cfg.d.plungerMin, cfg.d.plungerZero, cfg.d.plungerMax); 01774 } 01775 else { 01776 printf("Factory reset\r\n"); 01777 cfg.d.plungerCal = 0; 01778 cfg.d.plungerMin = 0; // assume we can go all the way forward... 01779 cfg.d.plungerMax = npix; // ...and all the way back 01780 cfg.d.plungerZero = npix/6; // the rest position is usually around 1/2" back 01781 cfg.d.ledWizUnitNo = DEFAULT_LEDWIZ_UNIT_NUMBER - 1; // unit numbering starts from 0 internally 01782 cfg.d.plungerEnabled = PLUNGER_CODE_ENABLED; 01783 } 01784 01785 // Create the joystick USB client. Note that we use the LedWiz unit 01786 // number from the saved configuration. 01787 MyUSBJoystick js( 01788 USB_VENDOR_ID, 01789 MAKE_USB_PRODUCT_ID(USB_VENDOR_ID, USB_PRODUCT_ID, cfg.d.ledWizUnitNo), 01790 USB_VERSION_NO); 01791 01792 // last report timer - we use this to throttle reports, since VP 01793 // doesn't want to hear from us more than about every 10ms 01794 Timer reportTimer; 01795 reportTimer.start(); 01796 01797 // initialize the calibration buttons, if present 01798 DigitalIn *calBtn = (CAL_BUTTON_PIN == NC ? 0 : new DigitalIn(CAL_BUTTON_PIN)); 01799 DigitalOut *calBtnLed = (CAL_BUTTON_LED == NC ? 0 : new DigitalOut(CAL_BUTTON_LED)); 01800 01801 // plunger calibration button debounce timer 01802 Timer calBtnTimer; 01803 calBtnTimer.start(); 01804 int calBtnLit = false; 01805 01806 // Calibration button state: 01807 // 0 = not pushed 01808 // 1 = pushed, not yet debounced 01809 // 2 = pushed, debounced, waiting for hold time 01810 // 3 = pushed, hold time completed - in calibration mode 01811 int calBtnState = 0; 01812 01813 // set up a timer for our heartbeat indicator 01814 Timer hbTimer; 01815 hbTimer.start(); 01816 int hb = 0; 01817 uint16_t hbcnt = 0; 01818 01819 // set a timer for accelerometer auto-centering 01820 Timer acTimer; 01821 acTimer.start(); 01822 01823 // create the accelerometer object 01824 Accel accel(MMA8451_SCL_PIN, MMA8451_SDA_PIN, MMA8451_I2C_ADDRESS, MMA8451_INT_PIN); 01825 01826 #ifdef ENABLE_JOYSTICK 01827 // last accelerometer report, in joystick units (we report the nudge 01828 // acceleration via the joystick x & y axes, per the VP convention) 01829 int x = 0, y = 0; 01830 01831 // flag: send a pixel dump after the next read 01832 bool reportPix = false; 01833 #endif 01834 01835 // create our plunger sensor object 01836 PlungerSensor plungerSensor; 01837 01838 // last plunger report position, in 'npix' normalized pixel units 01839 int pos = 0; 01840 01841 // last plunger report, in joystick units (we report the plunger as the 01842 // "z" axis of the joystick, per the VP convention) 01843 int z = 0; 01844 01845 // most recent prior plunger readings, for tracking release events(z0 is 01846 // reading just before the last one we reported, z1 is the one before that, 01847 // z2 the next before that) 01848 int z0 = 0, z1 = 0, z2 = 0; 01849 01850 // Simulated "bounce" position when firing. We model the bounce off of 01851 // the barrel spring when the plunger is released as proportional to the 01852 // distance it was retracted just before being released. 01853 int zBounce = 0; 01854 01855 // Simulated Launch Ball button state. If a "ZB Launch Ball" port is 01856 // defined for our LedWiz port mapping, any time that port is turned ON, 01857 // we'll simulate pushing the Launch Ball button if the player pulls 01858 // back and releases the plunger, or simply pushes on the plunger from 01859 // the rest position. This allows the plunger to be used in lieu of a 01860 // physical Launch Ball button for tables that don't have plungers. 01861 // 01862 // States: 01863 // 0 = default 01864 // 1 = cocked (plunger has been pulled back about 1" from state 0) 01865 // 2 = uncocked (plunger is pulled back less than 1" from state 1) 01866 // 3 = launching, plunger is forward beyond park position 01867 // 4 = launching, plunger is behind park position 01868 // 5 = pressed and holding (plunger has been pressed forward beyond 01869 // the park position from state 0) 01870 int lbState = 0; 01871 01872 // Time since last lbState transition. Some of the states are time- 01873 // sensitive. In the "uncocked" state, we'll return to state 0 if 01874 // we remain in this state for more than a few milliseconds, since 01875 // it indicates that the plunger is being slowly returned to rest 01876 // rather than released. In the "launching" state, we need to release 01877 // the Launch Ball button after a moment, and we need to wait for 01878 // the plunger to come to rest before returning to state 0. 01879 Timer lbTimer; 01880 lbTimer.start(); 01881 01882 // Launch Ball simulated push timer. We start this when we simulate 01883 // the button push, and turn off the simulated button when enough time 01884 // has elapsed. 01885 Timer lbBtnTimer; 01886 01887 // Simulated button states. This is a vector of button states 01888 // for the simulated buttons. We combine this with the physical 01889 // button states on each USB joystick report, so we will report 01890 // a button as pressed if either the physical button is being pressed 01891 // or we're simulating a press on the button. This is used for the 01892 // simulated Launch Ball button. 01893 uint32_t simButtons = 0; 01894 01895 // Firing in progress: we set this when we detect the start of rapid 01896 // plunger movement from a retracted position towards the rest position. 01897 // 01898 // When we detect a firing event, we send VP a series of synthetic 01899 // reports simulating the idealized plunger motion. The actual physical 01900 // motion is much too fast to report to VP; in the time between two USB 01901 // reports, the plunger can shoot all the way forward, rebound off of 01902 // the barrel spring, bounce back part way, and bounce forward again, 01903 // or even do all of this more than once. This means that sampling the 01904 // physical motion at the USB report rate would create a misleading 01905 // picture of the plunger motion, since our samples would catch the 01906 // plunger at random points in this oscillating motion. From the 01907 // user's perspective, the physical action that occurred is simply that 01908 // the plunger was released from a particular distance, so it's this 01909 // high-level event that we want to convey to VP. To do this, we 01910 // synthesize a series of reports to convey an idealized version of 01911 // the release motion that's perfectly synchronized to the VP reports. 01912 // Essentially we pretend that our USB position samples are exactly 01913 // aligned in time with (1) the point of retraction just before the 01914 // user released the plunger, (2) the point of maximum forward motion 01915 // just after the user released the plunger (the point of maximum 01916 // compression as the plunger bounces off of the barrel spring), and 01917 // (3) the plunger coming to rest at the park position. This series 01918 // of reports is synthetic in the sense that it's not what we actually 01919 // see on the CCD at the times of these reports - the true plunger 01920 // position is oscillating at high speed during this period. But at 01921 // the same time it conveys a more faithful picture of the true physical 01922 // motion to VP, and allows VP to reproduce the true physical motion 01923 // more faithfully in its simulation model, by correcting for the 01924 // relatively low sampling rate in the communication path between the 01925 // real plunger and VP's model plunger. 01926 // 01927 // If 'firing' is non-zero, it's the index of our current report in 01928 // the synthetic firing report series. 01929 int firing = 0; 01930 01931 // start the first CCD integration cycle 01932 plungerSensor.init(); 01933 01934 // Device status. We report this on each update so that the host config 01935 // tool can detect our current settings. This is a bit mask consisting 01936 // of these bits: 01937 // 0x0001 -> plunger sensor enabled 01938 // 0x8000 -> RESERVED - must always be zero 01939 // 01940 // Note that the high bit (0x8000) must always be 0, since we use that 01941 // to distinguish special request reply packets. 01942 uint16_t statusFlags = (cfg.d.plungerEnabled ? 0x01 : 0x00); 01943 01944 // we're all set up - now just loop, processing sensor reports and 01945 // host requests 01946 for (;;) 01947 { 01948 // Look for an incoming report. Process a few input reports in 01949 // a row, but stop after a few so that a barrage of inputs won't 01950 // starve our output event processing. Also, pause briefly between 01951 // reads; allowing reads to occur back-to-back seems to occasionally 01952 // stall the USB pipeline (for reasons unknown; I'd fix the underlying 01953 // problem if I knew what it was). 01954 HID_REPORT report; 01955 for (int rr = 0 ; rr < 4 && js.readNB(&report) ; ++rr, wait_ms(1)) 01956 { 01957 // all Led-Wiz reports are 8 bytes exactly 01958 if (report.length == 8) 01959 { 01960 // LedWiz commands come in two varieties: SBA and PBA. An 01961 // SBA is marked by the first byte having value 64 (0x40). In 01962 // the real LedWiz protocol, any other value in the first byte 01963 // means it's a PBA message. However, *valid* PBA messages 01964 // always have a first byte (and in fact all 8 bytes) in the 01965 // range 0-49 or 129-132. Anything else is invalid. We take 01966 // advantage of this to implement private protocol extensions. 01967 // So our full protocol is as follows: 01968 // 01969 // first byte = 01970 // 0-48 -> LWZ-PBA 01971 // 64 -> LWZ SBA 01972 // 65 -> private control message; second byte specifies subtype 01973 // 129-132 -> LWZ-PBA 01974 // 200-219 -> extended bank brightness set for outputs N to N+6, where 01975 // N is (first byte - 200)*7 01976 // other -> reserved for future use 01977 // 01978 uint8_t *data = report.data; 01979 if (data[0] == 64) 01980 { 01981 // LWZ-SBA - first four bytes are bit-packed on/off flags 01982 // for the outputs; 5th byte is the pulse speed (1-7) 01983 //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n", 01984 // data[1], data[2], data[3], data[4], data[5]); 01985 01986 // update all on/off states 01987 for (int i = 0, bit = 1, ri = 1 ; i < 32 ; ++i, bit <<= 1) 01988 { 01989 // figure the on/off state bit for this output 01990 if (bit == 0x100) { 01991 bit = 1; 01992 ++ri; 01993 } 01994 01995 // set the on/off state 01996 wizOn[i] = ((data[ri] & bit) != 0); 01997 01998 // If the wizVal setting is 255, it means that this 01999 // output was last set to a brightness value with the 02000 // extended protocol. Return it to LedWiz control by 02001 // rescaling the brightness setting to the LedWiz range 02002 // and updating wizVal with the result. If it's any 02003 // other value, it was previously set by a PBA message, 02004 // so simply retain the last setting - in the normal 02005 // LedWiz protocol, the "profile" (brightness) and on/off 02006 // states are independent, so an SBA just turns an output 02007 // on or off but retains its last brightness level. 02008 if (wizVal[i] == 255) 02009 wizVal[i] = (uint8_t)round(outLevel[i]*48); 02010 } 02011 02012 // set the flash speed - enforce the value range 1-7 02013 wizSpeed = data[5]; 02014 if (wizSpeed < 1) 02015 wizSpeed = 1; 02016 else if (wizSpeed > 7) 02017 wizSpeed = 7; 02018 02019 // update the physical outputs 02020 updateWizOuts(); 02021 hc595.update(); 02022 02023 // reset the PBA counter 02024 pbaIdx = 0; 02025 } 02026 else if (data[0] == 65) 02027 { 02028 // Private control message. This isn't an LedWiz message - it's 02029 // an extension for this device. 65 is an invalid PBA setting, 02030 // and isn't used for any other LedWiz message, so we appropriate 02031 // it for our own private use. The first byte specifies the 02032 // message type. 02033 if (data[1] == 1) 02034 { 02035 // 1 = Set Configuration: 02036 // data[2] = LedWiz unit number (0x00 to 0x0f) 02037 // data[3] = feature enable bit mask: 02038 // 0x01 = enable plunger sensor 02039 02040 // we'll need a reset if the LedWiz unit number is changing 02041 uint8_t newUnitNo = data[2] & 0x0f; 02042 needReset |= (newUnitNo != cfg.d.ledWizUnitNo); 02043 02044 // set the configuration parameters from the message 02045 cfg.d.ledWizUnitNo = newUnitNo; 02046 cfg.d.plungerEnabled = data[3] & 0x01; 02047 02048 // update the status flags 02049 statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01); 02050 02051 // if the ccd is no longer enabled, use 0 for z reports 02052 if (!cfg.d.plungerEnabled) 02053 z = 0; 02054 02055 // save the configuration 02056 cfg.save(iap, flash_addr); 02057 } 02058 #ifdef ENABLE_JOYSTICK 02059 else if (data[1] == 2) 02060 { 02061 // 2 = Calibrate plunger 02062 // (No parameters) 02063 02064 // enter calibration mode 02065 calBtnState = 3; 02066 calBtnTimer.reset(); 02067 cfg.resetPlunger(); 02068 } 02069 else if (data[1] == 3) 02070 { 02071 // 3 = pixel dump 02072 // (No parameters) 02073 reportPix = true; 02074 02075 // show purple until we finish sending the report 02076 ledR = 0; 02077 ledB = 0; 02078 ledG = 1; 02079 } 02080 else if (data[1] == 4) 02081 { 02082 // 4 = hardware configuration query 02083 // (No parameters) 02084 wait_ms(1); 02085 js.reportConfig(numOutputs, cfg.d.ledWizUnitNo); 02086 } 02087 else if (data[1] == 5) 02088 { 02089 // 5 = all outputs off, reset to LedWiz defaults 02090 allOutputsOff(); 02091 } 02092 #endif // ENABLE_JOYSTICK 02093 } 02094 else if (data[0] >= 200 && data[0] < 220) 02095 { 02096 // Extended protocol - banked brightness update. 02097 // data[0]-200 gives us the bank of 7 outputs we're setting: 02098 // 200 is outputs 0-6, 201 is outputs 7-13, 202 is 14-20, etc. 02099 // The remaining bytes are brightness levels, 0-255, for the 02100 // seven outputs in the selected bank. The LedWiz flashing 02101 // modes aren't accessible in this message type; we can only 02102 // set a fixed brightness, but in exchange we get 8-bit 02103 // resolution rather than the paltry 0-48 scale that the real 02104 // LedWiz uses. There's no separate on/off status for outputs 02105 // adjusted with this message type, either, as there would be 02106 // for a PBA message - setting a non-zero value immediately 02107 // turns the output, overriding the last SBA setting. 02108 // 02109 // For outputs 0-31, this overrides any previous PBA/SBA 02110 // settings for the port. Any subsequent PBA/SBA message will 02111 // in turn override the setting made here. It's simple - the 02112 // most recent message of either type takes precedence. For 02113 // outputs above the LedWiz range, PBA/SBA messages can't 02114 // address those ports anyway. 02115 int i0 = (data[0] - 200)*7; 02116 int i1 = i0 + 7 < numOutputs ? i0 + 7 : numOutputs; 02117 for (int i = i0 ; i < i1 ; ++i) 02118 { 02119 // set the brightness level for the output 02120 float b = data[i-i0+1]/255.0; 02121 outLevel[i] = b; 02122 02123 // if it's in the basic LedWiz output set, set the LedWiz 02124 // profile value to 255, which means "use outLevel" 02125 if (i < 32) 02126 wizVal[i] = 255; 02127 02128 // set the output 02129 lwPin[i]->set(b); 02130 } 02131 02132 // update 74HC595 outputs, if attached 02133 hc595.update(); 02134 } 02135 else 02136 { 02137 // Everything else is LWZ-PBA. This is a full "profile" 02138 // dump from the host for one bank of 8 outputs. Each 02139 // byte sets one output in the current bank. The current 02140 // bank is implied; the bank starts at 0 and is reset to 0 02141 // by any LWZ-SBA message, and is incremented to the next 02142 // bank by each LWZ-PBA message. Our variable pbaIdx keeps 02143 // track of our notion of the current bank. There's no direct 02144 // way for the host to select the bank; it just has to count 02145 // on us staying in sync. In practice, the host will always 02146 // send a full set of 4 PBA messages in a row to set all 32 02147 // outputs. 02148 // 02149 // Note that a PBA implicitly overrides our extended profile 02150 // messages (message prefix 200-219), because this sets the 02151 // wizVal[] entry for each output, and that takes precedence 02152 // over the extended protocol settings. 02153 // 02154 //printf("LWZ-PBA[%d] %02x %02x %02x %02x %02x %02x %02x %02x\r\n", 02155 // pbaIdx, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); 02156 02157 // Update all output profile settings 02158 for (int i = 0 ; i < 8 ; ++i) 02159 wizVal[pbaIdx + i] = data[i]; 02160 02161 // Update the physical LED state if this is the last bank. 02162 // Note that hosts always send a full set of four PBA 02163 // messages, so there's no need to do a physical update 02164 // until we've received the last bank's PBA message. 02165 if (pbaIdx == 24) 02166 { 02167 updateWizOuts(); 02168 hc595.update(); 02169 pbaIdx = 0; 02170 } 02171 else 02172 pbaIdx += 8; 02173 } 02174 } 02175 } 02176 02177 // check for plunger calibration 02178 if (calBtn != 0 && !calBtn->read()) 02179 { 02180 // check the state 02181 switch (calBtnState) 02182 { 02183 case 0: 02184 // button not yet pushed - start debouncing 02185 calBtnTimer.reset(); 02186 calBtnState = 1; 02187 break; 02188 02189 case 1: 02190 // pushed, not yet debounced - if the debounce time has 02191 // passed, start the hold period 02192 if (calBtnTimer.read_ms() > 50) 02193 calBtnState = 2; 02194 break; 02195 02196 case 2: 02197 // in the hold period - if the button has been held down 02198 // for the entire hold period, move to calibration mode 02199 if (calBtnTimer.read_ms() > 2050) 02200 { 02201 // enter calibration mode 02202 calBtnState = 3; 02203 calBtnTimer.reset(); 02204 cfg.resetPlunger(); 02205 } 02206 break; 02207 02208 case 3: 02209 // Already in calibration mode - pushing the button here 02210 // doesn't change the current state, but we won't leave this 02211 // state as long as it's held down. So nothing changes here. 02212 break; 02213 } 02214 } 02215 else 02216 { 02217 // Button released. If we're in calibration mode, and 02218 // the calibration time has elapsed, end the calibration 02219 // and save the results to flash. 02220 // 02221 // Otherwise, return to the base state without saving anything. 02222 // If the button is released before we make it to calibration 02223 // mode, it simply cancels the attempt. 02224 if (calBtnState == 3 && calBtnTimer.read_ms() > 15000) 02225 { 02226 // exit calibration mode 02227 calBtnState = 0; 02228 02229 // save the updated configuration 02230 cfg.d.plungerCal = 1; 02231 cfg.save(iap, flash_addr); 02232 } 02233 else if (calBtnState != 3) 02234 { 02235 // didn't make it to calibration mode - cancel the operation 02236 calBtnState = 0; 02237 } 02238 } 02239 02240 // light/flash the calibration button light, if applicable 02241 int newCalBtnLit = calBtnLit; 02242 switch (calBtnState) 02243 { 02244 case 2: 02245 // in the hold period - flash the light 02246 newCalBtnLit = ((calBtnTimer.read_ms()/250) & 1); 02247 break; 02248 02249 case 3: 02250 // calibration mode - show steady on 02251 newCalBtnLit = true; 02252 break; 02253 02254 default: 02255 // not calibrating/holding - show steady off 02256 newCalBtnLit = false; 02257 break; 02258 } 02259 02260 // light or flash the external calibration button LED, and 02261 // do the same with the on-board blue LED 02262 if (calBtnLit != newCalBtnLit) 02263 { 02264 calBtnLit = newCalBtnLit; 02265 if (calBtnLit) { 02266 if (calBtnLed != 0) 02267 calBtnLed->write(1); 02268 ledR = 1; 02269 ledG = 1; 02270 ledB = 0; 02271 } 02272 else { 02273 if (calBtnLed != 0) 02274 calBtnLed->write(0); 02275 ledR = 1; 02276 ledG = 1; 02277 ledB = 1; 02278 } 02279 } 02280 02281 // If the plunger is enabled, and we're not already in a firing event, 02282 // and the last plunger reading had the plunger pulled back at least 02283 // a bit, watch for plunger release events until it's time for our next 02284 // USB report. 02285 if (!firing && cfg.d.plungerEnabled && z >= JOYMAX/6) 02286 { 02287 // monitor the plunger until it's time for our next report 02288 while (reportTimer.read_ms() < 15) 02289 { 02290 // do a fast low-res scan; if it's at or past the zero point, 02291 // start a firing event 02292 if (plungerSensor.lowResScan() <= cfg.d.plungerZero) 02293 firing = 1; 02294 } 02295 } 02296 02297 // read the plunger sensor, if it's enabled 02298 if (cfg.d.plungerEnabled) 02299 { 02300 // start with the previous reading, in case we don't have a 02301 // clear result on this frame 02302 int znew = z; 02303 if (plungerSensor.highResScan(pos)) 02304 { 02305 // We got a new reading. If we're in calibration mode, use it 02306 // to figure the new calibration, otherwise adjust the new reading 02307 // for the established calibration. 02308 if (calBtnState == 3) 02309 { 02310 // Calibration mode. If this reading is outside of the current 02311 // calibration bounds, expand the bounds. 02312 if (pos < cfg.d.plungerMin) 02313 cfg.d.plungerMin = pos; 02314 if (pos < cfg.d.plungerZero) 02315 cfg.d.plungerZero = pos; 02316 if (pos > cfg.d.plungerMax) 02317 cfg.d.plungerMax = pos; 02318 02319 // normalize to the full physical range while calibrating 02320 znew = int(round(float(pos)/npix * JOYMAX)); 02321 } 02322 else 02323 { 02324 // Not in calibration mode, so normalize the new reading to the 02325 // established calibration range. 02326 // 02327 // Note that negative values are allowed. Zero represents the 02328 // "park" position, where the plunger sits when at rest. A mechanical 02329 // plunger has a small amount of travel in the "push" direction, 02330 // since the barrel spring can be compressed slightly. Negative 02331 // values represent travel in the push direction. 02332 if (pos > cfg.d.plungerMax) 02333 pos = cfg.d.plungerMax; 02334 znew = int(round(float(pos - cfg.d.plungerZero) 02335 / (cfg.d.plungerMax - cfg.d.plungerZero + 1) * JOYMAX)); 02336 } 02337 } 02338 02339 // If we're not already in a firing event, check to see if the 02340 // new position is forward of the last report. If it is, a firing 02341 // event might have started during the high-res scan. This might 02342 // seem unlikely given that the scan only takes about 5ms, but that 02343 // 5ms represents about 25-30% of our total time between reports, 02344 // there's about a 1 in 4 chance that a release starts during a 02345 // scan. 02346 if (!firing && z0 > 0 && znew < z0) 02347 { 02348 // The plunger has moved forward since the previous report. 02349 // Watch it for a few more ms to see if we can get a stable 02350 // new position. 02351 int pos0 = plungerSensor.lowResScan(); 02352 int pos1 = pos0; 02353 Timer tw; 02354 tw.start(); 02355 while (tw.read_ms() < 6) 02356 { 02357 // read the new position 02358 int pos2 = plungerSensor.lowResScan(); 02359 02360 // If it's stable over consecutive readings, stop looping. 02361 // (Count it as stable if the position is within about 1/8". 02362 // pos1 and pos2 are reported in pixels, so they range from 02363 // 0 to npix. The overall travel of a standard plunger is 02364 // about 3.2", so we have (npix/3.2) pixels per inch, hence 02365 // 1/8" is (npix/3.2)*(1/8) pixels.) 02366 if (abs(pos2 - pos1) < int(npix/(3.2*8))) 02367 break; 02368 02369 // If we've crossed the rest position, and we've moved by 02370 // a minimum distance from where we starting this loop, begin 02371 // a firing event. (We require a minimum distance to prevent 02372 // spurious firing from random analog noise in the readings 02373 // when the plunger is actually just sitting still at the 02374 // rest position. If it's at rest, it's normal to see small 02375 // random fluctuations in the analog reading +/- 1% or so 02376 // from the 0 point, especially with a sensor like a 02377 // potentionemeter that reports the position as a single 02378 // analog voltage.) Note that we compare the latest reading 02379 // to the first reading of the loop - we don't require the 02380 // threshold motion over consecutive readings, but any time 02381 // over the stability wait loop. 02382 if (pos1 < cfg.d.plungerZero 02383 && abs(pos2 - pos0) > int(npix/(3.2*8))) 02384 { 02385 firing = 1; 02386 break; 02387 } 02388 02389 // the new reading is now the prior reading 02390 pos1 = pos2; 02391 } 02392 } 02393 02394 // Check for a simulated Launch Ball button press, if enabled 02395 if (ZBLaunchBallPort != 0) 02396 { 02397 const int cockThreshold = JOYMAX/3; 02398 const int pushThreshold = int(-JOYMAX/3 * LaunchBallPushDistance); 02399 int newState = lbState; 02400 switch (lbState) 02401 { 02402 case 0: 02403 // Base state. If the plunger is pulled back by an inch 02404 // or more, go to "cocked" state. If the plunger is pushed 02405 // forward by 1/4" or more, go to "pressed" state. 02406 if (znew >= cockThreshold) 02407 newState = 1; 02408 else if (znew <= pushThreshold) 02409 newState = 5; 02410 break; 02411 02412 case 1: 02413 // Cocked state. If a firing event is now in progress, 02414 // go to "launch" state. Otherwise, if the plunger is less 02415 // than 1" retracted, go to "uncocked" state - the player 02416 // might be slowly returning the plunger to rest so as not 02417 // to trigger a launch. 02418 if (firing || znew <= 0) 02419 newState = 3; 02420 else if (znew < cockThreshold) 02421 newState = 2; 02422 break; 02423 02424 case 2: 02425 // Uncocked state. If the plunger is more than an inch 02426 // retracted, return to cocked state. If we've been in 02427 // the uncocked state for more than half a second, return 02428 // to the base state. This allows the user to return the 02429 // plunger to rest without triggering a launch, by moving 02430 // it at manual speed to the rest position rather than 02431 // releasing it. 02432 if (znew >= cockThreshold) 02433 newState = 1; 02434 else if (lbTimer.read_ms() > 500) 02435 newState = 0; 02436 break; 02437 02438 case 3: 02439 // Launch state. If the plunger is no longer pushed 02440 // forward, switch to launch rest state. 02441 if (znew >= 0) 02442 newState = 4; 02443 break; 02444 02445 case 4: 02446 // Launch rest state. If the plunger is pushed forward 02447 // again, switch back to launch state. If not, and we've 02448 // been in this state for at least 200ms, return to the 02449 // default state. 02450 if (znew <= pushThreshold) 02451 newState = 3; 02452 else if (lbTimer.read_ms() > 200) 02453 newState = 0; 02454 break; 02455 02456 case 5: 02457 // Press-and-Hold state. If the plunger is no longer pushed 02458 // forward, AND it's been at least 50ms since we generated 02459 // the simulated Launch Ball button press, return to the base 02460 // state. The minimum time is to ensure that VP has a chance 02461 // to see the button press and to avoid transient key bounce 02462 // effects when the plunger position is right on the threshold. 02463 if (znew > pushThreshold && lbTimer.read_ms() > 50) 02464 newState = 0; 02465 break; 02466 } 02467 02468 // change states if desired 02469 const uint32_t lbButtonBit = (1 << (LaunchBallButton - 1)); 02470 if (newState != lbState) 02471 { 02472 // If we're entering Launch state OR we're entering the 02473 // Press-and-Hold state, AND the ZB Launch Ball LedWiz signal 02474 // is turned on, simulate a Launch Ball button press. 02475 if (((newState == 3 && lbState != 4) || newState == 5) 02476 && wizOn[ZBLaunchBallPort-1]) 02477 { 02478 lbBtnTimer.reset(); 02479 lbBtnTimer.start(); 02480 simButtons |= lbButtonBit; 02481 } 02482 02483 // if we're switching to state 0, release the button 02484 if (newState == 0) 02485 simButtons &= ~(1 << (LaunchBallButton - 1)); 02486 02487 // switch to the new state 02488 lbState = newState; 02489 02490 // start timing in the new state 02491 lbTimer.reset(); 02492 } 02493 02494 // If the Launch Ball button press is in effect, but the 02495 // ZB Launch Ball LedWiz signal is no longer turned on, turn 02496 // off the button. 02497 // 02498 // If we're in one of the Launch states (state #3 or #4), 02499 // and the button has been on for long enough, turn it off. 02500 // The Launch mode is triggered by a pull-and-release gesture. 02501 // From the user's perspective, this is just a single gesture 02502 // that should trigger just one momentary press on the Launch 02503 // Ball button. Physically, though, the plunger usually 02504 // bounces back and forth for 500ms or so before coming to 02505 // rest after this gesture. That's what the whole state 02506 // #3-#4 business is all about - we stay in this pair of 02507 // states until the plunger comes to rest. As long as we're 02508 // in these states, we won't send duplicate button presses. 02509 // But we also don't want the one button press to continue 02510 // the whole time, so we'll time it out now. 02511 // 02512 // (This could be written as one big 'if' condition, but 02513 // I'm breaking it out verbosely like this to make it easier 02514 // for human readers such as myself to comprehend the logic.) 02515 if ((simButtons & lbButtonBit) != 0) 02516 { 02517 int turnOff = false; 02518 02519 // turn it off if the ZB Launch Ball signal is off 02520 if (!wizOn[ZBLaunchBallPort-1]) 02521 turnOff = true; 02522 02523 // also turn it off if we're in state 3 or 4 ("Launch"), 02524 // and the button has been on long enough 02525 if ((lbState == 3 || lbState == 4) && lbBtnTimer.read_ms() > 250) 02526 turnOff = true; 02527 02528 // if we decided to turn off the button, do so 02529 if (turnOff) 02530 { 02531 lbBtnTimer.stop(); 02532 simButtons &= ~lbButtonBit; 02533 } 02534 } 02535 } 02536 02537 // If a firing event is in progress, generate synthetic reports to 02538 // describe an idealized version of the plunger motion to VP rather 02539 // than reporting the actual physical plunger position. 02540 // 02541 // We use the synthetic reports during a release event because the 02542 // physical plunger motion when released is too fast for VP to track. 02543 // VP only syncs its internal physics model with the outside world 02544 // about every 10ms. In that amount of time, the plunger moves 02545 // fast enough when released that it can shoot all the way forward, 02546 // bounce off of the barrel spring, and rebound part of the way 02547 // back. The result is the classic analog-to-digital problem of 02548 // sample aliasing. If we happen to time our sample during the 02549 // release motion so that we catch the plunger at the peak of a 02550 // bounce, the digital signal incorrectly looks like the plunger 02551 // is moving slowly forward - VP thinks we went from fully 02552 // retracted to half retracted in the sample interval, whereas 02553 // we actually traveled all the way forward and half way back, 02554 // so the speed VP infers is about 1/3 of the actual speed. 02555 // 02556 // To correct this, we take advantage of our ability to sample 02557 // the CCD image several times in the course of a VP report. If 02558 // we catch the plunger near the origin after we've seen it 02559 // retracted, we go into Release Event mode. During this mode, 02560 // we stop reporting the true physical plunger position, and 02561 // instead report an idealized pattern: we report the plunger 02562 // immediately shooting forward to a position in front of the 02563 // park position that's in proportion to how far back the plunger 02564 // was just before the release, and we then report it stationary 02565 // at the park position. We continue to report the stationary 02566 // park position until the actual physical plunger motion has 02567 // stabilized on a new position. We then exit Release Event 02568 // mode and return to reporting the true physical position. 02569 if (firing) 02570 { 02571 // Firing in progress. Keep reporting the park position 02572 // until the physical plunger position comes to rest. 02573 const int restTol = JOYMAX/24; 02574 if (firing == 1) 02575 { 02576 // For the first couple of frames, show the plunger shooting 02577 // forward past the zero point, to simulate the momentum carrying 02578 // it forward to bounce off of the barrel spring. Show the 02579 // bounce as proportional to the distance it was retracted 02580 // in the prior report. 02581 z = zBounce = -z0/6; 02582 ++firing; 02583 } 02584 else if (firing == 2) 02585 { 02586 // second frame - keep the bounce a little longer 02587 z = zBounce; 02588 ++firing; 02589 } 02590 else if (firing > 4 02591 && abs(znew - z0) < restTol 02592 && abs(znew - z1) < restTol 02593 && abs(znew - z2) < restTol) 02594 { 02595 // The physical plunger has come to rest. Exit firing 02596 // mode and resume reporting the actual position. 02597 firing = false; 02598 z = znew; 02599 } 02600 else 02601 { 02602 // until the physical plunger comes to rest, simply 02603 // report the park position 02604 z = 0; 02605 ++firing; 02606 } 02607 } 02608 else 02609 { 02610 // not in firing mode - report the true physical position 02611 z = znew; 02612 } 02613 02614 // shift the new reading into the recent history buffer 02615 z2 = z1; 02616 z1 = z0; 02617 z0 = znew; 02618 } 02619 02620 // update the buttons 02621 uint32_t buttons = readButtons(); 02622 02623 #ifdef ENABLE_JOYSTICK 02624 // If it's been long enough since our last USB status report, 02625 // send the new report. We throttle the report rate because 02626 // it can overwhelm the PC side if we report too frequently. 02627 // VP only wants to sync with the real world in 10ms intervals, 02628 // so reporting more frequently only creates i/o overhead 02629 // without doing anything to improve the simulation. 02630 if (reportTimer.read_ms() > 15) 02631 { 02632 // read the accelerometer 02633 int xa, ya; 02634 accel.get(xa, ya); 02635 02636 // confine the results to our joystick axis range 02637 if (xa < -JOYMAX) xa = -JOYMAX; 02638 if (xa > JOYMAX) xa = JOYMAX; 02639 if (ya < -JOYMAX) ya = -JOYMAX; 02640 if (ya > JOYMAX) ya = JOYMAX; 02641 02642 // store the updated accelerometer coordinates 02643 x = xa; 02644 y = ya; 02645 02646 // Report the current plunger position UNLESS the ZB Launch Ball 02647 // signal is on, in which case just report a constant 0 value. 02648 // ZB Launch Ball turns off the plunger position because it 02649 // tells us that the table has a Launch Ball button instead of 02650 // a traditional plunger. 02651 int zrep = (ZBLaunchBallPort != 0 && wizOn[ZBLaunchBallPort-1] ? 0 : z); 02652 02653 // Send the status report. Note that we have to map the X and Y 02654 // axes from the accelerometer to match the Windows joystick axes. 02655 // The mapping is determined according to the mounting direction 02656 // set in config.h via the ORIENTATION_xxx macros. 02657 js.update(JOY_X(x,y), JOY_Y(x,y), zrep, buttons | simButtons, statusFlags); 02658 02659 // we've just started a new report interval, so reset the timer 02660 reportTimer.reset(); 02661 } 02662 02663 // If we're in pixel dump mode, report all pixel exposure values 02664 if (reportPix) 02665 { 02666 // send the report 02667 plungerSensor.sendExposureReport(js); 02668 02669 // we have satisfied this request 02670 reportPix = false; 02671 } 02672 02673 #else // ENABLE_JOYSTICK 02674 // We're a secondary controller, with no joystick reporting. Send 02675 // a generic status report to the host periodically for the sake of 02676 // the Windows config tool. 02677 if (reportTimer.read_ms() > 200) 02678 { 02679 js.updateStatus(0); 02680 } 02681 02682 #endif // ENABLE_JOYSTICK 02683 02684 #ifdef DEBUG_PRINTF 02685 if (x != 0 || y != 0) 02686 printf("%d,%d\r\n", x, y); 02687 #endif 02688 02689 // check for connection status changes 02690 int newConnected = js.isConnected() && !js.isSuspended(); 02691 if (newConnected != connected) 02692 { 02693 // give it a few seconds to stabilize 02694 time_t tc = time(0); 02695 if (tc - connectChangeTime > 3) 02696 { 02697 // note the new status 02698 connected = newConnected; 02699 connectChangeTime = tc; 02700 02701 // if we're no longer connected, turn off all outputs 02702 if (!connected) 02703 allOutputsOff(); 02704 } 02705 } 02706 02707 // provide a visual status indication on the on-board LED 02708 if (calBtnState < 2 && hbTimer.read_ms() > 1000) 02709 { 02710 if (!newConnected) 02711 { 02712 // suspended - turn off the LED 02713 ledR = 1; 02714 ledG = 1; 02715 ledB = 1; 02716 02717 // show a status flash every so often 02718 if (hbcnt % 3 == 0) 02719 { 02720 // disconnected = red/red flash; suspended = red 02721 for (int n = js.isConnected() ? 1 : 2 ; n > 0 ; --n) 02722 { 02723 ledR = 0; 02724 wait(0.05); 02725 ledR = 1; 02726 wait(0.25); 02727 } 02728 } 02729 } 02730 else if (needReset) 02731 { 02732 // connected, need to reset due to changes in config parameters - 02733 // flash red/green 02734 hb = !hb; 02735 ledR = (hb ? 0 : 1); 02736 ledG = (hb ? 1 : 0); 02737 ledB = 0; 02738 } 02739 else if (cfg.d.plungerEnabled && !cfg.d.plungerCal) 02740 { 02741 // connected, plunger calibration needed - flash yellow/green 02742 hb = !hb; 02743 ledR = (hb ? 0 : 1); 02744 ledG = 0; 02745 ledB = 1; 02746 } 02747 else 02748 { 02749 // connected - flash blue/green 02750 hb = !hb; 02751 ledR = 1; 02752 ledG = (hb ? 0 : 1); 02753 ledB = (hb ? 1 : 0); 02754 } 02755 02756 // reset the heartbeat timer 02757 hbTimer.reset(); 02758 ++hbcnt; 02759 } 02760 } 02761 }
Generated on Fri Jul 15 2022 08:43:32 by 1.7.2