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

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

/media/uploads/mjr/pinscape_no_background_small_L7Miwr6.jpg

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

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

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

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

Downloads

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

Documentation

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

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

System Requirements

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

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

Main Features

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

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

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

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

Expansion Boards

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

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

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

Expansion Board project page

Update notes

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

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

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

New Features

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

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

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

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

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

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

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

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

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

More Downloads

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

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

Copyright and License

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

Warning to VirtuaPin Kit Owners

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

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

Committer:
mjr
Date:
Thu Dec 03 07:34:57 2015 +0000
Revision:
34:6b981a2afab7
Parent:
33:d832bcab089e
Child:
35:e959ffba78fd
74HC595 output port extensions (for "chime board")

Who changed what in which revision?

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