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

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

/media/uploads/mjr/pinscape_no_background_small_L7Miwr6.jpg

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

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

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

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

Downloads

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

Documentation

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

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

System Requirements

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

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

Main Features

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

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

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

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

Expansion Boards

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

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

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

Expansion Board project page

Update notes

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

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

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

New Features

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

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

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

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

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

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

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

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

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

More Downloads

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

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

Copyright and License

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

Warning to VirtuaPin Kit Owners

This software isn't designed as a replacement for the VirtuaPin plunger kit's firmware. If you bought the VirtuaPin kit, I recommend that you don't install this software. The VirtuaPin kit uses the same KL25Z microcontroller that Pinscape uses, but the rest of its hardware is different and incompatible. In particular, the Pinscape firmware doesn't include support for the IR proximity sensor used in the VirtuaPin plunger kit, so you won't be able to use your plunger device with the Pinscape firmware. In addition, the VirtuaPin setup uses a different set of GPIO pins for the button inputs from the Pinscape defaults, so if you do install the Pinscape firmware, you'll have to go into the Config Tool and reassign all of the buttons to match the VirtuaPin wiring.

IRRemote/IRReceiver.h

Committer:
mjr
Date:
12 months ago
Revision:
109:310ac82cbbee
Parent:
97:fc7727303038

File content as of revision 109:310ac82cbbee:

