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 Oct 21 21:53:07 2015 +0000
Revision:
33:d832bcab089e
Parent:
30:6e9902f06f48
Child:
34:6b981a2afab7
With expansion board 5940 "power enable" output; saving this feature, which is to be removed.

Who changed what in which revision?

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