Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
IRRemote/IRTransmitter.h@116:7a67265d7c19, 2021-10-01 (annotated)
- Committer:
- arnoz
- Date:
- Fri Oct 01 08:19:46 2021 +0000
- Revision:
- 116:7a67265d7c19
- Parent:
- 77:0b96f6867312
- Correct information regarding your last merge
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 77:0b96f6867312 | 1 | // IR Remote Transmitter |
mjr | 77:0b96f6867312 | 2 | // |
mjr | 77:0b96f6867312 | 3 | // This class lets you control an IR emitter LED connected to a GPIO port |
mjr | 77:0b96f6867312 | 4 | // to transmit remote control codes using numerous standard and proprietary |
mjr | 77:0b96f6867312 | 5 | // protocols. You can use this to send remote codes to any device with |
mjr | 77:0b96f6867312 | 6 | // a typical IR remote, such as A/V equipment, home automation devices, etc. |
mjr | 77:0b96f6867312 | 7 | // You can also use this with the companion IR Receiver class running on |
mjr | 77:0b96f6867312 | 8 | // a separate KL25Z to send IR commands to the other device. |
mjr | 77:0b96f6867312 | 9 | // |
mjr | 77:0b96f6867312 | 10 | // We do all of our transmissions with specific protocols rather than raw |
mjr | 77:0b96f6867312 | 11 | // IR signals. Every remote control has its own way of representing a |
mjr | 77:0b96f6867312 | 12 | // string of data bits as a series of timed IR flashes. The exact mapping |
mjr | 77:0b96f6867312 | 13 | // between data bits and IR flashes is the protocol. There are some quasi |
mjr | 77:0b96f6867312 | 14 | // industry standard protocols, where several companies use the same format |
mjr | 77:0b96f6867312 | 15 | // for their codes, but there are many proprietary protocols as well. We |
mjr | 77:0b96f6867312 | 16 | // have handlers for the most widely used protocols: NEC, Sony, Philips RC5 |
mjr | 77:0b96f6867312 | 17 | // and RC6, Pioneer, Panasonic, and several others. If your device isn't |
mjr | 77:0b96f6867312 | 18 | // covered yet, it could probably be added, since we've tried to design |
mjr | 77:0b96f6867312 | 19 | // the system to make it easy to add new protocols. |
mjr | 77:0b96f6867312 | 20 | // |
mjr | 77:0b96f6867312 | 21 | // When you transmit a code, you specify it in terms of the protocol to use |
mjr | 77:0b96f6867312 | 22 | // and the "code" value to send. A "code" is just the data value for a |
mjr | 77:0b96f6867312 | 23 | // particular key on a particular remote control, usually expressed as a |
mjr | 77:0b96f6867312 | 24 | // hex number. There are published tables of codes for many remotes, but |
mjr | 77:0b96f6867312 | 25 | // unfortunately they're not very consistent in how they represent the hex |
mjr | 77:0b96f6867312 | 26 | // code values, so you'll often see the same key represented with different |
mjr | 77:0b96f6867312 | 27 | // hex codes in different published tables. We of course have our own way |
mjr | 77:0b96f6867312 | 28 | // of mapping the hex codes; we've tried to use the format that the original |
mjr | 77:0b96f6867312 | 29 | // manufacturer uses in their tales, if they publish them at all, but these |
mjr | 77:0b96f6867312 | 30 | // may or may not be consistent with what you find in any tables you consult. |
mjr | 77:0b96f6867312 | 31 | // So your best bet for finding the right codes to use here is usually to |
mjr | 77:0b96f6867312 | 32 | // "learn" the codes using our companion class IRReceiver. That class has a |
mjr | 77:0b96f6867312 | 33 | // protocol decoder for each protocol transmitter we can use here, so if you |
mjr | 77:0b96f6867312 | 34 | // set that up and point a remote at it, it will tell you the exact code we |
mjr | 77:0b96f6867312 | 35 | // use for the key. |
mjr | 77:0b96f6867312 | 36 | // |
mjr | 77:0b96f6867312 | 37 | // The transmitter class provides a "virtual remote control" interface. |
mjr | 77:0b96f6867312 | 38 | // This gives you an imaginary remote control keypad, with a set of |
mjr | 77:0b96f6867312 | 39 | // virtual buttons programmed for individual remote control commands. |
mjr | 77:0b96f6867312 | 40 | // You specify the protocol and command code for each virtual button. |
mjr | 77:0b96f6867312 | 41 | // You can use different protocols for different buttons. |
mjr | 77:0b96f6867312 | 42 | // |
mjr | 77:0b96f6867312 | 43 | // |
mjr | 77:0b96f6867312 | 44 | // How to use the software |
mjr | 77:0b96f6867312 | 45 | // |
mjr | 77:0b96f6867312 | 46 | // First, create an instance of IRTransmitter, telling it which pin the |
mjr | 77:0b96f6867312 | 47 | // IR emitter is connected to (see below for wiring instructions) and how |
mjr | 77:0b96f6867312 | 48 | // many virtual remote control keys you want. The pin must be PWM capable. |
mjr | 77:0b96f6867312 | 49 | // |
mjr | 77:0b96f6867312 | 50 | // IRTransmitter *tx = new IRTransmitter(PTC9, 32); |
mjr | 77:0b96f6867312 | 51 | // |
mjr | 77:0b96f6867312 | 52 | // Next, program the virtual remote keys. For each key, set the IR protocol |
mjr | 77:0b96f6867312 | 53 | // to use (an IRPRO_xxx code from IRProtocolID.h), the "ditto" mode (more on |
mjr | 77:0b96f6867312 | 54 | // this below), and the hex code for the command. |
mjr | 77:0b96f6867312 | 55 | // |
mjr | 77:0b96f6867312 | 56 | // // program virtual button #0 with Sony 20-bit code 0x123, no dittos |
mjr | 77:0b96f6867312 | 57 | // tx->programButton(0, IRPRO_SONY20, false, 0x123); |
mjr | 77:0b96f6867312 | 58 | // |
mjr | 77:0b96f6867312 | 59 | // Now you're set up to transmit. In your main loop, decide when it's time |
mjr | 77:0b96f6867312 | 60 | // to transmit a button, such as by monitoring a physical pushbutton via a |
mjr | 77:0b96f6867312 | 61 | // GPIO DigitalIn pin. When you want to transmit a code, just tell the |
mjr | 77:0b96f6867312 | 62 | // transmitter that your virtual button is pressed, by calling pushButton() |
mjr | 77:0b96f6867312 | 63 | // with the virtual button ID (corresponding to a virtual button ID you |
mjr | 77:0b96f6867312 | 64 | // previously programmed wtih programButton()) and a status of 'true', |
mjr | 77:0b96f6867312 | 65 | // meaning that the button is pressed. |
mjr | 77:0b96f6867312 | 66 | // |
mjr | 77:0b96f6867312 | 67 | // tx->pushButton(0, true); // push virtual button #0 |
mjr | 77:0b96f6867312 | 68 | // |
mjr | 77:0b96f6867312 | 69 | // This starts the transmission and returns immediately. The transmission |
mjr | 77:0b96f6867312 | 70 | // proceeds in the background (via timer interrupts), so your main loop can |
mjr | 77:0b96f6867312 | 71 | // go about its other business without waiting for the transmission to |
mjr | 77:0b96f6867312 | 72 | // finish. Most remote codes take 50ms to 100ms to transmit, and you don't |
mjr | 77:0b96f6867312 | 73 | // usually want to stall an MCU app for that long. |
mjr | 77:0b96f6867312 | 74 | // |
mjr | 77:0b96f6867312 | 75 | // If a prior transmission is still in progress when you call pushButton(), |
mjr | 77:0b96f6867312 | 76 | // the new transmission doesn't interrupt the previous one. Every code is |
mjr | 77:0b96f6867312 | 77 | // sent as a complete unit to ensure data integrity, so the old one has to |
mjr | 77:0b96f6867312 | 78 | // finish before the new one starts. Some protocols have minimum repeat |
mjr | 77:0b96f6867312 | 79 | // counts, and the transmitter takes this into account as well. For example, |
mjr | 77:0b96f6867312 | 80 | // the Sony protocols require each command to be sent at least three times, |
mjr | 77:0b96f6867312 | 81 | // even if the button is only tapped for a brief instant. So if you send |
mjr | 77:0b96f6867312 | 82 | // a Sony code, a new command won't start transmitting until the last command |
mjr | 77:0b96f6867312 | 83 | // has been sent completely, not just once, but at least three times. |
mjr | 77:0b96f6867312 | 84 | // |
mjr | 77:0b96f6867312 | 85 | // Once the transmitter starts sending the code for a new button, it keeps |
mjr | 77:0b96f6867312 | 86 | // sending the same code on auto-repeat until you either un-press the |
mjr | 77:0b96f6867312 | 87 | // virtual button or press a new virtual button. Handling auto-repeat |
mjr | 77:0b96f6867312 | 88 | // in the transmitter like this has an important benefit, besides just making |
mjr | 77:0b96f6867312 | 89 | // the API simpler: it allows the transmitter to use the proper coding for |
mjr | 77:0b96f6867312 | 90 | // the repeats according to the rules of the protocol. Some protocols use |
mjr | 77:0b96f6867312 | 91 | // a different format for the first code of a key press and auto-repeats |
mjr | 77:0b96f6867312 | 92 | // of the same key. Some protocols also have other repetition features, |
mjr | 77:0b96f6867312 | 93 | // such as "toggle bits" or sequence counters. The protocol handlers use |
mjr | 77:0b96f6867312 | 94 | // the appropriate handling for their protocols, so you only have to think |
mjr | 77:0b96f6867312 | 95 | // in terms of when the virtual buttons are pressed and un-pressed, without |
mjr | 77:0b96f6867312 | 96 | // worrying about whether a toggle bit or a "ditto" code or a sequence |
mjr | 77:0b96f6867312 | 97 | // counter is needed. |
mjr | 77:0b96f6867312 | 98 | // |
mjr | 77:0b96f6867312 | 99 | // When the button is no longer pressed, call pushButton() again with a |
mjr | 77:0b96f6867312 | 100 | // status of 'false': |
mjr | 77:0b96f6867312 | 101 | // |
mjr | 77:0b96f6867312 | 102 | // tx->pushButton(0, false); |
mjr | 77:0b96f6867312 | 103 | // |
mjr | 77:0b96f6867312 | 104 | // Multiple button presses use simple PC keyboard-like semantics. At any |
mjr | 77:0b96f6867312 | 105 | // given time, there can be only one pressed button. When you call |
mjr | 77:0b96f6867312 | 106 | // pushButton(N, true), N becomes the pressed button, which means that the |
mjr | 77:0b96f6867312 | 107 | // previous pressed button (if any) is forgotten. As mentioned above, this |
mjr | 77:0b96f6867312 | 108 | // doesn't cancel the previous transmission if it's still in progress. The |
mjr | 77:0b96f6867312 | 109 | // transmitter continues with the last code until it's finished. When it |
mjr | 77:0b96f6867312 | 110 | // finishes with a code, the transmitter looks to see if the same button is |
mjr | 77:0b96f6867312 | 111 | // still pressed. If so, it starts a new transmission for the same button, |
mjr | 77:0b96f6867312 | 112 | // using the appropriate repeat code. If a new button is pressed, the |
mjr | 77:0b96f6867312 | 113 | // transmitter starts transmitting the new button's code. If no button is |
mjr | 77:0b96f6867312 | 114 | // pressed, the transmitter stops sending and becomes idle until you press |
mjr | 77:0b96f6867312 | 115 | // another button. |
mjr | 77:0b96f6867312 | 116 | // |
mjr | 77:0b96f6867312 | 117 | // Note that button presses aren't queued. Suppose you press button #0 |
mjr | 77:0b96f6867312 | 118 | // (while no other code is being sent): this starts transmitting the code |
mjr | 77:0b96f6867312 | 119 | // for button #0 and returns. Now suppose that a very short time later, |
mjr | 77:0b96f6867312 | 120 | // while that first send is still in progress, you briefly press and release |
mjr | 77:0b96f6867312 | 121 | // button #1. Button #1 will never be sent in this case. When you press |
mjr | 77:0b96f6867312 | 122 | // button #1, the transmitter is still sending the first code, so all it |
mjr | 77:0b96f6867312 | 123 | // does at this point is mark button #1 as the currently pressed button, |
mjr | 77:0b96f6867312 | 124 | // replacing button #0. But as explained above, this doesn't cancel the |
mjr | 77:0b96f6867312 | 125 | // button #0 code transmission in progress. That continues until the |
mjr | 77:0b96f6867312 | 126 | // complete code has been sent. At that point, the transmitter looks to |
mjr | 77:0b96f6867312 | 127 | // see which button is pressed, and discovers that NO button is pressed: |
mjr | 77:0b96f6867312 | 128 | // you already told it button #1 was released. So the transmitter simply |
mjr | 77:0b96f6867312 | 129 | // stops sending and becomes idle. |
mjr | 77:0b96f6867312 | 130 | // |
mjr | 77:0b96f6867312 | 131 | // |
mjr | 77:0b96f6867312 | 132 | // How to determine command codes and the "ditto" mode |
mjr | 77:0b96f6867312 | 133 | // |
mjr | 77:0b96f6867312 | 134 | // Our command codes are expressed as 64-bit integers. The code numbers |
mjr | 77:0b96f6867312 | 135 | // are in essence the data bits transmitted in the IR signal, but the mapping |
mjr | 77:0b96f6867312 | 136 | // between the IR data bits and the 64-bit code value is different for each |
mjr | 77:0b96f6867312 | 137 | // protocol. We've tried to make our codes match the numbers shown in the |
mjr | 77:0b96f6867312 | 138 | // tables published by the respective manufacturers for any given remote, |
mjr | 77:0b96f6867312 | 139 | // but you might also find third-party tables that have completely different |
mjr | 77:0b96f6867312 | 140 | // mappings. The easiest thing to do, really, is to ignore all of that and |
mjr | 77:0b96f6867312 | 141 | // just treat the codes as arbitrary, opaque identifiers, and identify the |
mjr | 77:0b96f6867312 | 142 | // codes for the remote you want to use by "learning" them. That is, set up |
mjr | 77:0b96f6867312 | 143 | // a receiver with our companion class IRReceiver, point your remote at it, |
mjr | 77:0b96f6867312 | 144 | // and see what IRReceiver reports as the decoded value for each button. |
mjr | 77:0b96f6867312 | 145 | // Simply use the same code value for each button when sending. |
mjr | 77:0b96f6867312 | 146 | // |
mjr | 77:0b96f6867312 | 147 | // The "ditto" flag is ignored for most protocols, but it's important for a |
mjr | 77:0b96f6867312 | 148 | // few, such as the various NEC protocols. This tells the sender whether to |
mjr | 77:0b96f6867312 | 149 | // use the protocol's special repeat code for auto-repeats (true), or to send |
mjr | 77:0b96f6867312 | 150 | // send the same key code repeatedly (false). The concept of dittos only |
mjr | 77:0b96f6867312 | 151 | // applies to a few protocols; most protocols just do the obvious thing and |
mjr | 77:0b96f6867312 | 152 | // send the same code repeatedly when you hold down a key. But the NEC |
mjr | 77:0b96f6867312 | 153 | // protocols and a few others have special coding for repeated keys. It's |
mjr | 77:0b96f6867312 | 154 | // important to use the special coding for devices that expect it, because |
mjr | 77:0b96f6867312 | 155 | // it lets them distinguish auto-repeat from multiple key presses, which |
mjr | 77:0b96f6867312 | 156 | // can affect how they respond to certain commands. The tricky part is that |
mjr | 77:0b96f6867312 | 157 | // manufacturers aren't always consistent about using dittos even when it's |
mjr | 77:0b96f6867312 | 158 | // a standard part of the protocol they're using, so you have to determine |
mjr | 77:0b96f6867312 | 159 | // whether or not to use it on a per-device basis. The easiest way to do |
mjr | 77:0b96f6867312 | 160 | // this is just like learning codes: set up a receiever with IRReceiver and |
mjr | 77:0b96f6867312 | 161 | // see what it reports. But this time, you're interested in what happens |
mjr | 77:0b96f6867312 | 162 | // when you hold down a key. You'll always get one ordinary report first, |
mjr | 77:0b96f6867312 | 163 | // but check what happens for the repeats. If IRReceiver reports the same |
mjr | 77:0b96f6867312 | 164 | // code repeatedly, set dittos = false when sending those codes. If the |
mjr | 77:0b96f6867312 | 165 | // repeats have the "ditto bit" set, though, set dittos = true when sending. |
mjr | 77:0b96f6867312 | 166 | // |
mjr | 77:0b96f6867312 | 167 | // |
mjr | 77:0b96f6867312 | 168 | // How to wire an IR emitter |
mjr | 77:0b96f6867312 | 169 | // |
mjr | 77:0b96f6867312 | 170 | // Any IR LED should work as the emitter. I used a Vishay TSAL6400 for my |
mjr | 77:0b96f6867312 | 171 | // reference/testing implementation. The TSAL6400 is quite bright, so it |
mjr | 77:0b96f6867312 | 172 | // should send signals well across fairly large distances. |
mjr | 77:0b96f6867312 | 173 | // |
mjr | 77:0b96f6867312 | 174 | // WARNING! DON'T connect the LED directly to the GPIO pin. KL25Z GPIO |
mjr | 77:0b96f6867312 | 175 | // pins have very low current limits - a typical IR emitter LED draws |
mjr | 77:0b96f6867312 | 176 | // enough current to damage or destroy the KL25Z. You'll need to build a |
mjr | 77:0b96f6867312 | 177 | // simple transistor circuit to interface with the LED. You'll need a |
mjr | 77:0b96f6867312 | 178 | // common small signal NPN transistor (such as a 2222 or 2N4401), a 2.2K |
mjr | 77:0b96f6867312 | 179 | // resistor, the IR LED, of course, and a current-limiting resistor for |
mjr | 77:0b96f6867312 | 180 | // the LED. Choose the current-limiting resistor by plugging your LED's |
mjr | 77:0b96f6867312 | 181 | // specs into an LED resistor calculator, using a 5V supply voltage. Now |
mjr | 77:0b96f6867312 | 182 | // connect the GPIO pin to the current-limiting resistor, connect the |
mjr | 77:0b96f6867312 | 183 | // resistor to the LED anode (+), connect the LED cathode (-) to the NPN |
mjr | 77:0b96f6867312 | 184 | // collector, connect the NPN emitter to ground, connect the NPN base to |
mjr | 77:0b96f6867312 | 185 | // the 2.2K resistor, and connect the 2.2K resistor to the GPIO pin. |
mjr | 77:0b96f6867312 | 186 | // It's simple enough for a schematic rendered in ASCII art: |
mjr | 77:0b96f6867312 | 187 | // |
mjr | 77:0b96f6867312 | 188 | // +5V (from the KL25Z +5V pin, or directly from |
mjr | 77:0b96f6867312 | 189 | // | the KL25Z's power supply) |
mjr | 77:0b96f6867312 | 190 | // < |
mjr | 77:0b96f6867312 | 191 | // > R1 - use an LED resistor calculator to choose |
mjr | 77:0b96f6867312 | 192 | // < the resistor size based on your selected |
mjr | 77:0b96f6867312 | 193 | // | LED's forward current & voltage and 5V source |
mjr | 77:0b96f6867312 | 194 | // --- + |
mjr | 77:0b96f6867312 | 195 | // \ / LED - Infrared emitter (e.g., Vishay TSAL6400) |
mjr | 77:0b96f6867312 | 196 | // --- - |
mjr | 77:0b96f6867312 | 197 | // | |
mjr | 77:0b96f6867312 | 198 | // | |
mjr | 77:0b96f6867312 | 199 | // \| 2.2K |
mjr | 77:0b96f6867312 | 200 | // |-----/\/\/\---> to this GPIO pin |
mjr | 77:0b96f6867312 | 201 | // /| |
mjr | 77:0b96f6867312 | 202 | // v |
mjr | 77:0b96f6867312 | 203 | // | |
mjr | 77:0b96f6867312 | 204 | // ----- |
mjr | 77:0b96f6867312 | 205 | // --- Ground (KL25Z GND pin, or ground on the |
mjr | 77:0b96f6867312 | 206 | // - KL25Z's power supply) |
mjr | 77:0b96f6867312 | 207 | // |
mjr | 77:0b96f6867312 | 208 | // If you want to be able to see the transmitter in action, you can connect |
mjr | 77:0b96f6867312 | 209 | // another LED (a blue one, say) and its own current-limiting resistor in |
mjr | 77:0b96f6867312 | 210 | // parallel with the R1 + IR LED circuit. Let's call the blue LED's |
mjr | 77:0b96f6867312 | 211 | // resistor R2. Connect R2 to +5V, connect the other end of R2 to the |
mjr | 77:0b96f6867312 | 212 | // blue LED (+), and connect the blue LED (-) to the NPN collector. This |
mjr | 77:0b96f6867312 | 213 | // will make the blue LED flash in sync with the IR LED. IR remote control |
mjr | 77:0b96f6867312 | 214 | // codes are slow enough that you'll be able to see the blue LED come on |
mjr | 77:0b96f6867312 | 215 | // and flicker during each transmission, although the "bits" are too fast |
mjr | 77:0b96f6867312 | 216 | // to see individually with the naked eye. The detector shouldn't be |
mjr | 77:0b96f6867312 | 217 | // bothered by the extra light since these sensors have optical filters |
mjr | 77:0b96f6867312 | 218 | // that block most of the incoming light outside of the IR band the sensor |
mjr | 77:0b96f6867312 | 219 | // is looking for. |
mjr | 77:0b96f6867312 | 220 | |
mjr | 77:0b96f6867312 | 221 | #ifndef _IRTRANSMITTER_H_ |
mjr | 77:0b96f6867312 | 222 | #define _IRTRANSMITTER_H_ |
mjr | 77:0b96f6867312 | 223 | |
mjr | 77:0b96f6867312 | 224 | #include <mbed.h> |
mjr | 77:0b96f6867312 | 225 | |
mjr | 77:0b96f6867312 | 226 | #include "NewPwm.h" |
mjr | 77:0b96f6867312 | 227 | #include "IRRemote.h" |
mjr | 77:0b96f6867312 | 228 | #include "IRCommand.h" |
mjr | 77:0b96f6867312 | 229 | #include "IRProtocols.h" |
mjr | 77:0b96f6867312 | 230 | |
mjr | 77:0b96f6867312 | 231 | |
mjr | 77:0b96f6867312 | 232 | // IR Remote Transmitter |
mjr | 77:0b96f6867312 | 233 | class IRTransmitter |
mjr | 77:0b96f6867312 | 234 | { |
mjr | 77:0b96f6867312 | 235 | public: |
mjr | 77:0b96f6867312 | 236 | // Construct. |
mjr | 77:0b96f6867312 | 237 | // |
mjr | 77:0b96f6867312 | 238 | // 'pin' is the GPIO pin controlling the IR LED. The pin must be |
mjr | 77:0b96f6867312 | 239 | // PWM-capable. (Note also that each PWM channel on the KL25Z is |
mjr | 77:0b96f6867312 | 240 | // shared among multiple pins, so be sure you're using a pin connected |
mjr | 77:0b96f6867312 | 241 | // to a channel that isn't already used elsewhere in your application.) |
mjr | 77:0b96f6867312 | 242 | // Don't connect the LED directly to this pin; see the circuit diagram |
mjr | 77:0b96f6867312 | 243 | // at the top of the file for details of how to connect it through a |
mjr | 77:0b96f6867312 | 244 | // transistor to safely boost the current to LED levels. |
mjr | 77:0b96f6867312 | 245 | // |
mjr | 77:0b96f6867312 | 246 | // 'nButtons' is the number of virtual button slots to allocate. Each |
mjr | 77:0b96f6867312 | 247 | // slot represents a virtual remote control button that can be programmed |
mjr | 77:0b96f6867312 | 248 | // with a remote code to transmit. Allocate as many slots as you need |
mjr | 77:0b96f6867312 | 249 | // for unique commands or buttons. Note that the caller is responsible |
mjr | 77:0b96f6867312 | 250 | // for deciding when a button is pressed; if you want to tie these to |
mjr | 77:0b96f6867312 | 251 | // physical buttons, you'll need to create your own DigitalIn objects |
mjr | 77:0b96f6867312 | 252 | // for the pins, monitor them, and call pushButton() to press and |
mjr | 77:0b96f6867312 | 253 | // release virtual buttons when the physical button states change. |
mjr | 77:0b96f6867312 | 254 | IRTransmitter(PinName pin, int nButtons) : ledPin(pin) |
mjr | 77:0b96f6867312 | 255 | { |
mjr | 77:0b96f6867312 | 256 | // make sure the protocol singletons are allocated |
mjr | 77:0b96f6867312 | 257 | IRProtocol::allocProtocols(); |
mjr | 77:0b96f6867312 | 258 | |
mjr | 77:0b96f6867312 | 259 | // no command is active |
mjr | 77:0b96f6867312 | 260 | curBtnId = -1; |
mjr | 77:0b96f6867312 | 261 | |
mjr | 77:0b96f6867312 | 262 | // allocate the command list |
mjr | 77:0b96f6867312 | 263 | buttons = new ButtonCmd[nButtons]; |
mjr | 77:0b96f6867312 | 264 | |
mjr | 77:0b96f6867312 | 265 | // the transmitter "thread" isn't yet running |
mjr | 77:0b96f6867312 | 266 | txRunning = false; |
mjr | 77:0b96f6867312 | 267 | txBtnId = -1; |
mjr | 77:0b96f6867312 | 268 | txProtocol = 0; |
mjr | 77:0b96f6867312 | 269 | } |
mjr | 77:0b96f6867312 | 270 | |
mjr | 77:0b96f6867312 | 271 | ~IRTransmitter() |
mjr | 77:0b96f6867312 | 272 | { |
mjr | 77:0b96f6867312 | 273 | delete[] buttons; |
mjr | 77:0b96f6867312 | 274 | } |
mjr | 77:0b96f6867312 | 275 | |
mjr | 77:0b96f6867312 | 276 | // Program the command code for a virtual button |
mjr | 77:0b96f6867312 | 277 | void programButton(int buttonId, int protocolId, bool dittos, uint64_t cmdCode) |
mjr | 77:0b96f6867312 | 278 | { |
mjr | 77:0b96f6867312 | 279 | ButtonCmd &btn = buttons[buttonId]; |
mjr | 77:0b96f6867312 | 280 | btn.pro = protocolId; |
mjr | 77:0b96f6867312 | 281 | btn.dittos = dittos; |
mjr | 77:0b96f6867312 | 282 | btn.cmd = cmdCode; |
mjr | 77:0b96f6867312 | 283 | } |
mjr | 77:0b96f6867312 | 284 | |
mjr | 77:0b96f6867312 | 285 | // Push a virtual button. |
mjr | 77:0b96f6867312 | 286 | // |
mjr | 77:0b96f6867312 | 287 | // When this is called, we'll start transmitting the command code |
mjr | 77:0b96f6867312 | 288 | // associated with the button immediately if no other transmission |
mjr | 77:0b96f6867312 | 289 | // is already in progress. On the other hand, if a transmission of |
mjr | 77:0b96f6867312 | 290 | // a prior command code is already in progress, the previous command |
mjr | 77:0b96f6867312 | 291 | // isn't interrupted; we always send whole commands, and never |
mjr | 77:0b96f6867312 | 292 | // interrupt a command in progress. Instead, the new button is |
mjr | 77:0b96f6867312 | 293 | // set as pending. As soon as the prior transmission finishes, |
mjr | 77:0b96f6867312 | 294 | // the pending button becomes the current button and we start |
mjr | 77:0b96f6867312 | 295 | // transmitting its code - but only if the button is still pressed |
mjr | 77:0b96f6867312 | 296 | // when the previous code finishes. This means that if you both |
mjr | 77:0b96f6867312 | 297 | // press and release a button during the time that another |
mjr | 77:0b96f6867312 | 298 | // transmission is in progress, the new button will never be |
mjr | 77:0b96f6867312 | 299 | // transmitted. We operate this way to keep things simple and |
mjr | 77:0b96f6867312 | 300 | // consistent when it comes to more than just one pending button. |
mjr | 77:0b96f6867312 | 301 | // This way we don't have to consider queues of pending buttons |
mjr | 77:0b96f6867312 | 302 | // or create mechanisms for canceling pending commands. |
mjr | 77:0b96f6867312 | 303 | // |
mjr | 77:0b96f6867312 | 304 | // If the button is still down when its first transmission ends, |
mjr | 77:0b96f6867312 | 305 | // and no other button has been pressed in the meantime, the button |
mjr | 77:0b96f6867312 | 306 | // will auto-repeat. This continues as long as the button is still |
mjr | 77:0b96f6867312 | 307 | // pressed and no other button has been pressed. |
mjr | 77:0b96f6867312 | 308 | // |
mjr | 77:0b96f6867312 | 309 | // Only one code can be transmitted at a time, obviously. The |
mjr | 77:0b96f6867312 | 310 | // semantics for multiple simultaneous button presses are like those |
mjr | 77:0b96f6867312 | 311 | // of a PC keyboard. Suppose you press button A, then a while later, |
mjr | 77:0b96f6867312 | 312 | // while A is still down, you press B. Then a while later still, |
mjr | 77:0b96f6867312 | 313 | // you press C, continuing to hold both A and B down. We transmit |
mjr | 77:0b96f6867312 | 314 | // A repeatedly until you press B, at which point we finish sending |
mjr | 77:0b96f6867312 | 315 | // the current repeat of A (we never interrupt a code in the middle: |
mjr | 77:0b96f6867312 | 316 | // once started, a code is always finished whole) and start sending |
mjr | 77:0b96f6867312 | 317 | // B. B continues to repeat until you press C, at which point we |
mjr | 77:0b96f6867312 | 318 | // finish the last repetition of B and start sending C. Once A or |
mjr | 77:0b96f6867312 | 319 | // B have been superseded, it makes no difference whether you continue |
mjr | 77:0b96f6867312 | 320 | // to hold them down or release them. They'll never start repeating |
mjr | 77:0b96f6867312 | 321 | // again, even if you then release C while A and B are still down. |
mjr | 77:0b96f6867312 | 322 | void pushButton(int id, bool on) |
mjr | 77:0b96f6867312 | 323 | { |
mjr | 77:0b96f6867312 | 324 | if (on) |
mjr | 77:0b96f6867312 | 325 | { |
mjr | 77:0b96f6867312 | 326 | // make this the current command |
mjr | 77:0b96f6867312 | 327 | curBtnId = id; |
mjr | 77:0b96f6867312 | 328 | |
mjr | 77:0b96f6867312 | 329 | // start the transmitter |
mjr | 77:0b96f6867312 | 330 | txStart(); |
mjr | 77:0b96f6867312 | 331 | } |
mjr | 77:0b96f6867312 | 332 | else |
mjr | 77:0b96f6867312 | 333 | { |
mjr | 77:0b96f6867312 | 334 | // if this is the current command, cancel it |
mjr | 77:0b96f6867312 | 335 | if (id == curBtnId) |
mjr | 77:0b96f6867312 | 336 | curBtnId = -1; |
mjr | 77:0b96f6867312 | 337 | } |
mjr | 77:0b96f6867312 | 338 | } |
mjr | 77:0b96f6867312 | 339 | |
mjr | 77:0b96f6867312 | 340 | // Is a transmission in progress? |
mjr | 77:0b96f6867312 | 341 | bool isSending() const { return txRunning; } |
mjr | 77:0b96f6867312 | 342 | |
mjr | 77:0b96f6867312 | 343 | |
mjr | 77:0b96f6867312 | 344 | protected: |
mjr | 77:0b96f6867312 | 345 | // Start the transmitter "thread", if it's not already running. The |
mjr | 77:0b96f6867312 | 346 | // thread is actually just a series of timer interrupts; each interrupt |
mjr | 77:0b96f6867312 | 347 | // sets the next interrupt at an appropriate interval, so the effect is |
mjr | 77:0b96f6867312 | 348 | // like a thread. |
mjr | 77:0b96f6867312 | 349 | void txStart() |
mjr | 77:0b96f6867312 | 350 | { |
mjr | 77:0b96f6867312 | 351 | if (!txRunning) |
mjr | 77:0b96f6867312 | 352 | { |
mjr | 77:0b96f6867312 | 353 | // The thread isn't running. Note that this means that there's |
mjr | 77:0b96f6867312 | 354 | // no possibility that txRunning will change out from under us |
mjr | 77:0b96f6867312 | 355 | // asynchronously, since there's no pending interrupt handler |
mjr | 77:0b96f6867312 | 356 | // to change it. Mark the thread as running. |
mjr | 77:0b96f6867312 | 357 | txRunning = true; |
mjr | 77:0b96f6867312 | 358 | |
mjr | 77:0b96f6867312 | 359 | // Directly invoke the thread handler for the first call. It |
mjr | 77:0b96f6867312 | 360 | // will normally run in interrupt context, but since there's |
mjr | 77:0b96f6867312 | 361 | // no pending interrupt yet that would re-enter it, we can |
mjr | 77:0b96f6867312 | 362 | // launch it first in application context. If there's work |
mjr | 77:0b96f6867312 | 363 | // pending, it'll kick off the transmission and schedule the |
mjr | 77:0b96f6867312 | 364 | // next timer interrupt to continue the thread. |
mjr | 77:0b96f6867312 | 365 | txThread(); |
mjr | 77:0b96f6867312 | 366 | } |
mjr | 77:0b96f6867312 | 367 | } |
mjr | 77:0b96f6867312 | 368 | |
mjr | 77:0b96f6867312 | 369 | // Transmitter "thread" main. This handles the timer interrupt for each |
mjr | 77:0b96f6867312 | 370 | // event in a transmission. |
mjr | 77:0b96f6867312 | 371 | void txThread() |
mjr | 77:0b96f6867312 | 372 | { |
mjr | 77:0b96f6867312 | 373 | // if we're working on a command, process the next step |
mjr | 77:0b96f6867312 | 374 | if (txProtocol != 0) |
mjr | 77:0b96f6867312 | 375 | { |
mjr | 77:0b96f6867312 | 376 | // Determine if the virtual button for the current transmission |
mjr | 77:0b96f6867312 | 377 | // is still pressed. It's still pressed if we have a valid |
mjr | 77:0b96f6867312 | 378 | // transmitting button ID, and the current pressed button is the |
mjr | 77:0b96f6867312 | 379 | // same as the transmitting button. |
mjr | 77:0b96f6867312 | 380 | txState.pressed = (txBtnId != -1 && txBtnId == curBtnId); |
mjr | 77:0b96f6867312 | 381 | |
mjr | 77:0b96f6867312 | 382 | // Perform the next step via the protocol handler. The handler |
mjr | 77:0b96f6867312 | 383 | // returns a positive time value for the next timeout if it still |
mjr | 77:0b96f6867312 | 384 | // has more work to do. |
mjr | 77:0b96f6867312 | 385 | int t = txProtocol->txStep(&txState); |
mjr | 77:0b96f6867312 | 386 | |
mjr | 77:0b96f6867312 | 387 | // check if the transmission is done |
mjr | 77:0b96f6867312 | 388 | if (t > 0) |
mjr | 77:0b96f6867312 | 389 | { |
mjr | 77:0b96f6867312 | 390 | // The handler returned a positive time value, so it has |
mjr | 77:0b96f6867312 | 391 | // more work to do. That means we're done here - just set |
mjr | 77:0b96f6867312 | 392 | // the next timeout and exit the interrupt handler. |
mjr | 77:0b96f6867312 | 393 | txTimeout.attach_us(this, &IRTransmitter::txThread, t); |
mjr | 77:0b96f6867312 | 394 | return; |
mjr | 77:0b96f6867312 | 395 | } |
mjr | 77:0b96f6867312 | 396 | else |
mjr | 77:0b96f6867312 | 397 | { |
mjr | 77:0b96f6867312 | 398 | // The transmission is done. Clear the send data. |
mjr | 77:0b96f6867312 | 399 | txBtnId = -1; |
mjr | 77:0b96f6867312 | 400 | txProtocol = 0; |
mjr | 77:0b96f6867312 | 401 | } |
mjr | 77:0b96f6867312 | 402 | } |
mjr | 77:0b96f6867312 | 403 | |
mjr | 77:0b96f6867312 | 404 | // If we made it here, the transmitter is now idle. Check to |
mjr | 77:0b96f6867312 | 405 | // see if we have a new virtual button press. |
mjr | 77:0b96f6867312 | 406 | if (curBtnId != -1) |
mjr | 77:0b96f6867312 | 407 | { |
mjr | 77:0b96f6867312 | 408 | // load the command |
mjr | 77:0b96f6867312 | 409 | txBtnId = curBtnId; |
mjr | 77:0b96f6867312 | 410 | txCmd = buttons[curBtnId]; |
mjr | 77:0b96f6867312 | 411 | txProtocol = IRProtocol::senderForId(txCmd.pro); |
mjr | 77:0b96f6867312 | 412 | |
mjr | 77:0b96f6867312 | 413 | // If we found a protocol handler, start the transmission |
mjr | 77:0b96f6867312 | 414 | if (txProtocol != 0) |
mjr | 77:0b96f6867312 | 415 | { |
mjr | 77:0b96f6867312 | 416 | // fill in the transmission state object with the new command |
mjr | 77:0b96f6867312 | 417 | // details |
mjr | 77:0b96f6867312 | 418 | txState.cmdCode = txCmd.cmd; |
mjr | 77:0b96f6867312 | 419 | txState.protocolId = txCmd.pro; |
mjr | 77:0b96f6867312 | 420 | txState.dittos = txCmd.dittos; |
mjr | 77:0b96f6867312 | 421 | txState.pin = &ledPin; |
mjr | 77:0b96f6867312 | 422 | txState.pressed = true; |
mjr | 77:0b96f6867312 | 423 | |
mjr | 77:0b96f6867312 | 424 | // reset the transmission step counters |
mjr | 77:0b96f6867312 | 425 | txState.step = 0; |
mjr | 77:0b96f6867312 | 426 | txState.bit = 0; |
mjr | 77:0b96f6867312 | 427 | txState.bitstep = 0; |
mjr | 77:0b96f6867312 | 428 | txState.rep = 0; |
mjr | 77:0b96f6867312 | 429 | |
mjr | 77:0b96f6867312 | 430 | // this is a new transmission, so toggle the toggle bit |
mjr | 77:0b96f6867312 | 431 | txState.toggle ^= 1; |
mjr | 77:0b96f6867312 | 432 | |
mjr | 77:0b96f6867312 | 433 | // Turn off the IR and set the PWM frequency of the IR LED to |
mjr | 77:0b96f6867312 | 434 | // the carrier frequency for the chosen protocol |
mjr | 77:0b96f6867312 | 435 | ledPin.write(0); |
mjr | 77:0b96f6867312 | 436 | ledPin.getUnit()->period(txProtocol->pwmPeriod(&txState)); |
mjr | 77:0b96f6867312 | 437 | |
mjr | 77:0b96f6867312 | 438 | // start the transmission timer |
mjr | 77:0b96f6867312 | 439 | txState.txTime.reset(); |
mjr | 77:0b96f6867312 | 440 | txState.txTime.start(); |
mjr | 77:0b96f6867312 | 441 | |
mjr | 77:0b96f6867312 | 442 | // initiate the transmission |
mjr | 77:0b96f6867312 | 443 | int t = txProtocol->txStart(&txState); |
mjr | 77:0b96f6867312 | 444 | |
mjr | 77:0b96f6867312 | 445 | // set the timer for the next step of the transmission, then |
mjr | 77:0b96f6867312 | 446 | // we're done |
mjr | 77:0b96f6867312 | 447 | txTimeout.attach_us(this, &IRTransmitter::txThread, t); |
mjr | 77:0b96f6867312 | 448 | return; |
mjr | 77:0b96f6867312 | 449 | } |
mjr | 77:0b96f6867312 | 450 | } |
mjr | 77:0b96f6867312 | 451 | |
mjr | 77:0b96f6867312 | 452 | // If we made it here, there's no transmission in progress, |
mjr | 77:0b96f6867312 | 453 | // so the thread is no longer running. |
mjr | 77:0b96f6867312 | 454 | txRunning = false; |
mjr | 77:0b96f6867312 | 455 | } |
mjr | 77:0b96f6867312 | 456 | |
mjr | 77:0b96f6867312 | 457 | // LED output pin controlling the IR LED. The pin must be PWM-capable. |
mjr | 77:0b96f6867312 | 458 | // WARNING! Don't connect the IR LED directly to the pin. See wiring |
mjr | 77:0b96f6867312 | 459 | // diagram at the top of the file. |
mjr | 77:0b96f6867312 | 460 | NewPwmOut ledPin; |
mjr | 77:0b96f6867312 | 461 | |
mjr | 77:0b96f6867312 | 462 | // Virtual button slots. Each slot represents a virtual remote control |
mjr | 77:0b96f6867312 | 463 | // button, containing a preprogrammed IR command code to send when the |
mjr | 77:0b96f6867312 | 464 | // button is pressed. Program a button by calling programButton(). |
mjr | 77:0b96f6867312 | 465 | // Press a button by calling pushButton(). |
mjr | 77:0b96f6867312 | 466 | struct ButtonCmd |
mjr | 77:0b96f6867312 | 467 | { |
mjr | 77:0b96f6867312 | 468 | uint64_t cmd; // command code |
mjr | 77:0b96f6867312 | 469 | uint8_t pro; // protocol ID (IRPRO_xxx) |
mjr | 77:0b96f6867312 | 470 | uint8_t dittos : 1; // use "ditto" codes for auto-repeat |
mjr | 77:0b96f6867312 | 471 | } __attribute__ ((packed)); |
mjr | 77:0b96f6867312 | 472 | ButtonCmd *buttons; |
mjr | 77:0b96f6867312 | 473 | |
mjr | 77:0b96f6867312 | 474 | // Current active virtual button ID. This is managed in application |
mjr | 77:0b96f6867312 | 475 | // context and read in interrupt context. This represents the currently |
mjr | 77:0b96f6867312 | 476 | // pushed button. |
mjr | 77:0b96f6867312 | 477 | int curBtnId; |
mjr | 77:0b96f6867312 | 478 | |
mjr | 77:0b96f6867312 | 479 | // Is the transmitter "thread" running? This is true when a timer is |
mjr | 77:0b96f6867312 | 480 | // pending, false if not. The timer interrupt handler clears this |
mjr | 77:0b96f6867312 | 481 | // before exiting on its last run of a transmission. |
mjr | 77:0b96f6867312 | 482 | // |
mjr | 77:0b96f6867312 | 483 | // Synchronization: if txRunning is false, no timer interrupt is either |
mjr | 77:0b96f6867312 | 484 | // running or pending, so there's no possibility that anyone else will |
mjr | 77:0b96f6867312 | 485 | // change it, so it's safe for the application to test and set it. If |
mjr | 77:0b96f6867312 | 486 | // txRunning is true, only interrupt context can change it, so application |
mjr | 77:0b96f6867312 | 487 | // context can only read it. |
mjr | 77:0b96f6867312 | 488 | volatile bool txRunning; |
mjr | 77:0b96f6867312 | 489 | |
mjr | 77:0b96f6867312 | 490 | // Transmitter thread timeout |
mjr | 77:0b96f6867312 | 491 | Timeout txTimeout; |
mjr | 77:0b96f6867312 | 492 | |
mjr | 77:0b96f6867312 | 493 | // Command ID being transmitted in the background "thread". The thread |
mjr | 77:0b96f6867312 | 494 | // loads this from curBtnID whenever it's out of other work to do. |
mjr | 77:0b96f6867312 | 495 | int txBtnId; |
mjr | 77:0b96f6867312 | 496 | |
mjr | 77:0b96f6867312 | 497 | // Protocol for the current transmission |
mjr | 77:0b96f6867312 | 498 | IRProtocol *txProtocol; |
mjr | 77:0b96f6867312 | 499 | |
mjr | 77:0b96f6867312 | 500 | // Command value we're currently transmitting |
mjr | 77:0b96f6867312 | 501 | ButtonCmd txCmd; |
mjr | 77:0b96f6867312 | 502 | |
mjr | 77:0b96f6867312 | 503 | // Protocol state. This is for use by the individual protocol |
mjr | 77:0b96f6867312 | 504 | // classes to keep track of their state while the transmission |
mjr | 77:0b96f6867312 | 505 | // proceeds. |
mjr | 77:0b96f6867312 | 506 | IRTXState txState; |
mjr | 77:0b96f6867312 | 507 | }; |
mjr | 77:0b96f6867312 | 508 | |
mjr | 77:0b96f6867312 | 509 | #endif |