// IR Remote Receiver
//
// This is a multi-protocol receiver for IR remote control signals.  The
// IR signals are physically received through an external sensor.  Our
// reference device is the TSOP384xx, but most other IR remote sensors
// are similar in design and will proably work.  We have two main requirements
// for the sensor: first, it has to demodulate the IR carrier wave; second,
// it has to present a single-wire digital signal representing the demodulated
// IR status.  We assume active low signaling, where 0V on the signal line 
// represents "IR ON" and Vcc represents "IR OFF".  It would be fairly easy
// to adapt the code to the opposite signaling sense, but I haven't bothered
// to parameterize this because I haven't seen any active-high sensors.  The
// sensor also obviously has to be electrically compatible with the KL25Z,
// which mostly means that it runs on a 3.3V supply.  If your sensor uses
// a different supply voltage, it might still be workable, but you might
// need to interpose a voltage level converter on the logic input to make
// sure that the KL25Z GPIO pin doesn't go above 3.3V, as these pins aren't
// tolerant of higher voltages.
//
// How to wire the sensor
//
// To physically wire the sensor, you just need to connect the sensor's
// Vs and GND pins to the the 3.3V out (P3V3) and GND on the KL25Z,
// respectively, and connect its "OUT" or "data" pin (pin 1 on a TSOP384xx)
// to a free, interrupt-capable GPIO on the KL25Z.  On the KL25Z, all PTAxx
// and PTDxx ports are interrupt-capable (and conversely, PTBxx, PTCxx, and
// PTExx ports aren't, so you can't use one of those).  You should check the
// data sheet for the sensor you're using to see if any other external
// components are required; e.g., the TSOP384xx data sheet recommends a 
// capacitor and resistor for ESD protection and power supply conditioning.
// The data sheet will include a diagram showing the suggested application
// wiring if there are any special considerations like that.  Note that the
// TSOP384xx data sheet doesn't specify exact values for the resistor and 
// capacitor, so I'll mention what I'm using in my test setup: 220 ohms for
// the resistor, 150nF for the capacitor.
// 
// How to use it in your application
//
// To use the receiver in an application, first create an IRReceiver object,
// telling it which pin to use as the sensor input, and how big you want the
// raw sample buffer to be.  The raw buffer needs to be big enough to hold
// samples that arrive during each iteration of your main loop, so you need
// approximately one buffer entry per 250us of your main loop's maximum 
// iteration time.  If RAM isn't tight in your app, just pick a fairly large 
// size (maybe 200 entries); if RAM is tight, figure your worst-case main loop
// time, divide by 250us, and add maybe 25% or 50% padding.  Once you create
// the receiver object, call enable() to enable reception.  You can do this
// once at the outset, or you can selectively enable() and disable() it at
// any time if you only need reception at specific times.  Reception takes
// a small amount of CPU time (in interrupt mode) whenever signals arrive,
// so if you have a time-critical task to do at a time when reception isn't
// useful, you can turn it off to avoid any latency from IR interrupts.
//
//    IRReceiver *rx = new IRReceiver(PTA13, 32);
//    rx->enable();
//
// If you're using the companion transmitter class in the same application
// to create a device that's both an IR transmitter and receiver, you might
// want to tell the receiver about the transmitter, via setTransmitter().
// This causes the receiver to ignore incoming signals whenever the
// transmitter is sending, so that you don't receive your own transmissions.
// This isn't necessary if the receiver is positioned so that it can't see
// the transmitter's signals.
//
//    rx->setTransmitter(tx);
//
// Once you have a receiver set up and enabled, you need to call its process()
// method on each iteration of your main loop.  This method takes all of the
// signals that have been received since the last call and runs them through
// the protocol decoders.  To minimize time spent in interrupt handlers, the
// interrupt handlers merely queue the messages internally; this makes them
// return extremely quickly, so that they don't add any significant latency 
// for other hardware or timer interrupts your application might use.
//
//    rx->process();
//
// Also in your main loop, read incoming IR remote codes by calling
// readCommand() on the receiver.  If a command is available, this will read
// it into an IRCommand object, which tells you the protocol the sender used
// (see IRProtocolID.h), and provides a "universal" representation of the
// command.  The universal representation is protocol-specific mapping of
// the raw data bits to an integer value.  We try to do this in a way that's
// most useful per protocol, with two main goals in mind.  First, if there
// are any internal bits that are more structural than meaningful, such as
// checksums or other integrity checks, we generally remove them.  Second,
// if there are published tables of codes from a manufacturer, we try to
// match the format used there, to make it easier to verify that codes are
// as expected and to make it easier to construct apps around specific types
// of remotes.
//
//    IRCommand cmd;
//    while (rx->readCommand(cmd)) { process the command; }
//
// You can also optionally read the raw incoming data, by calling processOne()
// instead of process().  processOne() runs a reading through the protocol
// decoders but also hands it back to you.  Raw samples are simply "IR ON"
// and "IR OFF" signals with the time the IR was continuously on or off.
// The raw samples are useful if you want to build something like a repeater
// that only has to replicate the physical IR signals without regard to the
// underlying data bits.  Raw signals are obviously also very useful if you
// want to analyze an unknown protocol and figure out how to write a new
// encoder/decoder for it.  One thing that raw signals aren't great for,
// somewhat counterintuitively, is for building a learning remote.  Many of
// the protocols have special ways of handling repeated codes (e.g., when 
// holding down a key) that make verbatim repetition of a signal problematic
// for learning remote use.  If you just repeat a raw code, the receiver
// might be confused into thinking that one key press looks like several
// key presses, or vice versa.  It's better when possible to decode a signal
// into a recognized protocol, store the decoded bit data rather than the
// raw signals as the "learned" code, and then reconstruct the appropriate 
// signal for transmission by re-encoding the learned bit code using the 
// protocol's known structure.
//
//
// Internal architecture
//
// The IRReceiver object is simply a coordinator that manages the sensor
// hardware interface, reads the raw signals from the sensor, and passes
// the raw signals to the protocol objects for decoding.  For each protocol
// we recognize, we define a specialized handler class for the protocol.  
// Each protocol handler implements a state machine for decoding signals 
// using its protocol.  When IRReceiver reads a raw signal, it simply passes 
// it to each of the protocol handlers in turn.  They all operate as 
// independent state machines, so in effect we have specialized receivers 
// for all of the protocols operating in parallel, all eavesdropping on the 
// same incoming stream of signals.  When one of the protocol handlers 
// successfully decodes a complete "command" (a key press on a remote, in 
// most cases), it adds the command to our queue, using a universal 
// representation that we define.  Clients can then read the incoming
// commands from the queue without worrying about the raw signal details.
//
// It might sound chaotic to have all of these different protocol decoders
// working on the same data at the same time, but in practice the various 
// protocols have enough internal structure that only the "right" handler
// will be able to do anything with a given signal, and the rest will just
// ignore it, and bide their time until something shows up that they can make 
// sense of.  It might also sound like a lot of overhead, but in practice 
// it's very lightweight: it takes about 4% CPU to service the decoding 
// process while a signal is actually coming in, and essentially 0% when
// the IR airwaves are silent.  What's more, that 4% CPU time is all in
// application context, not in interrupt context, so it doesn't contribute
// any latency to any other hardware interrupts you need to handle in your
// application.
//
// The individual protocol state machines are all very simple, typically 
// doing just a few integer compares on the incoming timing data per signal.
// They also require very little state, usually on the order of a few 'int's 
// per decoder, which translates to a small RAM footprint.  The decoders 
// operate incrementally and decode in near real time, so decoded commands 
// appear essentially at the same time that their signals finish.
//
// Note that, unlike some other MCU IR libraries, we don't any have sort 
// of global receiver state.  In particular, we don't try to guess about
// message boundaries globally.  All of the boundary detection and protocol 
// state is in the individual protocol decoders.  That eliminates the need
// for heuristics or special cases to guess about what "usually" indicates
// a message boundary across all protocols.  There are enough special cases
// to make such guesses problematic, which becomes apparent if you look at
// the code in libraries that work that way.  Since we don't need to know
// about message boundaries globally, we don't need to make such guesses or
// apply such special cases.  We simply deal in the raw pulses and let 
// each decoder separately judge for itself where its own message boundaries 
// are.  This might seem odd, because the implication is that one decoder
// might think we're in the middle of a message while another decoder 
// thinks we're on a boundary.  But that's just fine, and it's exactly
// why we shouldn't be making those judgments globally: if two protocols
// have contradictory rules like that, the way to reconcile it is to accept
// that there really is no correct global judgment, and leave it to the 
// decoders to track their own states independently.
//
// We receive signals from the sensor via interrupts on the input GPIO pin.
// This allows for the most accurate timing possible, which is important
// because IR coding is entirely in the signal timing.  Interrupts gives us
// much more accurate timing than polling would for obvious reasons.  As
// mentioned above, though, we try to minimize the time we spend in IRQ
// context, since time spent in one interrupt handler translates to added
// latency for any other interrupts that occur at the same time.  To 
// accomplish this, the interrupt handlers don't do any decoding at all.
// They simply add the incoming signal data to an internal queue and then
// return.  We do the decoding work back in application context, by having
// the main loop call our process() routine periodically.  This takes signal
// readings off of the queue and runs them through the decoders.  This
// introduces a small amount of lag time between physically receiving a
// signal and decoding it, but the lag time is only on the order of the
// main loop run time.  In most MCU applications this is a very short
// time, perhaps only microseconds or perhaps as long as a few millseconds.
// But in any case it's almost always so short that a user can't perceive
// the delay, so for all practical purposes decoding is done in real time.
//
//
// How IR remotes work in general
//
// IR remote controls work by transmitting timed pulses of infrared light.
// These pulses are modulated in two ways: first, with a "carrier", which
// is a PWM signal operating at a fixed, relatively high frequency; and
// second, with a lower frequency data signal superimposed on the PWM
// signal.  (And I suppose you could say there's a third layer of 
// modulation in the IR light itself, since that's an electromagnetic 
// wave operating at an even higher frequency of around 300 THz.)
//
// Carrier: The PWM carrier uses a fixed frequency, usually around 40kHz.  
// The carrier doesn't encode any data, since it's just constant fixed-length
// pulses.  Its function, rather, is to provide a regular oscillating signal
// that receivers can use to distinguish data signals from ambient light.
// This is necessary because the IR light wavelengths are also contained
// in sunlight and ordinary household lighting.  (Fluourescent lights even
// have their own characteristic oscillating frequencies in the IR band, so
// the receiver not only has to distinguish the signal from constant
// amgient light levels but also from other types of oscillating light
// levels.  The PWM carrier frequencies used in remotes are chosen based
// on the practical need to distinguish remote control signals from the
// common household interference sources.)  Receivers can separate the 
// an oscillating PWM signal at a particular frequency from other signals
// through a process known as demodulation, which is the same mechanism
// that radio receivers use to pluck AM or FM signals from the jumble of 
// background noise in the radio spectrum.
//
// For our purposes, we don't worry about demodulation in the software,
// since the sensor hardware does that part of the job.  Each type of sensor 
// is designed to demodulate a particular carrier frequency, so you should 
// choose a sensor based on the types of remotes you plan to use it with.  
// Most CE manufacturers have more or less standardized on 38kHz, which is
// why we recommend the TSOP384xx series.  Not everyone is at exactly 38kHz,
// but most are within 2kHz plus or minus, and the TSOP seems to demodulate
// signals within a few kHz of its nominal frequency very well.  38kHz seems
// to be a good centerpoint for home electronics devices, which is why we
// recommend the 38kHz part as a "universal" receiver.  If your application
// only needs to receive from one specific remote (rather than act as a 
// universal receiver), you might be better served with a different TSOP
// part that's tuned to your transmitter's carrier frequency, if that's
// something other than 38kHz.
//
// Data signal: The data signal is superimposed on the PWM carrier by 
// turning the PWM'ed IR source on and off at a lower, variable frequency. 
// These longer on/off pulses are of different lengths.  The data bits are 
// encoded in the varying lengths, although there's no one true way of 
// doing this.  Each protocol has its own way of representing bits as
// combinations of on times and off times, which we'll come to shortly.
//
// "On" pulses are called "marks", and "off" pulses are called "spaces".  
// The terms come from wired asynchronous protocols, which share many 
// properties with IR signals at this level.
//
// Note that each pulse has to be long enough to contain some minimum 
// number (maybe 5-10) of PWM pulses, because otherwise the demodulator 
// wouldn't be able to detect the presence or absence of the underlying 
// PWM pulses.  This makes IR remote codes fairly slow in terms of data 
// rate, since the absolute minimum time per bit is the time in the shortest 
// data pulse.  Most codings actually use at least two pulses per bit for 
// the sake of signal integrity, so the effective data rate lower still.  
// Fortunately, this is all rather unimportant, since IR remotes don't 
// need a very high data rate.  They're mostly used to transmit button 
// presses made by hand by a human user, which are at a fairly low rate
// to start with; plus, the amount of data per button is minuscule, 
// usually from 8 to 32 bits.
//
// Encodings
//
// The timing of the marks and spaces carries the information, but exactly
// how it does this is a whole separate matter, known as an encoding.  An
// encoding is a mapping from '0' and '1' bits to a pattern of marks and
// spaces, and vice versa.  At first glance, it might seem that you could
// just use a mark as a '1' and a space as a '0', and in fact some protocols
// do something like this.  But that simple approach has some big drawbacks
// arising from the lack of a shared clock between sender and receiver.
// Most encodings therefore do something to embed a timing signal of some
// sort within the data signal, by using the lengths of the pulses to encode 
// bits rather than just the presence of the pulses.
//
// There are probably an infinite number of possible ways to do this in
// principle.  Fortunately, the CE companies have only put a finite number
// of them into practice.  In fact, it seems that we can cover practically
// all of the remotes out there by considering a small handful of encoding 
// schemes.  Here are the main ones, and the ones we can use in this
// receiver library:
//
//  - Async bit stream.  This is basically the IR equivalent of a wired
//    UART.  Each code word consists of a fixed number of bits.  Each bit
//    is represented by IR ON for '1' and IR OFF for '0', transmitted for
//    a fixed time length.  To transmit, simply turn the IR on and off in
//    sequence for the fixed bit time per bit.  To receive and decode,
//    observe whether the IR signal is on or off in each time window. 
//    This type of protocol looks simple, but it presents some difficulties
//    in implementation, because it doesn't provide any cues embedded in
//    the IR signal to help the receiver synchronize with the sender or
//    recognize the boundaries of code words, as all of the other common
//    protocols do.  That might be why this class seems to be rarely used 
//    in real applications.  Protocols based on simple async bits usually
//    add something at the protocol level that helps the reciever detect
//    word boundaries and check signal integrity.
//
//  - Pulse distance coding, also known as space length coding.  In this 
//    scheme, marks are all of equal length, but spaces come in two lengths, 
//    A and B, where B is much longer than A (usually twice as long, but it
//    could be even longer).  A encodes 0 and B encodes 1.  The marks serve
//    as regular clock signals, allowing the receiver to keep in sync with
//    the sender, and the long and short space times (A and B) are different
//    enough that the receiver can easily distinguish them reliably, even
//    with a low-precision clock.  This scheme is probably the most widely
//    used in CE products, because it's the encoding used by the NEC 
//    protocol, which most Japanese CE companies use.
//
//  - Pulse length coding, also known as mark length coding.  This is simply 
//    the inverse of pulse distance coding: spaces are all of equal length, 
//    and marks come in two lengths, with the short mark encoding 0 and the 
//    long mark encoding 1.  This is practically the same in all meaningful
//    ways as the space length coding; the only reason both kinds exist is
//    probably that either someone had a bad case of NIH or they wanted to
//    avoid paying a patent royalty.  Mark length coding is the scheme Sony
//    uses (in their SIRCS protocol).
//
//  - Manchester coding.  The message is divided into time slices of 
//    equal size, one bit per slice.  Within each slice, the IR is on for 
//    half the window and off for half the window.  The 0 and 1 bits are
//    encoded by the direction of the transition: if a bit window starts
//    with a mark (IR ON) and ends with a space (IR OFF), it's a '1'; if it
//    starts with a space and ends with a mark, it's a '0'.  Or vice versa.
//    Each mark or space therefore lasts for either 1/2 or 1 bit time 
//    length, never longer.  This makes it fairly easy for the receiver to 
//    distinguish the two time lengths, even with a fairly low-precision 
//    clock, since they're so different.  It's also easy for the receiver
//    to distinguish each bit, since there's always at least one transition
//    (mark to space or space to mark) per bit.  What's more, '0' and '1' 
//    bits take the same time to transmit (unlike the mark-length and 
//    space-length protocols), so every code word (assuming a fixed bit 
//    count) takes the same time regardless of the bit values within. 
//    Manchester modulation is used in the Philips RC5 and RC6 protocols, 
//    which are widely used among European CE companies.
//
// Protocols
//
// On top of the encoding scheme, there's another level of structure called
// a protocol.  A given protocol uses a given encoding for the data bits,
// but then also adds some extra structure.  
//
// For starters, the IR protocols all work in terms of "code words".  In 
// computer terms, a code word amounts to a datatype with a fixed number 
// of bits.  For example, the NEC protocol uses a 32-bit code word: each 
// button press is represented by a 32-bit transmission.  A single key 
// press usually maps to a single code word, although not always; the 
// Pioneer protocol, for example, transmits two words for certain buttons, 
// using a special "shift" code for the first word to give a second meaning 
// to the second word, to extend the possible number of commands that would
// be otherwise limited by the number of bits in a single code word.

