An I/O controller for virtual pinball machines: accelerometer nudge sensing, analog plunger input, button input encoding, LedWiz compatible output controls, and more.

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

/media/uploads/mjr/pinscape_no_background_small_L7Miwr6.jpg

This is Version 2 of the Pinscape Controller, an I/O controller for virtual pinball machines. (You can find the old version 1 software here.) Pinscape is software for the KL25Z that turns the board into a full-featured I/O controller for virtual pinball, with support for accelerometer-based nudging, a mechanical plunger, button inputs, and feedback device control.

In case you haven't heard of the idea before, a "virtual pinball machine" is basically a video pinball simulator that's built into a real pinball machine body. A TV monitor goes in place of the pinball playfield, and a second TV goes in the backbox to show the backglass artwork. Some cabs also include a third monitor to simulate the DMD (Dot Matrix Display) used for scoring on 1990s machines, or even an original plasma DMD. A computer (usually a Windows PC) is hidden inside the cabinet, running pinball emulation software that displays a life-sized playfield on the main TV. The cabinet has all of the usual buttons, too, so it not only looks like the real thing, but plays like it too. That's a picture of my own machine to the right. On the outside, it's built exactly like a real arcade pinball machine, with the same overall dimensions and all of the standard pinball cabinet trim hardware.

It's possible to buy a pre-built virtual pinball machine, but it also makes a great DIY project. If you have some basic wood-working skills and know your way around PCs, you can build one from scratch. The computer part is just an ordinary Windows PC, and all of the pinball emulation can be built out of free, open-source software. In that spirit, the Pinscape Controller is an open-source software/hardware project that offers a no-compromises, all-in-one control center for all of the unique input/output needs of a virtual pinball cabinet. If you've been thinking about building one of these, but you're not sure how to connect a plunger, flipper buttons, lights, nudge sensor, and whatever else you can think of, this project might be just what you're looking for.

You can find much more information about DIY Pin Cab building in general in the Virtual Cabinet Forum on vpforums.org. Also visit my Pinscape Resources page for more about this project and other virtual pinball projects I'm working on.

Downloads

  • Pinscape Release Builds: This page has download links for all of the Pinscape software. To get started, install and run the Pinscape Config Tool on your Windows computer. It will lead you through the steps for installing the Pinscape firmware on the KL25Z.
  • Config Tool Source Code. The complete C# source code for the config tool. You don't need this to run the tool, but it's available if you want to customize anything or see how it works inside.

Documentation

The new Version 2 Build Guide is now complete! This new version aims to be a complete guide to building a virtual pinball machine, including not only the Pinscape elements but all of the basics, from sourcing parts to building all of the hardware.

You can also refer to the original Hardware Build Guide (PDF), but that's out of date now, since it refers to the old version 1 software, which was rather different (especially when it comes to configuration).

System Requirements

The new Config Tool requires a fairly up-to-date Microsoft .NET installation. If you use Windows Update to keep your system current, you should be fine. A modern version of Internet Explorer (IE) is required, even if you don't use it as your main browser, because the Config Tool uses some system components that Microsoft packages into the IE install set. I test with IE11, so that's known to work. IE8 doesn't work. IE9 and 10 are unknown at this point.

The Windows requirements are only for the config tool. The firmware doesn't care about anything on the Windows side, so if you can make do without the config tool, you can use almost any Windows setup.

Main Features

Plunger: The Pinscape Controller started out as a "mechanical plunger" controller: a device for attaching a real pinball plunger to the video game software so that you could launch the ball the natural way. This is still, of course, a central feature of the project. The software supports several types of sensors: a high-resolution optical sensor (which works by essentially taking pictures of the plunger as it moves); a slide potentiometer (which determines the position via the changing electrical resistance in the pot); a quadrature sensor (which counts bars printed on a special guide rail that it moves along); and an IR distance sensor (which determines the position by sending pulses of light at the plunger and measuring the round-trip travel time). The Build Guide explains how to set up each type of sensor.

Nudging: The KL25Z (the little microcontroller that the software runs on) has a built-in accelerometer. The Pinscape software uses it to sense when you nudge the cabinet, and feeds the acceleration data to the pinball software on the PC. This turns physical nudges into virtual English on the ball. The accelerometer is quite sensitive and accurate, so we can measure the difference between little bumps and hard shoves, and everything in between. The result is natural and immersive.

Buttons: You can wire real pinball buttons to the KL25Z, and the software will translate the buttons into PC input. You have the option to map each button to a keyboard key or joystick button. You can wire up your flipper buttons, Magna Save buttons, Start button, coin slots, operator buttons, and whatever else you need.

Feedback devices: You can also attach "feedback devices" to the KL25Z. Feedback devices are things that create tactile, sound, and lighting effects in sync with the game action. The most popular PC pinball emulators know how to address a wide variety of these devices, and know how to match them to on-screen action in each virtual table. You just need an I/O controller that translates commands from the PC into electrical signals that turn the devices on and off. The Pinscape Controller can do that for you.

