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 real plunger, button inputs, and feedback device control.

In case you haven't heard of the concept 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 serve as the "backglass" display. A third smaller monitor can serve as the "DMD" (the Dot Matrix Display used for scoring on newer machines), or you can even install a real pinball plasma DMD. A computer 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 hardware.

A few small companies build and sell complete, finished virtual pinball machines, but I think it's more fun as a 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 potentionmeter (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 VirtuaPin kit uses the same KL25Z microcontroller that Pinscape uses, but the rest of its hardware is different and incompatible. In particular, the Pinscape firmware doesn't include support for the IR proximity sensor used in the VirtuaPin plunger kit, so you won't be able to use your plunger device with the Pinscape firmware. In addition, the VirtuaPin setup uses a different set of GPIO pins for the button inputs from the Pinscape defaults, so if you do install the Pinscape firmware, you'll have to go into the Config Tool and reassign all of the buttons to match the VirtuaPin wiring.

Committer:
mjr
Date:
Fri Sep 25 21:28:31 2015 +0000
Revision:
30:6e9902f06f48
Parent:
29:582472d0bc57
Child:
33:d832bcab089e
Use DMA for TLC5940 SPI transfer to reduce time interrupt handler (fixes problem with MMA8415Q freezing up).  All LedWiz flashing modes now fully supported.

Who changed what in which revision?

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