// Second, most of the IR protocols add special non-data signals that
// mark the beginning and/or end of a code word.  These are usually just
// extra-long marks or spaces, which are distinguishable from the marks
// and spaces within a code word because they're too long to be valid in
// the data encoding scheme.  These are important to reliable communication
// because the sender and receiver don't have any other way to share state 
// with each other.  Consider what would happen if someone walked in the
// way while you were transmitting a remote code: the receiver would miss
// at least a few data bits, so it would be out of sync with the sender.
// If there weren't some way to distinguish the start of a code word from
// the IR pulses themselves, the receiver would now be permanently out
// of sync from the sender by however many bits it missed.  But with the
// special "header" code, the receiver can sync up again as soon as the
// next code word starts, since it can tell from the timing that the
// header can't possibly be a bit in the middle of a code word.


#ifndef _IRRECEIVER_H_
#define _IRRECEIVER_H_

#include <mbed.h>

#include "IRRemote.h"
#include "IRCommand.h"
#include "circbuf.h"
#include "FastInterruptIn.h"


// IR receiver protocol interface.  This contains functions that we only
// want to make accessible to the protocol decoders.
class IRRecvProIfc
{
public:
    // write a command to the command queue
    void writeCommand(IRCommand &cmd) { commands.write(cmd); }

protected:
    // Decoded command queue.  The protocol handlers add commands here
    // as soon as they decode them.
    CircBuf<IRCommand, 8> commands;
};