Expansion Boards

There are two main ways to run the Pinscape Controller: standalone, or using the "expansion boards".

In the basic standalone setup, you just need the KL25Z, plus whatever buttons, sensors, and feedback devices you want to attach to it. This mode lets you take advantage of everything the software can do, but for some features, you'll have to build some ad hoc external circuitry to interface external devices with the KL25Z. The Build Guide has detailed plans for exactly what you need to build.

The other option is the Pinscape Expansion Boards. The expansion boards are a companion project, which is also totally free and open-source, that provides Printed Circuit Board (PCB) layouts that are designed specifically to work with the Pinscape software. The PCB designs are in the widely used EAGLE format, which many PCB manufacturers can turn directly into physical boards for you. The expansion boards organize all of the external connections more neatly than on the standalone KL25Z, and they add all of the interface circuitry needed for all of the advanced software functions. The big thing they bring to the table is lots of high-power outputs. The boards provide a modular system that lets you add boards to add more outputs. If you opt for the basic core setup, you'll have enough outputs for all of the toys in a really well-equipped cabinet. If your ambitions go beyond merely well-equipped and run to the ridiculously extravagant, just add an extra board or two. The modular design also means that you can add to the system over time.

Expansion Board project page

Update notes

If you have a Pinscape V1 setup already installed, you should be able to switch to the new version pretty seamlessly. There are just a couple of things to be aware of.

First, the "configuration" procedure is completely different in the new version. Way better and way easier, but it's not what you're used to from V1. In V1, you had to edit the project source code and compile your own custom version of the program. No more! With V2, you simply install the standard, pre-compiled .bin file, and select options using the Pinscape Config Tool on Windows.

Second, if you're using the TSL1410R optical sensor for your plunger, there's a chance you'll need to boost your light source's brightness a little bit. The "shutter speed" is faster in this version, which means that it doesn't spend as much time collecting light per frame as before. The software actually does "auto exposure" adaptation on every frame, so the increased shutter speed really shouldn't bother it, but it does require a certain minimum level of contrast, which requires a certain minimal level of lighting. Check the plunger viewer in the setup tool if you have any problems; if the image looks totally dark, try increasing the light level to see if that helps.

New Features

V2 has numerous new features. Here are some of the highlights...

Dynamic configuration: as explained above, configuration is now handled through the Config Tool on Windows. It's no longer necessary to edit the source code or compile your own modified binary.

Improved plunger sensing: the software now reads the TSL1410R optical sensor about 15x faster than it did before. This allows reading the sensor at full resolution (400dpi), about 400 times per second. The faster frame rate makes a big difference in how accurately we can read the plunger position during the fast motion of a release, which allows for more precise position sensing and faster response. The differences aren't dramatic, since the sensing was already pretty good even with the slower V1 scan rate, but you might notice a little better precision in tricky skill shots.

Keyboard keys: button inputs can now be mapped to keyboard keys. The joystick button option is still available as well, of course. Keyboard keys have the advantage of being closer to universal for PC pinball software: some pinball software can be set up to take joystick input, but nearly all PC pinball emulators can take keyboard input, and nearly all of them use the same key mappings.

Local shift button: one physical button can be designed as the local shift button. This works like a Shift button on a keyboard, but with cabinet buttons. It allows each physical button on the cabinet to have two PC keys assigned, one normal and one shifted. Hold down the local shift button, then press another key, and the other key's shifted key mapping is sent to the PC. The shift button can have a regular key mapping of its own as well, so it can do double duty. The shift feature lets you access more functions without cluttering your cabinet with extra buttons. It's especially nice for less frequently used functions like adjusting the volume or activating night mode.

Night mode: the output controller has a new "night mode" option, which lets you turn off all of your noisy devices with a single button, switch, or PC command. You can designate individual ports as noisy or not. Night mode only disables the noisemakers, so you still get the benefit of your flashers, button lights, and other quiet devices. This lets you play late into the night without disturbing your housemates or neighbors.

Gamma correction: you can designate individual output ports for gamma correction. This adjusts the intensity level of an output to make it match the way the human eye perceives brightness, so that fades and color mixes look more natural in lighting devices. You can apply this to individual ports, so that it only affects ports that actually have lights of some kind attached.

IR Remote Control: the controller software can transmit and/or receive IR remote control commands if you attach appropriate parts (an IR LED to send, an IR sensor chip to receive). This can be used to turn on your TV(s) when the system powers on, if they don't turn on automatically, and for any other functions you can think of requiring IR send/receive capabilities. You can assign IR commands to cabinet buttons, so that pressing a button on your cabinet sends a remote control command from the attached IR LED, and you can have the controller generate virtual key presses on your PC in response to received IR commands. If you have the IR sensor attached, the system can use it to learn commands from your existing remotes.

