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:
Thu Dec 24 01:37:40 2015 +0000
Revision:
37:ed52738445fc
Parent:
36:b9747461331e
Child:
38:091e511ce8a0
Bug fixes to USB HAL

Who changed what in which revision?

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