// IR Remote Receiver
class IRReceiver : protected IRRecvProIfc
{
public:
    // Construct a receiver with the given data input pin.  The receiver
    // is initially disabled.  To start receiving signals, call enable().
    //
    // Choose a raw buffer size according to the longest iteration time
    // for your main application loop between the required periodic calls 
    // to our process() function.  The interrupt handlers write pulse times
    // to the raw buffer as the pulses arrive, and these are held in the 
    // buffer until they're removed by process().  The raw buffer only needs
    // to be big enough for the "backlog" that occurs between the real-time
    // incoming signals and the main loop's processing calls.  The fastest
    // IR pulses are about 250us long, so size the buffer according to how
    // many 250us intervals will occur in the worst case, that is, the 
    // longest main loop iteration.  If the main loop always runs in 2.5ms 
    // or shorter, that means you need about a 10-element buffer.  To be
    // conservative, size it at perhaps 2x the expected maximum.
    //
    IRReceiver(PinName rxpin, size_t rawBufCount);
    
    // Destructor
    ~IRReceiver();
    
    // Optionally connect to a transmitter, to suppress reception while
    // we're transmitting.  This prevents spuriously receiving our own
    // transmissions, if our IR LED and sensor are physically close enough
    // to one another that our sensor would pick up light from our LED.
    // If the two are physically isolated so that we can't receive our
    // own transmissions, it's not necessary to connect the transmitter
    // here, as there's no restriction on the software side on sending
    // and receiving simultaneously - the suppression is only needed to
    // avoid self-interference with the physical IR signals.
    void setTransmitter(class IRTransmitter *transmitter)
    {
        this->transmitter = transmitter;
    }