Yet more USB fixes: I've been gradually finding and fixing USB bugs in the mbed library for months now. This version has all of the fixes of the last couple of releases, of course, plus some new ones. It also has a new "last resort" feature, since there always seems to be "just one more" USB bug. The last resort is that you can tell the device to automatically reboot itself if it loses the USB connection and can't restore it within a given time limit.

More Downloads

  • Custom VP builds: I created modified versions of Visual Pinball 9.9 and Physmod5 that you might want to use in combination with this controller. The modified versions have special handling for plunger calibration specific to the Pinscape Controller, as well as some enhancements to the nudge physics. If you're not using the plunger, you might still want it for the nudge improvements. The modified version also works with any other input controller, so you can get the enhanced nudging effects even if you're using a different plunger/nudge kit. The big change in the modified versions is a "filter" for accelerometer input that's designed to make the response to cabinet nudges more realistic. It also makes the response more subdued than in the standard VP, so it's not to everyone's taste. The downloads include both the updated executables and the source code changes, in case you want to merge the changes into your own custom version(s).

    Note! These features are now standard in the official VP releases, so you don't need my custom builds if you're using 9.9.1 or later and/or VP 10. I don't think there's any reason to use my versions instead of the latest official ones, and in fact I'd encourage you to use the official releases since they're more up to date, but I'm leaving my builds available just in case. In the official versions, look for the checkbox "Enable Nudge Filter" in the Keys preferences dialog. My custom versions don't include that checkbox; they just enable the filter unconditionally.
  • Output circuit shopping list: This is a saved shopping cart at mouser.com with the parts needed to build one copy of the high-power output circuit for the LedWiz emulator feature, for use with the standalone KL25Z (that is, without the expansion boards). The quantities in the cart are for one output channel, so if you want N outputs, simply multiply the quantities by the N, with one exception: you only need one ULN2803 transistor array chip for each eight output circuits. If you're using the expansion boards, you won't need any of this, since the boards provide their own high-power outputs.
  • Cary Owens' optical sensor housing: A 3D-printable design for a housing/mounting bracket for the optical plunger sensor, designed by Cary Owens. This makes it easy to mount the sensor.
  • Lemming77's potentiometer mounting bracket and shooter rod connecter: Sketchup designs for 3D-printable parts for mounting a slide potentiometer as the plunger sensor. These were designed for a particular slide potentiometer that used to be available from an Aliexpress.com seller but is no longer listed. You can probably use this design as a starting point for other similar devices; just check the dimensions before committing the design to plastic.

Copyright and License

The Pinscape firmware is copyright 2014, 2021 by Michael J Roberts. It's released under an MIT open-source license. See License.

Warning to VirtuaPin Kit Owners

This software isn't designed as a replacement for the VirtuaPin plunger kit's firmware. If you bought the VirtuaPin kit, I recommend that you don't install this software. The KL25Z can only run one firmware program at a time, so if you install the Pinscape firmware on your KL25Z, it will replace and erase your existing VirtuaPin proprietary firmware. If you do this, the only way to restore your VirtuaPin firmware is to physically ship the KL25Z back to VirtuaPin and ask them to re-flash it. They don't allow you to do this at home, and they don't even allow you to back up your firmware, since they want to protect their proprietary software from copying. For all of these reasons, if you want to run the Pinscape software, I strongly recommend that you buy a "blank" retail KL25Z to use with Pinscape. They only cost about $15 and are available at several online retailers, including Amazon, Mouser, and eBay. The blank retail boards don't come with any proprietary firmware pre-installed, so installing Pinscape won't delete anything that you paid extra for.

With those warnings in mind, if you're absolutely sure that you don't mind permanently erasing your VirtuaPin firmware, it is at least possible to use Pinscape as a replacement for the VirtuaPin firmware. Pinscape uses the same button wiring conventions as the VirtuaPin setup, so you can keep your buttons (although you'll have to update the GPIO pin mappings in the Config Tool to match your physical wiring). As of the June, 2021 firmware, the Vishay VCNL4010 plunger sensor that comes with the VirtuaPin v3 plunger kit is supported, so you can also keep your plunger, if you have that chip. (You should check to be sure that's the sensor chip you have before committing to this route, if keeping the plunger sensor is important to you. The older VirtuaPin plunger kits came with different IR sensors that the Pinscape software doesn't handle.)

Committer:
mjr
Date:
Wed Jun 03 18:50:34 2015 +0000
Revision:
23:14f8c5004cd0
Parent:
22:71422c359f2a
Child:
25:e22b88bd783a
Use regular AnalogIn (not FastAnalogIn) for potentiometer readings, and take the average of several readings, to reduce noise; add a minimum distance threshold before a firing event when crossing the rest position.

Who changed what in which revision?

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