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:
Fri Mar 17 22:02:08 2017 +0000
Revision:
77:0b96f6867312
Child:
82:4f6209cb5c33
New memory pool management; keeping old ones as #ifdefs for now for reference.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mjr 77:0b96f6867312 1 // IR Remote Receiver
mjr 77:0b96f6867312 2 //
mjr 77:0b96f6867312 3 // This is a multi-protocol receiver for IR remote control signals. The
mjr 77:0b96f6867312 4 // IR signals are physically received through an external sensor. Our
mjr 77:0b96f6867312 5 // reference device is the TSOP384xx, but most other IR remote sensors
mjr 77:0b96f6867312 6 // are similar in design and will proably work. We have two main requirements
mjr 77:0b96f6867312 7 // for the sensor: first, it has to demodulate the IR carrier wave; second,
mjr 77:0b96f6867312 8 // it has to present a single-wire digital signal representing the demodulated
mjr 77:0b96f6867312 9 // IR status. We assume active low signaling, where 0V on the signal line
mjr 77:0b96f6867312 10 // represents "IR ON" and Vcc represents "IR OFF". It would be fairly easy
mjr 77:0b96f6867312 11 // to adapt the code to the opposite signaling sense, but I haven't bothered
mjr 77:0b96f6867312 12 // to parameterize this because I haven't seen any active-high sensors. The
mjr 77:0b96f6867312 13 // sensor also obviously has to be electrically compatible with the KL25Z,
mjr 77:0b96f6867312 14 // which mostly means that it runs on a 3.3V supply. If your sensor uses
mjr 77:0b96f6867312 15 // a different supply voltage, it might still be workable, but you might
mjr 77:0b96f6867312 16 // need to interpose a voltage level converter on the logic input to make
mjr 77:0b96f6867312 17 // sure that the KL25Z GPIO pin doesn't go above 3.3V, as these pins aren't
mjr 77:0b96f6867312 18 // tolerant of higher voltages.
mjr 77:0b96f6867312 19 //
mjr 77:0b96f6867312 20 // How to wire the sensor
mjr 77:0b96f6867312 21 //
mjr 77:0b96f6867312 22 // To physically wire the sensor, you just need to connect the sensor's
mjr 77:0b96f6867312 23 // Vs and GND pins to the the 3.3V out (P3V3) and GND on the KL25Z,
mjr 77:0b96f6867312 24 // respectively, and connect its "OUT" or "data" pin (pin 1 on a TSOP384xx)
mjr 77:0b96f6867312 25 // to a free, interrupt-capable GPIO on the KL25Z. On the KL25Z, all PTAxx
mjr 77:0b96f6867312 26 // and PTDxx ports are interrupt-capable (and conversely, PTBxx, PTCxx, and
mjr 77:0b96f6867312 27 // PTExx ports aren't, so you can't use one of those). You should check the
mjr 77:0b96f6867312 28 // data sheet for the sensor you're using to see if any other external
mjr 77:0b96f6867312 29 // components are required; e.g., the TSOP384xx data sheet recommends a
mjr 77:0b96f6867312 30 // capacitor and resistor for ESD protection and power supply conditioning.
mjr 77:0b96f6867312 31 // The data sheet will include a diagram showing the suggested application
mjr 77:0b96f6867312 32 // wiring if there are any special considerations like that. Note that the
mjr 77:0b96f6867312 33 // TSOP384xx data sheet doesn't specify exact values for the resistor and
mjr 77:0b96f6867312 34 // capacitor, so I'll mention what I'm using in my test setup: 220 ohms for
mjr 77:0b96f6867312 35 // the resistor, 150nF for the capacitor.
mjr 77:0b96f6867312 36 //
mjr 77:0b96f6867312 37 // How to use it in your application
mjr 77:0b96f6867312 38 //
mjr 77:0b96f6867312 39 // To use the receiver in an application, first create an IRReceiver object,
mjr 77:0b96f6867312 40 // telling it which pin to use as the sensor input, and how big you want the
mjr 77:0b96f6867312 41 // raw sample buffer to be. The raw buffer needs to be big enough to hold
mjr 77:0b96f6867312 42 // samples that arrive during each iteration of your main loop, so you need
mjr 77:0b96f6867312 43 // approximately one buffer entry per 250us of your main loop's maximum
mjr 77:0b96f6867312 44 // iteration time. If RAM isn't tight in your app, just pick a fairly large
mjr 77:0b96f6867312 45 // size (maybe 200 entries); if RAM is tight, figure your worst-case main loop
mjr 77:0b96f6867312 46 // time, divide by 250us, and add maybe 25% or 50% padding. Once you create
mjr 77:0b96f6867312 47 // the receiver object, call enable() to enable reception. You can do this
mjr 77:0b96f6867312 48 // once at the outset, or you can selectively enable() and disable() it at
mjr 77:0b96f6867312 49 // any time if you only need reception at specific times. Reception takes
mjr 77:0b96f6867312 50 // a small amount of CPU time (in interrupt mode) whenever signals arrive,
mjr 77:0b96f6867312 51 // so if you have a time-critical task to do at a time when reception isn't
mjr 77:0b96f6867312 52 // useful, you can turn it off to avoid any latency from IR interrupts.
mjr 77:0b96f6867312 53 //
mjr 77:0b96f6867312 54 // IRReceiver *rx = new IRReceiver(PTA13, 32);
mjr 77:0b96f6867312 55 // rx->enable();
mjr 77:0b96f6867312 56 //
mjr 77:0b96f6867312 57 // If you're using the companion transmitter class in the same application
mjr 77:0b96f6867312 58 // to create a device that's both an IR transmitter and receiver, you might
mjr 77:0b96f6867312 59 // want to tell the receiver about the transmitter, via setTransmitter().
mjr 77:0b96f6867312 60 // This causes the receiver to ignore incoming signals whenever the
mjr 77:0b96f6867312 61 // transmitter is sending, so that you don't receive your own transmissions.
mjr 77:0b96f6867312 62 // This isn't necessary if the receiver is positioned so that it can't see
mjr 77:0b96f6867312 63 // the transmitter's signals.
mjr 77:0b96f6867312 64 //
mjr 77:0b96f6867312 65 // rx->setTransmitter(tx);
mjr 77:0b96f6867312 66 //
mjr 77:0b96f6867312 67 // Once you have a receiver set up and enabled, you need to call its process()
mjr 77:0b96f6867312 68 // method on each iteration of your main loop. This method takes all of the
mjr 77:0b96f6867312 69 // signals that have been received since the last call and runs them through
mjr 77:0b96f6867312 70 // the protocol decoders. To minimize time spent in interrupt handlers, the
mjr 77:0b96f6867312 71 // interrupt handlers merely queue the messages internally; this makes them
mjr 77:0b96f6867312 72 // return extremely quickly, so that they don't add any significant latency
mjr 77:0b96f6867312 73 // for other hardware or timer interrupts your application might use.
mjr 77:0b96f6867312 74 //
mjr 77:0b96f6867312 75 // rx->process();
mjr 77:0b96f6867312 76 //
mjr 77:0b96f6867312 77 // Also in your main loop, read incoming IR remote codes by calling
mjr 77:0b96f6867312 78 // readCommand() on the receiver. If a command is available, this will read
mjr 77:0b96f6867312 79 // it into an IRCommand object, which tells you the protocol the sender used
mjr 77:0b96f6867312 80 // (see IRProtocolID.h), and provides a "universal" representation of the
mjr 77:0b96f6867312 81 // command. The universal representation is protocol-specific mapping of
mjr 77:0b96f6867312 82 // the raw data bits to an integer value. We try to do this in a way that's
mjr 77:0b96f6867312 83 // most useful per protocol, with two main goals in mind. First, if there
mjr 77:0b96f6867312 84 // are any internal bits that are more structural than meaningful, such as
mjr 77:0b96f6867312 85 // checksums or other integrity checks, we generally remove them. Second,
mjr 77:0b96f6867312 86 // if there are published tables of codes from a manufacturer, we try to
mjr 77:0b96f6867312 87 // match the format used there, to make it easier to verify that codes are
mjr 77:0b96f6867312 88 // as expected and to make it easier to construct apps around specific types
mjr 77:0b96f6867312 89 // of remotes.
mjr 77:0b96f6867312 90 //
mjr 77:0b96f6867312 91 // IRCommand cmd;
mjr 77:0b96f6867312 92 // while (rx->readCommand(cmd)) { process the command; }
mjr 77:0b96f6867312 93 //
mjr 77:0b96f6867312 94 // You can also optionally read the raw incoming data, by calling processOne()
mjr 77:0b96f6867312 95 // instead of process(). processOne() runs a reading through the protocol
mjr 77:0b96f6867312 96 // decoders but also hands it back to you. Raw samples are simply "IR ON"
mjr 77:0b96f6867312 97 // and "IR OFF" signals with the time the IR was continuously on or off.
mjr 77:0b96f6867312 98 // The raw samples are useful if you want to build something like a repeater
mjr 77:0b96f6867312 99 // that only has to replicate the physical IR signals without regard to the
mjr 77:0b96f6867312 100 // underlying data bits. Raw signals are obviously also very useful if you
mjr 77:0b96f6867312 101 // want to analyze an unknown protocol and figure out how to write a new
mjr 77:0b96f6867312 102 // encoder/decoder for it. One thing that raw signals aren't great for,
mjr 77:0b96f6867312 103 // somewhat counterintuitively, is for building a learning remote. Many of
mjr 77:0b96f6867312 104 // the protocols have special ways of handling repeated codes (e.g., when
mjr 77:0b96f6867312 105 // holding down a key) that make verbatim repetition of a signal problematic
mjr 77:0b96f6867312 106 // for learning remote use. If you just repeat a raw code, the receiver
mjr 77:0b96f6867312 107 // might be confused into thinking that one key press looks like several
mjr 77:0b96f6867312 108 // key presses, or vice versa. It's better when possible to decode a signal
mjr 77:0b96f6867312 109 // into a recognized protocol, store the decoded bit data rather than the
mjr 77:0b96f6867312 110 // raw signals as the "learned" code, and then reconstruct the appropriate
mjr 77:0b96f6867312 111 // signal for transmission by re-encoding the learned bit code using the
mjr 77:0b96f6867312 112 // protocol's known structure.
mjr 77:0b96f6867312 113 //
mjr 77:0b96f6867312 114 //
mjr 77:0b96f6867312 115 // Internal architecture
mjr 77:0b96f6867312 116 //
mjr 77:0b96f6867312 117 // The IRReceiver object is simply a coordinator that manages the sensor
mjr 77:0b96f6867312 118 // hardware interface, reads the raw signals from the sensor, and passes
mjr 77:0b96f6867312 119 // the raw signals to the protocol objects for decoding. For each protocol
mjr 77:0b96f6867312 120 // we recognize, we define a specialized handler class for the protocol.
mjr 77:0b96f6867312 121 // Each protocol handler implements a state machine for decoding signals
mjr 77:0b96f6867312 122 // using its protocol. When IRReceiver reads a raw signal, it simply passes
mjr 77:0b96f6867312 123 // it to each of the protocol handlers in turn. They all operate as
mjr 77:0b96f6867312 124 // independent state machines, so in effect we have specialized receivers
mjr 77:0b96f6867312 125 // for all of the protocols operating in parallel, all eavesdropping on the
mjr 77:0b96f6867312 126 // same incoming stream of signals. When one of the protocol handlers
mjr 77:0b96f6867312 127 // successfully decodes a complete "command" (a key press on a remote, in
mjr 77:0b96f6867312 128 // most cases), it adds the command to our queue, using a universal
mjr 77:0b96f6867312 129 // representation that we define. Clients can then read the incoming
mjr 77:0b96f6867312 130 // commands from the queue without worrying about the raw signal details.
mjr 77:0b96f6867312 131 //
mjr 77:0b96f6867312 132 // It might sound chaotic to have all of these different protocol decoders
mjr 77:0b96f6867312 133 // working on the same data at the same time, but in practice the various
mjr 77:0b96f6867312 134 // protocols have enough internal structure that only the "right" handler
mjr 77:0b96f6867312 135 // will be able to do anything with a given signal, and the rest will just
mjr 77:0b96f6867312 136 // ignore it, and bide them time until something shows up that they can make
mjr 77:0b96f6867312 137 // sense of. It might also sound like a lot of overhead, but in practice
mjr 77:0b96f6867312 138 // it's very lightweight: it takes about 4% CPU to service the decoding
mjr 77:0b96f6867312 139 // process while a signal is actually coming in, and essentially 0% when
mjr 77:0b96f6867312 140 // the IR airwaves are silent. What's more, that 4% CPU time is all in
mjr 77:0b96f6867312 141 // application context, not in interrupt context, so it doesn't contribute
mjr 77:0b96f6867312 142 // any latency to any other hardware interrupts you need to handle in your
mjr 77:0b96f6867312 143 // application.
mjr 77:0b96f6867312 144 //
mjr 77:0b96f6867312 145 // The individual protocol state machines are all very simple, typically
mjr 77:0b96f6867312 146 // doing just a few integer compares on the incoming timing data per signal.
mjr 77:0b96f6867312 147 // They also require very little state, usually on the order of a few 'int's
mjr 77:0b96f6867312 148 // per decoder, which translates to a small RAM footprint. The decoders
mjr 77:0b96f6867312 149 // operate incrementally and decode in near real time, so decoded commands
mjr 77:0b96f6867312 150 // appear essentially at the same time that their signals finish.
mjr 77:0b96f6867312 151 //
mjr 77:0b96f6867312 152 // Note that, unlike some other MCU IR libraries, we don't any have sort
mjr 77:0b96f6867312 153 // of global receiver state. In particular, we don't try to guess about
mjr 77:0b96f6867312 154 // message boundaries globally. All of the boundary detection and protocol
mjr 77:0b96f6867312 155 // state is in the individual protocol decoders. That eliminates the need
mjr 77:0b96f6867312 156 // for heuristics or special cases to guess about what "usually" indicates
mjr 77:0b96f6867312 157 // a message boundary across all protocols. There are enough special cases
mjr 77:0b96f6867312 158 // to make such guesses problematic, which becomes apparent if you look at
mjr 77:0b96f6867312 159 // the code in libraries that work that way. Since we don't need to know
mjr 77:0b96f6867312 160 // about message boundaries globally, we don't need to make such guesses or
mjr 77:0b96f6867312 161 // apply such special cases. We simply deal in the raw pulses and let
mjr 77:0b96f6867312 162 // each decoder separately judge for itself where its own message boundaries
mjr 77:0b96f6867312 163 // are. This might seem odd, because the implication is that one decoder
mjr 77:0b96f6867312 164 // might think we're in the middle of a message while another decoder
mjr 77:0b96f6867312 165 // thinks we're on a boundary. But that's just fine, and it's exactly
mjr 77:0b96f6867312 166 // why we shouldn't be making those judgments globally: if two protocols
mjr 77:0b96f6867312 167 // have contradictory rules like that, the way to reconcile it is to accept
mjr 77:0b96f6867312 168 // that there really is no correct global judgment, and leave it to the
mjr 77:0b96f6867312 169 // decoders to track their own states independently.
mjr 77:0b96f6867312 170 //
mjr 77:0b96f6867312 171 // We receive signals from the sensor via interrupts on the input GPIO pin.
mjr 77:0b96f6867312 172 // This allows for the most accurate timing possible, which is important
mjr 77:0b96f6867312 173 // because IR coding is entirely in the signal timing. Interrupts gives us
mjr 77:0b96f6867312 174 // much more accurate timing than polling would for obvious reasons. As
mjr 77:0b96f6867312 175 // mentioned above, though, we try to minimize the time we spend in IRQ
mjr 77:0b96f6867312 176 // context, since time spent in one interrupt handler translates to added
mjr 77:0b96f6867312 177 // latency for any other interrupts that occur at the same time. To
mjr 77:0b96f6867312 178 // accomplish this, the interrupt handlers don't do any decoding at all.
mjr 77:0b96f6867312 179 // They simply add the incoming signal data to an internal queue and then
mjr 77:0b96f6867312 180 // return. We do the decoding work back in application context, by having
mjr 77:0b96f6867312 181 // the main loop call our process() routine periodically. This takes signal
mjr 77:0b96f6867312 182 // readings off of the queue and runs them through the decoders. This
mjr 77:0b96f6867312 183 // introduces a small amount of lag time between physically receiving a
mjr 77:0b96f6867312 184 // signal and decoding it, but the lag time is only on the order of the
mjr 77:0b96f6867312 185 // main loop run time. In most MCU applications this is a very short
mjr 77:0b96f6867312 186 // time, perhaps only microseconds or perhaps as long as a few millseconds.
mjr 77:0b96f6867312 187 // But in any case it's almost always so short that a user can't perceive
mjr 77:0b96f6867312 188 // the delay, so for all practical purposes decoding is done in real time.
mjr 77:0b96f6867312 189 //
mjr 77:0b96f6867312 190 //
mjr 77:0b96f6867312 191 // How IR remotes work in general
mjr 77:0b96f6867312 192 //
mjr 77:0b96f6867312 193 // IR remote controls work by transmitting timed pulses of infrared light.
mjr 77:0b96f6867312 194 // These pulses are modulated in two ways: first, with a "carrier", which
mjr 77:0b96f6867312 195 // is a PWM signal operating at a fixed, relatively high frequency; and
mjr 77:0b96f6867312 196 // second, with a lower frequency data signal superimposed on the PWM
mjr 77:0b96f6867312 197 // signal. (And I suppose you could say there's a third layer of
mjr 77:0b96f6867312 198 // modulation in the IR light itself, since that's electromagnetic wave
mjr 77:0b96f6867312 199 // operating at an even higher frequency of around 300 THz.)
mjr 77:0b96f6867312 200 //
mjr 77:0b96f6867312 201 // Carrier: The PWM carrier uses a fixed frequency, usually around 40kHz.
mjr 77:0b96f6867312 202 // The carrier doesn't encode any data, since it's just constant fixed-length
mjr 77:0b96f6867312 203 // pulses. Its function, rather, is to provide a regular oscillating signal
mjr 77:0b96f6867312 204 // that receivers can use to distinguish data signals from ambient light.
mjr 77:0b96f6867312 205 // This is necessary because the IR light wavelengths are also contained
mjr 77:0b96f6867312 206 // in sunlight and ordinary household lighting. (Fluourescent lights even
mjr 77:0b96f6867312 207 // has their own characteristic oscillating frequencies in the IR band, so
mjr 77:0b96f6867312 208 // the receiver not only has to distinguish the signal from constant
mjr 77:0b96f6867312 209 // amgient light levels but also from other types of oscillating light
mjr 77:0b96f6867312 210 // levels. The PWM carrier frequencies used in remotes are chosen based
mjr 77:0b96f6867312 211 // on the practical need to be distinguishable from these sort of
mjr 77:0b96f6867312 212 // common household interference sources.) Receivers can separate the
mjr 77:0b96f6867312 213 // an oscillating PWM signal at a particular frequency from other signals
mjr 77:0b96f6867312 214 // through a process known as demodulation, which is the same mechanism
mjr 77:0b96f6867312 215 // that radio receivers use to pluck signals from the jumble of background
mjr 77:0b96f6867312 216 // noise in the radio spectrum.
mjr 77:0b96f6867312 217 //
mjr 77:0b96f6867312 218 // For our purposes, we don't worry about demodulation in the software,
mjr 77:0b96f6867312 219 // since the sensor hardware does that part of the job. Each type of sensor
mjr 77:0b96f6867312 220 // is designed to demodulate a particular carrier frequency, so you should
mjr 77:0b96f6867312 221 // choose a sensor based on the types of remotes you plan to use it with.
mjr 77:0b96f6867312 222 // Most CE manufacturers have more or less standardized on 38kHz, which is
mjr 77:0b96f6867312 223 // why we recommend the TSOP384xx series. Not everyone is at exactly 38kHz,
mjr 77:0b96f6867312 224 // but the TSOP seems perfectly happy to demodulate signals at nearby
mjr 77:0b96f6867312 225 // frequencies, so it's a good universal choice for most remotes you're
mjr 77:0b96f6867312 226 // likely to find at home.
mjr 77:0b96f6867312 227 //
mjr 77:0b96f6867312 228 // Data signal: The data signal is superimposed on the PWM carrier by
mjr 77:0b96f6867312 229 // turning the PWM'ed IR source on and off at a lower, variable frequency.
mjr 77:0b96f6867312 230 // These longer on/off pulses are of different lengths. The data bits are
mjr 77:0b96f6867312 231 // encoded in the varying lengths, although there's no one true way of
mjr 77:0b96f6867312 232 // doing this. Each protocol has its own way of representing bits as
mjr 77:0b96f6867312 233 // combinations of on times and off times, which we'll come to shortly.
mjr 77:0b96f6867312 234 //
mjr 77:0b96f6867312 235 // "On" pulses are called "marks", and "off" pulses are called "spaces".
mjr 77:0b96f6867312 236 // The terms come from wired asynchronous protocols, which share many
mjr 77:0b96f6867312 237 // properties with IR signals at this level.
mjr 77:0b96f6867312 238 //
mjr 77:0b96f6867312 239 // Note that each pulse has to be long enough to contain some minimum
mjr 77:0b96f6867312 240 // number (maybe 5-10) of PWM pulses, because otherwise the demodulator
mjr 77:0b96f6867312 241 // wouldn't be able to detect the presence or absence of the underlying
mjr 77:0b96f6867312 242 // PWM pulses. This makes IR remote codes fairly slow in terms of data
mjr 77:0b96f6867312 243 // rate, since the absolute minimum time per bit is the time in the shortest
mjr 77:0b96f6867312 244 // data pulse. Most codings actually use at least two pulses per bit for
mjr 77:0b96f6867312 245 // the sake of signal integrity, so the effective data rate lower still.
mjr 77:0b96f6867312 246 // Fortunately, this is all rather unimportant, since IR remotes don't
mjr 77:0b96f6867312 247 // need a very high data rate. They're mostly used to transmit button
mjr 77:0b96f6867312 248 // presses made by hand by a human user, which are at a fairly low rate
mjr 77:0b96f6867312 249 // to start with; plus, the amount of data per button is minuscule,
mjr 77:0b96f6867312 250 // usually from 8 to 32 bits.
mjr 77:0b96f6867312 251 //
mjr 77:0b96f6867312 252 // Encodings
mjr 77:0b96f6867312 253 //
mjr 77:0b96f6867312 254 // The timing of the marks and spaces carries the information, but exactly
mjr 77:0b96f6867312 255 // how it does this is a whole separate matter, known as an encoding. An
mjr 77:0b96f6867312 256 // encoding is a mapping from '0' and '1' bits to a pattern of marks and
mjr 77:0b96f6867312 257 // spaces, and vice versa. At first glance, it might seem that you could
mjr 77:0b96f6867312 258 // just use a mark as a '1' and a space as a '0', and in fact some protocols
mjr 77:0b96f6867312 259 // do something like this. But that simple approach has some big drawbacks
mjr 77:0b96f6867312 260 // arising from the lack of a shared clock between sender and receiver.
mjr 77:0b96f6867312 261 // Most encodings therefore do something to embed a timing signal of some
mjr 77:0b96f6867312 262 // sort within the data signal, by using the lengths of the pulses to encode
mjr 77:0b96f6867312 263 // bits rather than just the presence of the pulses.
mjr 77:0b96f6867312 264 //
mjr 77:0b96f6867312 265 // There are probably an infinite number of possible ways to do this in
mjr 77:0b96f6867312 266 // principle. Fortunately, the CE companies have only put a finite number
mjr 77:0b96f6867312 267 // of them into practice. In fact, it seems that we can cover practically
mjr 77:0b96f6867312 268 // all of the remotes out there by considering a small handful of encoding
mjr 77:0b96f6867312 269 // schemes. Here are the main ones, and the ones we can use in this
mjr 77:0b96f6867312 270 // receiver library:
mjr 77:0b96f6867312 271 //
mjr 77:0b96f6867312 272 // - Async bit stream. This is basically the IR equivalent of a wired
mjr 77:0b96f6867312 273 // UART. Each code word consists of a fixed number of bits. Each bit
mjr 77:0b96f6867312 274 // is represented by IR ON for '1' and IR OFF for '0', transmitted for
mjr 77:0b96f6867312 275 // a fixed time length. To transmit, simply turn the IR on and off in
mjr 77:0b96f6867312 276 // sequence for the fixed bit time per bit. To receive and decode,
mjr 77:0b96f6867312 277 // observe whether the IR signal is on or off in each time window.
mjr 77:0b96f6867312 278 // This type of protocol looks simple, but it presents some difficulties
mjr 77:0b96f6867312 279 // in implementation, because it doesn't provide any cues embedded in
mjr 77:0b96f6867312 280 // the IR signal to help the receiver synchronize with the sender or
mjr 77:0b96f6867312 281 // recognize the boundaries of code words, as all of the other common
mjr 77:0b96f6867312 282 // protocols do. That might be why this class seems to be rarely used
mjr 77:0b96f6867312 283 // in real applications. Protocols based on simple async bits usually
mjr 77:0b96f6867312 284 // add something at the protocol level that helps the reciever detect
mjr 77:0b96f6867312 285 // word boundaries and check signal integrity.
mjr 77:0b96f6867312 286 //
mjr 77:0b96f6867312 287 // - Pulse distance coding, also known as space length coding. In this
mjr 77:0b96f6867312 288 // scheme, marks are all of equal length, but spaces come in two lengths,
mjr 77:0b96f6867312 289 // A and B, where B is much longer than A (usually twice as long, but it
mjr 77:0b96f6867312 290 // could be even longer). A encodes 0 and B encodes 1. The marks serve
mjr 77:0b96f6867312 291 // as regular clock signals, allowing the receiver to keep in sync with
mjr 77:0b96f6867312 292 // the sender, and the long and short space times (A and B) are different
mjr 77:0b96f6867312 293 // enough that the receiver can easily distinguish them reliably, even
mjr 77:0b96f6867312 294 // with a low-precision clock. This scheme is probably the most widely
mjr 77:0b96f6867312 295 // used in CE products, because it's the encoding used by the NEC
mjr 77:0b96f6867312 296 // protocol, which most Japanese CE companies use.
mjr 77:0b96f6867312 297 //
mjr 77:0b96f6867312 298 // - Pulse length coding, also known as mark length coding. This is simply
mjr 77:0b96f6867312 299 // the inverse of pulse distance coding: spaces are all of equal length,
mjr 77:0b96f6867312 300 // and marks come in two lengths, with the short mark encoding 0 and the
mjr 77:0b96f6867312 301 // long mark encoding 1. This is practically the same in all meaningful
mjr 77:0b96f6867312 302 // ways as the space length coding; the only reason both kinds exist is
mjr 77:0b96f6867312 303 // probably that either someone had a bad case of NIH or they wanted to
mjr 77:0b96f6867312 304 // avoid paying a patent royalty. Mark length coding is the scheme Sony
mjr 77:0b96f6867312 305 // uses (in their SIRCS protocol).
mjr 77:0b96f6867312 306 //
mjr 77:0b96f6867312 307 // - Manchester coding. The message is divided into time slices of
mjr 77:0b96f6867312 308 // equal size, one bit per slice. Within each slice, the IR is on for
mjr 77:0b96f6867312 309 // half the window and off for half the window. The 0 and 1 bits are
mjr 77:0b96f6867312 310 // encoded by the direction of the transition: if a bit window starts
mjr 77:0b96f6867312 311 // with a mark (IR ON) and ends with a space (IR OFF), it's a '1'; if it
mjr 77:0b96f6867312 312 // starts with a space and ends with a mark, it's a '0'. Or vice versa.
mjr 77:0b96f6867312 313 // Each mark or space therefore lasts for either 1/2 or 1 bit time
mjr 77:0b96f6867312 314 // length, never longer. This makes it fairly easy for the receiver to
mjr 77:0b96f6867312 315 // distinguish the two time lengths, even with a fairly low-precision
mjr 77:0b96f6867312 316 // clock, since they're so different. It's also easy for the receiver
mjr 77:0b96f6867312 317 // to distinguish each bit, since there's always at least one transition
mjr 77:0b96f6867312 318 // (mark to space or space to mark) per bit. What's more, '0' and '1'
mjr 77:0b96f6867312 319 // bits take the same time to transmit (unlike the mark-length and
mjr 77:0b96f6867312 320 // space-length protocols), so every code word (assuming a fixed bit
mjr 77:0b96f6867312 321 // count) takes the same time regardless of the bit values within.
mjr 77:0b96f6867312 322 // Manchester modulation is used in the Philips RC5 and RC6 protocols,
mjr 77:0b96f6867312 323 // which are widely used among European CE companies.
mjr 77:0b96f6867312 324 //
mjr 77:0b96f6867312 325 // Protocols
mjr 77:0b96f6867312 326 //
mjr 77:0b96f6867312 327 // On top of the encoding scheme, there's another level of structure called
mjr 77:0b96f6867312 328 // a protocol. A given protocol uses a given encoding for the data bits,
mjr 77:0b96f6867312 329 // but then also adds some extra structure.
mjr 77:0b96f6867312 330 //
mjr 77:0b96f6867312 331 // For starters, the IR protocols all work in terms of "code words". In
mjr 77:0b96f6867312 332 // computer terms, a code word amounts to a datatype with a fixed number
mjr 77:0b96f6867312 333 // of bits. For example, the NEC protocol uses a 32-bit code word: each
mjr 77:0b96f6867312 334 // button press is represented by a 32-bit transmission. A single key
mjr 77:0b96f6867312 335 // press usually maps to a single code word, although not always; the
mjr 77:0b96f6867312 336 // Pioneer protocol, for example, transmits two words for certain buttons,
mjr 77:0b96f6867312 337 // using a special "shift" code for the first word to give a second meaning
mjr 77:0b96f6867312 338 // to the second word, to extend the possible number of commands that would
mjr 77:0b96f6867312 339 // be otherwise limited by the number of bits in a single code word.
mjr 77:0b96f6867312 340
mjr 77:0b96f6867312 341 // Second, most of the IR protocols add special non-data signals that
mjr 77:0b96f6867312 342 // mark the beginning and/or end of a code word. These are usually just
mjr 77:0b96f6867312 343 // extra-long marks or spaces, which are distinguishable from the marks
mjr 77:0b96f6867312 344 // and spaces within a code word because they're too long to be valid in
mjr 77:0b96f6867312 345 // the data encoding scheme. These are important to reliable communication
mjr 77:0b96f6867312 346 // because the sender and receiver don't have any other way to share state
mjr 77:0b96f6867312 347 // with each other. Consider what would happen if someone walked in the
mjr 77:0b96f6867312 348 // way while you were transmitting a remote code: the receiver would miss
mjr 77:0b96f6867312 349 // at least a few data bits, so it would be out of sync with the sender.
mjr 77:0b96f6867312 350 // If there weren't some way to distinguish the start of a code word from
mjr 77:0b96f6867312 351 // the IR pulses themselves, the receiver would now be permanently out
mjr 77:0b96f6867312 352 // of sync from the sender by however many bits it missed. But with the
mjr 77:0b96f6867312 353 // special "header" code, the receiver can sync up again as soon as the
mjr 77:0b96f6867312 354 // next code word starts, since it can tell from the timing that the
mjr 77:0b96f6867312 355 // header can't possibly be a bit in the middle of a code word.
mjr 77:0b96f6867312 356
mjr 77:0b96f6867312 357
mjr 77:0b96f6867312 358 #ifndef _IRRECEIVER_H_
mjr 77:0b96f6867312 359 #define _IRRECEIVER_H_
mjr 77:0b96f6867312 360
mjr 77:0b96f6867312 361 #include <mbed.h>
mjr 77:0b96f6867312 362
mjr 77:0b96f6867312 363 #include "IRRemote.h"
mjr 77:0b96f6867312 364 #include "IRCommand.h"
mjr 77:0b96f6867312 365 #include "circbuf.h"
mjr 77:0b96f6867312 366
mjr 77:0b96f6867312 367 // IR receiver protocol interface. This contains functions that we only
mjr 77:0b96f6867312 368 // want to make accessible to the protocol decoders.
mjr 77:0b96f6867312 369 class IRRecvProIfc
mjr 77:0b96f6867312 370 {
mjr 77:0b96f6867312 371 public:
mjr 77:0b96f6867312 372 // write a command to the command queue
mjr 77:0b96f6867312 373 void writeCommand(IRCommand &cmd) { commands.write(cmd); }
mjr 77:0b96f6867312 374
mjr 77:0b96f6867312 375 protected:
mjr 77:0b96f6867312 376 // Decoded command queue. The protocol handlers add commands here
mjr 77:0b96f6867312 377 // as soon as they decode them.
mjr 77:0b96f6867312 378 CircBuf<IRCommand, 8> commands;
mjr 77:0b96f6867312 379 };
mjr 77:0b96f6867312 380
mjr 77:0b96f6867312 381
mjr 77:0b96f6867312 382 // IR Remote Receiver
mjr 77:0b96f6867312 383 class IRReceiver : protected IRRecvProIfc
mjr 77:0b96f6867312 384 {
mjr 77:0b96f6867312 385 public:
mjr 77:0b96f6867312 386 // Construct a receiver with the given data input pin. The receiver
mjr 77:0b96f6867312 387 // is initially disabled. To start receiving signals, call enable().
mjr 77:0b96f6867312 388 //
mjr 77:0b96f6867312 389 // Choose a raw buffer size according to the longest iteration time
mjr 77:0b96f6867312 390 // for your main application loop between the required periodic calls
mjr 77:0b96f6867312 391 // to our process() function. The interrupt handlers write pulse times
mjr 77:0b96f6867312 392 // to the raw buffer as the pulses arrive, and these are held in the
mjr 77:0b96f6867312 393 // buffer until they're removed by process(). The raw buffer only needs
mjr 77:0b96f6867312 394 // to be big enough for the "backlog" that occurs between the real-time
mjr 77:0b96f6867312 395 // incoming signals and the main loop's processing calls. The fastest
mjr 77:0b96f6867312 396 // IR pulses are about 250us long, so size the buffer according to how
mjr 77:0b96f6867312 397 // many 250us intervals will occur in the worst case, that is, the
mjr 77:0b96f6867312 398 // longest main loop iteration. If the main loop always runs in 2.5ms
mjr 77:0b96f6867312 399 // or shorter, that means you need about a 10-element buffer. To be
mjr 77:0b96f6867312 400 // conservative, size it at perhaps 2x the expected maximum.
mjr 77:0b96f6867312 401 //
mjr 77:0b96f6867312 402 IRReceiver(PinName rxpin, size_t rawBufCount);
mjr 77:0b96f6867312 403
mjr 77:0b96f6867312 404 // Destructor
mjr 77:0b96f6867312 405 ~IRReceiver();
mjr 77:0b96f6867312 406
mjr 77:0b96f6867312 407 // Optionally connect to a transmitter, to suppress reception while
mjr 77:0b96f6867312 408 // we're transmitting. This prevents spuriously receiving our own
mjr 77:0b96f6867312 409 // transmissions, if our IR LED and sensor are physically close enough
mjr 77:0b96f6867312 410 // to one another that our sensor would pick up light from our LED.
mjr 77:0b96f6867312 411 // If the two are physically isolated so that we can't receive our
mjr 77:0b96f6867312 412 // own transmissions, it's not necessary to connect the transmitter
mjr 77:0b96f6867312 413 // here, as there's no restriction on the software side on sending
mjr 77:0b96f6867312 414 // and receiving simultaneously - the suppression is only needed to
mjr 77:0b96f6867312 415 // avoid self-interference with the physical IR signals.
mjr 77:0b96f6867312 416 void setTransmitter(class IRTransmitter *transmitter)
mjr 77:0b96f6867312 417 {
mjr 77:0b96f6867312 418 this->transmitter = transmitter;
mjr 77:0b96f6867312 419 }
mjr 77:0b96f6867312 420
mjr 77:0b96f6867312 421 // Enable/disable our interrupt handlers. If the main program
mjr 77:0b96f6867312 422 // doesn't need IR input, it can disable the receiver so that
mjr 77:0b96f6867312 423 // it doesn't consume any CPU time handling interrupts.
mjr 77:0b96f6867312 424 void enable();
mjr 77:0b96f6867312 425 void disable();
mjr 77:0b96f6867312 426
mjr 77:0b96f6867312 427 // Read a command. Returns true if a command was available, filling
mjr 77:0b96f6867312 428 // in 'cmd'. Returns false (without blocking) if no commands are
mjr 77:0b96f6867312 429 // available.
mjr 77:0b96f6867312 430 bool readCommand(IRCommand &cmd) { return commands.read(cmd); }
mjr 77:0b96f6867312 431
mjr 77:0b96f6867312 432 // Is a command ready to read?
mjr 77:0b96f6867312 433 bool isCommandReady() { return commands.readReady(); }
mjr 77:0b96f6867312 434
mjr 77:0b96f6867312 435 // Process signals received. The application main loop must call this
mjr 77:0b96f6867312 436 // as frequently as possible to process incoming signals from the raw
mjr 77:0b96f6867312 437 // buffer. This processes all samples in the raw buffer before
mjr 77:0b96f6867312 438 // returning.
mjr 77:0b96f6867312 439 void process();
mjr 77:0b96f6867312 440
mjr 77:0b96f6867312 441 // Process and retrieve one raw pulse. The application main loop can
mjr 77:0b96f6867312 442 // optionally call this, instead of process(), if it wants to retrieve
mjr 77:0b96f6867312 443 // each raw sample for its own purposes in addition to running them
mjr 77:0b96f6867312 444 // through the protocol state machines. If no sample is available, we
mjr 77:0b96f6867312 445 // immediately return false - the routine doesn't block waiting for a
mjr 77:0b96f6867312 446 // sample. If a sample is available, we fill in 'sample' with the pulse
mjr 77:0b96f6867312 447 // time in microseconds, and set 'mark' to true if the sample was a mark,
mjr 77:0b96f6867312 448 // false if it's a space.
mjr 77:0b96f6867312 449 //
mjr 77:0b96f6867312 450 // To use this instead of process(), on each main loop iteration, call
mjr 77:0b96f6867312 451 // this function in an inner loop until it returns false. That'll ensure
mjr 77:0b96f6867312 452 // that all pending samples have been processed through the protocol
mjr 77:0b96f6867312 453 // state machines and that maximum buffer space is available for the next
mjr 77:0b96f6867312 454 // main loop iteration.
mjr 77:0b96f6867312 455 bool processOne(uint32_t &sample, bool &mark);
mjr 77:0b96f6867312 456
mjr 77:0b96f6867312 457 // Process and retrieve one raw pulse. This works the same as the
mjr 77:0b96f6867312 458 // two-argument version above, but returns the sample in our internal
mjr 77:0b96f6867312 459 // format: the sample value is a time reading in 2us units, and the low
mjr 77:0b96f6867312 460 // bit is 1 for a mark, 0 for a space. To convert to a time reading in
mjr 77:0b96f6867312 461 // microseconds, mask out the low bit and multiply by 2.
mjr 77:0b96f6867312 462 bool processOne(uint16_t &sample);
mjr 77:0b96f6867312 463
mjr 77:0b96f6867312 464 // Maximum pulse length in microseconds. Anything longer will simply
mjr 77:0b96f6867312 465 // be represented with this value. This is long enough that anything
mjr 77:0b96f6867312 466 // longer has equivalent meaning in any of our protocols. Generally,
mjr 77:0b96f6867312 467 // space longer than this will only occur in a silent interval between
mjr 77:0b96f6867312 468 // transmissions (that is, while no one is sending any codes), and a
mjr 77:0b96f6867312 469 // mark longer than this could only be interference or noise.
mjr 77:0b96f6867312 470 //
mjr 77:0b96f6867312 471 // This value should be chosen so that it's high enough to be readily
mjr 77:0b96f6867312 472 // distinguishable (in terms of our error tolerance) from the longest
mjr 77:0b96f6867312 473 // *meaningful* space or pulse in any protocol we need to handle, but
mjr 77:0b96f6867312 474 // not much higher than that. It shouldn't be too long because it has
mjr 77:0b96f6867312 475 // a role as an inactivity timeout on receive: we can't always know
mjr 77:0b96f6867312 476 // that a signal has ended until there's inactivity for this amount
mjr 77:0b96f6867312 477 // of time. If the timeout is too long, it can become noticable as
mjr 77:0b96f6867312 478 // lag time in recognizing signals. In practice, the longest gap time
mjr 77:0b96f6867312 479 // between repeating signals in commonly used protocols is in the
mjr 77:0b96f6867312 480 // neighboorhood of 100ms.
mjr 77:0b96f6867312 481 //
mjr 77:0b96f6867312 482 // This value is chosen to be the largest we can fit into a 16-bit
mjr 77:0b96f6867312 483 // int, taking into account our 2X scaling and our use of the low bit
mjr 77:0b96f6867312 484 // for a mark/space indicator. That leaves us with 14 bits and 2X scale.
mjr 77:0b96f6867312 485 static const uint32_t MAX_PULSE = 131068;
mjr 77:0b96f6867312 486
mjr 77:0b96f6867312 487 private:
mjr 77:0b96f6867312 488 // Input pin - reads from a TSOP384xx or similar sensor. Any
mjr 77:0b96f6867312 489 // sensor should work that demodulates the carrier wave and
mjr 77:0b96f6867312 490 // gives us an active-low input on the pin.
mjr 77:0b96f6867312 491 InterruptIn pin;
mjr 77:0b96f6867312 492
mjr 77:0b96f6867312 493 // IR raw data buffer. The interrupt handlers store the pulse
mjr 77:0b96f6867312 494 // timings here as they arrive, and the process() routine reads from
mjr 77:0b96f6867312 495 // the buffer.
mjr 77:0b96f6867312 496 //
mjr 77:0b96f6867312 497 // Samples here are limited to 16 bits, so the longest time that
mjr 77:0b96f6867312 498 // can be represented is 65535us. Anything longer is capped to this.
mjr 77:0b96f6867312 499 //
mjr 77:0b96f6867312 500 // To keep track of marks vs spaces, we set the low-order bit of
mjr 77:0b96f6867312 501 // each sample time to 1 for a mark and 0 for a space. That means
mjr 77:0b96f6867312 502 // that the times are only good to 2us precision, but that's plenty
mjr 77:0b96f6867312 503 // of precision for all of the IR protocols, since the shortest time
mjr 77:0b96f6867312 504 // bases are around 250us.
mjr 77:0b96f6867312 505 CircBufV<uint16_t> rawbuf;
mjr 77:0b96f6867312 506
mjr 77:0b96f6867312 507 // Pulse timer. We reset the timer at the start of each pulse, so
mjr 77:0b96f6867312 508 // it tells us the duration thus far of the current pulse at any
mjr 77:0b96f6867312 509 // given time. We stop the timer (without resetting) any time a
mjr 77:0b96f6867312 510 // pulse reaches the maximum length, to ensure that the timer never
mjr 77:0b96f6867312 511 // rolls over, even in the indefinite gap between codes.
mjr 77:0b96f6867312 512 Timer pulseTimer;
mjr 77:0b96f6867312 513
mjr 77:0b96f6867312 514 // flag: the pulse timer has reached IR_MAX_PULSE
mjr 77:0b96f6867312 515 bool pulseAtMax;
mjr 77:0b96f6867312 516
mjr 77:0b96f6867312 517 // current pulse state: mark = 1, space = 0
mjr 77:0b96f6867312 518 bool pulseState;
mjr 77:0b96f6867312 519
mjr 77:0b96f6867312 520 // start the pulse timers with the new pulse state (1=mark, 0=space)
mjr 77:0b96f6867312 521 void startPulse(bool newPulseState);
mjr 77:0b96f6867312 522
mjr 77:0b96f6867312 523 // end the current pulse, checking that the pulse state matches the
mjr 77:0b96f6867312 524 // current state
mjr 77:0b96f6867312 525 void endPulse(bool lastPulseState);
mjr 77:0b96f6867312 526
mjr 77:0b96f6867312 527 // process a pulse through our protocol handlers
mjr 77:0b96f6867312 528 void processProtocols(uint32_t t, bool mark);
mjr 77:0b96f6867312 529
mjr 77:0b96f6867312 530 // rise and fall interrupt handlers for the input pin
mjr 77:0b96f6867312 531 void fall(void);
mjr 77:0b96f6867312 532 void rise(void);
mjr 77:0b96f6867312 533
mjr 77:0b96f6867312 534 // timeout for time-limited states
mjr 77:0b96f6867312 535 Timeout timeout;
mjr 77:0b96f6867312 536
mjr 77:0b96f6867312 537 // timeout handler for a pulse (mark or space)
mjr 77:0b96f6867312 538 void pulseTimeout(void);
mjr 77:0b96f6867312 539
mjr 77:0b96f6867312 540 // Connected transmitter. If this is set, we'll suppress reception
mjr 77:0b96f6867312 541 // while the transmitter is sending a signal, to avoid receiving our
mjr 77:0b96f6867312 542 // own transmissions.
mjr 77:0b96f6867312 543 class IRTransmitter *transmitter;
mjr 77:0b96f6867312 544 };
mjr 77:0b96f6867312 545
mjr 77:0b96f6867312 546 #endif