    // Enable/disable our interrupt handlers.  If the main program
    // doesn't need IR input, it can disable the receiver so that
    // it doesn't consume any CPU time handling interrupts.
    void enable();
    void disable();
    
    // Read a command.  Returns true if a command was available, filling
    // in 'cmd'.  Returns false (without blocking) if no commands are 
    // available.
    bool readCommand(IRCommand &cmd) { return commands.read(cmd); }
    
    // Is a command ready to read?
    bool isCommandReady() { return commands.readReady(); }
    
    // Process signals received.  The application main loop must call this
    // as frequently as possible to process incoming signals from the raw
    // buffer.  This processes all samples in the raw buffer before
    // returning.
    void process();
    
    // Process and retrieve one raw pulse.  The application main loop can
    // optionally call this, instead of process(), if it wants to retrieve
    // each raw sample for its own purposes in addition to running them 
    // through the protocol state machines.  If no sample is available, we
    // immediately return false - the routine doesn't block waiting for a
    // sample.  If a sample is available, we fill in 'sample' with the pulse 
    // time in microseconds, and set 'mark' to true if the sample was a mark, 
    // false if it's a space.  
    //
    // To use this instead of process(), on each main loop iteration, call 
    // this function in an inner loop until it returns false.  That'll ensure 
    // that all pending samples have been processed through the protocol 
    // state machines and that maximum buffer space is available for the next 
    // main loop iteration.
    bool processOne(uint32_t &sample, bool &mark);
    
    // Process and retrieve one raw pulse.  This works the same as the
    // two-argument version above, but returns the sample in our internal
    // format: the sample value is a time reading in 2us units, and the low
    // bit is 1 for a mark, 0 for a space.  To convert to a time reading in
    // microseconds, mask out the low bit and multiply by 2.
    bool processOne(uint16_t &sample);

    // Maximum pulse length in microseconds.  Anything longer will simply 
    // be represented with this value.  This is long enough that anything
    // longer has equivalent meaning in any of our protocols.  Generally,
    // space longer than this will only occur in a silent interval between
    // transmissions (that is, while no one is sending any codes), and a
    // mark longer than this could only be interference or noise.
    //
    // This value should be chosen so that it's high enough to be readily
    // distinguishable (in terms of our error tolerance) from the longest
    // *meaningful* space or pulse in any protocol we need to handle, but
    // not much higher than that.  It shouldn't be too long because it has
    // a role as an inactivity timeout on receive: we can't always know
    // that a signal has ended until there's inactivity for this amount
    // of time.  If the timeout is too long, it can become noticable as
    // lag time in recognizing signals.  In practice, the longest gap time
    // between repeating signals in commonly used protocols is in the 
    // neighboorhood of 100ms.
    //
    // This value is chosen to be the largest we can fit into a 16-bit
    // int, taking into account our 2X scaling and our use of the low bit
    // for a mark/space indicator.  That leaves us with 14 bits and 2X scale.
    static const uint32_t MAX_PULSE = 131068;    

private:
    // Input pin.  Reads from a TSOP384xx or similar sensor.  Any
    // sensor should work that demodulates the carrier wave and 
    // gives us an active-low input on the pin.  
    //
    // Note that we use our FastInterruptIn replacement instead of the
    // mbed InterruptIn.  We don't actually need the higher speed here of
    // FastInterruptIn, but we have to use it anyway because other parts
    // of the system use it.  The two classes don't play nice together:
    // the whole app has to use one or the other.
    FastInterruptIn pin;

    // IR raw data buffer.  The interrupt handlers store the pulse
    // timings here as they arrive, and the process() routine reads from
    // the buffer.
    //
    // Samples here are limited to 16 bits, so the longest time that
    // can be represented is 65535us.  Anything longer is capped to this.
    //
    // To keep track of marks vs spaces, we set the low-order bit of
    // each sample time to 1 for a mark and 0 for a space.  That means
    // that the times are only good to 2us precision, but that's plenty
    // of precision for all of the IR protocols, since the shortest time 
    // bases are around 250us.
    CircBufV<uint16_t> rawbuf;
    
    // Pulse timer.  We reset the timer at the start of each pulse, so
    // it tells us the duration thus far of the current pulse at any 
    // given time.  We stop the timer (without resetting) any time a
    // pulse reaches the maximum length, to ensure that the timer never
    // rolls over, even in the indefinite gap between codes.
    Timer pulseTimer;
    
    // flag: the pulse timer has reached IR_MAX_PULSE
    bool pulseAtMax;
    
    // current pulse state: mark = 1, space = 0
    bool pulseState;
    
    // start the pulse timers with the new pulse state (1=mark, 0=space)
    void startPulse(bool newPulseState);
    
    // end the current pulse, checking that the pulse state matches the
    // current state
    void endPulse(bool lastPulseState);

    // process a pulse through our protocol handlers
    void processProtocols(uint32_t t, bool mark);
    
    // rise and fall interrupt handlers for the input pin
    static void cbFall(void *obj) { ((IRReceiver *)obj)->fall(); }
    static void cbRise(void *obj) { ((IRReceiver *)obj)->rise(); }
    void fall();
    void rise();
    
    // timeout for time-limited states
    Timeout timeout;
    
    // timeout handler for a pulse (mark or space)
    void pulseTimeout(void);
    
    // Connected transmitter.  If this is set, we'll suppress reception
    // while the transmitter is sending a signal, to avoid receiving our
    // own transmissions.
    class IRTransmitter *transmitter;
};

#endif