Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
IRRemote/IRTransmitter.h
- Committer:
- arnoz
- Date:
- 2021-10-01
- Revision:
- 116:7a67265d7c19
- Parent:
- 77:0b96f6867312
File content as of revision 116:7a67265d7c19:
// IR Remote Transmitter // // This class lets you control an IR emitter LED connected to a GPIO port // to transmit remote control codes using numerous standard and proprietary // protocols. You can use this to send remote codes to any device with // a typical IR remote, such as A/V equipment, home automation devices, etc. // You can also use this with the companion IR Receiver class running on // a separate KL25Z to send IR commands to the other device. // // We do all of our transmissions with specific protocols rather than raw // IR signals. Every remote control has its own way of representing a // string of data bits as a series of timed IR flashes. The exact mapping // between data bits and IR flashes is the protocol. There are some quasi // industry standard protocols, where several companies use the same format // for their codes, but there are many proprietary protocols as well. We // have handlers for the most widely used protocols: NEC, Sony, Philips RC5 // and RC6, Pioneer, Panasonic, and several others. If your device isn't // covered yet, it could probably be added, since we've tried to design // the system to make it easy to add new protocols. // // When you transmit a code, you specify it in terms of the protocol to use // and the "code" value to send. A "code" is just the data value for a // particular key on a particular remote control, usually expressed as a // hex number. There are published tables of codes for many remotes, but // unfortunately they're not very consistent in how they represent the hex // code values, so you'll often see the same key represented with different // hex codes in different published tables. We of course have our own way // of mapping the hex codes; we've tried to use the format that the original // manufacturer uses in their tales, if they publish them at all, but these // may or may not be consistent with what you find in any tables you consult. // So your best bet for finding the right codes to use here is usually to // "learn" the codes using our companion class IRReceiver. That class has a // protocol decoder for each protocol transmitter we can use here, so if you // set that up and point a remote at it, it will tell you the exact code we // use for the key. // // The transmitter class provides a "virtual remote control" interface. // This gives you an imaginary remote control keypad, with a set of // virtual buttons programmed for individual remote control commands. // You specify the protocol and command code for each virtual button. // You can use different protocols for different buttons. // // // How to use the software // // First, create an instance of IRTransmitter, telling it which pin the // IR emitter is connected to (see below for wiring instructions) and how // many virtual remote control keys you want. The pin must be PWM capable. // // IRTransmitter *tx = new IRTransmitter(PTC9, 32); // // Next, program the virtual remote keys. For each key, set the IR protocol // to use (an IRPRO_xxx code from IRProtocolID.h), the "ditto" mode (more on // this below), and the hex code for the command. // // // program virtual button #0 with Sony 20-bit code 0x123, no dittos // tx->programButton(0, IRPRO_SONY20, false, 0x123); // // Now you're set up to transmit. In your main loop, decide when it's time // to transmit a button, such as by monitoring a physical pushbutton via a // GPIO DigitalIn pin. When you want to transmit a code, just tell the // transmitter that your virtual button is pressed, by calling pushButton() // with the virtual button ID (corresponding to a virtual button ID you // previously programmed wtih programButton()) and a status of 'true', // meaning that the button is pressed. // // tx->pushButton(0, true); // push virtual button #0 // // This starts the transmission and returns immediately. The transmission // proceeds in the background (via timer interrupts), so your main loop can // go about its other business without waiting for the transmission to // finish. Most remote codes take 50ms to 100ms to transmit, and you don't // usually want to stall an MCU app for that long. // // If a prior transmission is still in progress when you call pushButton(), // the new transmission doesn't interrupt the previous one. Every code is // sent as a complete unit to ensure data integrity, so the old one has to // finish before the new one starts. Some protocols have minimum repeat // counts, and the transmitter takes this into account as well. For example, // the Sony protocols require each command to be sent at least three times, // even if the button is only tapped for a brief instant. So if you send // a Sony code, a new command won't start transmitting until the last command // has been sent completely, not just once, but at least three times. // // Once the transmitter starts sending the code for a new button, it keeps // sending the same code on auto-repeat until you either un-press the // virtual button or press a new virtual button. Handling auto-repeat // in the transmitter like this has an important benefit, besides just making // the API simpler: it allows the transmitter to use the proper coding for // the repeats according to the rules of the protocol. Some protocols use // a different format for the first code of a key press and auto-repeats // of the same key. Some protocols also have other repetition features, // such as "toggle bits" or sequence counters. The protocol handlers use // the appropriate handling for their protocols, so you only have to think // in terms of when the virtual buttons are pressed and un-pressed, without // worrying about whether a toggle bit or a "ditto" code or a sequence // counter is needed. // // When the button is no longer pressed, call pushButton() again with a // status of 'false': // // tx->pushButton(0, false); // // Multiple button presses use simple PC keyboard-like semantics. At any // given time, there can be only one pressed button. When you call // pushButton(N, true), N becomes the pressed button, which means that the // previous pressed button (if any) is forgotten. As mentioned above, this // doesn't cancel the previous transmission if it's still in progress. The // transmitter continues with the last code until it's finished. When it // finishes with a code, the transmitter looks to see if the same button is // still pressed. If so, it starts a new transmission for the same button, // using the appropriate repeat code. If a new button is pressed, the // transmitter starts transmitting the new button's code. If no button is // pressed, the transmitter stops sending and becomes idle until you press // another button. // // Note that button presses aren't queued. Suppose you press button #0 // (while no other code is being sent): this starts transmitting the code // for button #0 and returns. Now suppose that a very short time later, // while that first send is still in progress, you briefly press and release // button #1. Button #1 will never be sent in this case. When you press // button #1, the transmitter is still sending the first code, so all it // does at this point is mark button #1 as the currently pressed button, // replacing button #0. But as explained above, this doesn't cancel the // button #0 code transmission in progress. That continues until the // complete code has been sent. At that point, the transmitter looks to // see which button is pressed, and discovers that NO button is pressed: // you already told it button #1 was released. So the transmitter simply // stops sending and becomes idle. // // // How to determine command codes and the "ditto" mode // // Our command codes are expressed as 64-bit integers. The code numbers // are in essence the data bits transmitted in the IR signal, but the mapping // between the IR data bits and the 64-bit code value is different for each // protocol. We've tried to make our codes match the numbers shown in the // tables published by the respective manufacturers for any given remote, // but you might also find third-party tables that have completely different // mappings. The easiest thing to do, really, is to ignore all of that and // just treat the codes as arbitrary, opaque identifiers, and identify the // codes for the remote you want to use by "learning" them. That is, set up // a receiver with our companion class IRReceiver, point your remote at it, // and see what IRReceiver reports as the decoded value for each button. // Simply use the same code value for each button when sending. // // The "ditto" flag is ignored for most protocols, but it's important for a // few, such as the various NEC protocols. This tells the sender whether to // use the protocol's special repeat code for auto-repeats (true), or to send // send the same key code repeatedly (false). The concept of dittos only // applies to a few protocols; most protocols just do the obvious thing and // send the same code repeatedly when you hold down a key. But the NEC // protocols and a few others have special coding for repeated keys. It's // important to use the special coding for devices that expect it, because // it lets them distinguish auto-repeat from multiple key presses, which // can affect how they respond to certain commands. The tricky part is that // manufacturers aren't always consistent about using dittos even when it's // a standard part of the protocol they're using, so you have to determine // whether or not to use it on a per-device basis. The easiest way to do // this is just like learning codes: set up a receiever with IRReceiver and // see what it reports. But this time, you're interested in what happens // when you hold down a key. You'll always get one ordinary report first, // but check what happens for the repeats. If IRReceiver reports the same // code repeatedly, set dittos = false when sending those codes. If the // repeats have the "ditto bit" set, though, set dittos = true when sending. // // // How to wire an IR emitter // // Any IR LED should work as the emitter. I used a Vishay TSAL6400 for my // reference/testing implementation. The TSAL6400 is quite bright, so it // should send signals well across fairly large distances. // // WARNING! DON'T connect the LED directly to the GPIO pin. KL25Z GPIO // pins have very low current limits - a typical IR emitter LED draws // enough current to damage or destroy the KL25Z. You'll need to build a // simple transistor circuit to interface with the LED. You'll need a // common small signal NPN transistor (such as a 2222 or 2N4401), a 2.2K // resistor, the IR LED, of course, and a current-limiting resistor for // the LED. Choose the current-limiting resistor by plugging your LED's // specs into an LED resistor calculator, using a 5V supply voltage. Now // connect the GPIO pin to the current-limiting resistor, connect the // resistor to the LED anode (+), connect the LED cathode (-) to the NPN // collector, connect the NPN emitter to ground, connect the NPN base to // the 2.2K resistor, and connect the 2.2K resistor to the GPIO pin. // It's simple enough for a schematic rendered in ASCII art: // // +5V (from the KL25Z +5V pin, or directly from // | the KL25Z's power supply) // < // > R1 - use an LED resistor calculator to choose // < the resistor size based on your selected // | LED's forward current & voltage and 5V source // --- + // \ / LED - Infrared emitter (e.g., Vishay TSAL6400) // --- - // | // | // \| 2.2K // |-----/\/\/\---> to this GPIO pin // /| // v // | // ----- // --- Ground (KL25Z GND pin, or ground on the // - KL25Z's power supply) // // If you want to be able to see the transmitter in action, you can connect // another LED (a blue one, say) and its own current-limiting resistor in // parallel with the R1 + IR LED circuit. Let's call the blue LED's // resistor R2. Connect R2 to +5V, connect the other end of R2 to the // blue LED (+), and connect the blue LED (-) to the NPN collector. This // will make the blue LED flash in sync with the IR LED. IR remote control // codes are slow enough that you'll be able to see the blue LED come on // and flicker during each transmission, although the "bits" are too fast // to see individually with the naked eye. The detector shouldn't be // bothered by the extra light since these sensors have optical filters // that block most of the incoming light outside of the IR band the sensor // is looking for. #ifndef _IRTRANSMITTER_H_ #define _IRTRANSMITTER_H_ #include <mbed.h> #include "NewPwm.h" #include "IRRemote.h" #include "IRCommand.h" #include "IRProtocols.h" // IR Remote Transmitter class IRTransmitter { public: // Construct. // // 'pin' is the GPIO pin controlling the IR LED. The pin must be // PWM-capable. (Note also that each PWM channel on the KL25Z is // shared among multiple pins, so be sure you're using a pin connected // to a channel that isn't already used elsewhere in your application.) // Don't connect the LED directly to this pin; see the circuit diagram // at the top of the file for details of how to connect it through a // transistor to safely boost the current to LED levels. // // 'nButtons' is the number of virtual button slots to allocate. Each // slot represents a virtual remote control button that can be programmed // with a remote code to transmit. Allocate as many slots as you need // for unique commands or buttons. Note that the caller is responsible // for deciding when a button is pressed; if you want to tie these to // physical buttons, you'll need to create your own DigitalIn objects // for the pins, monitor them, and call pushButton() to press and // release virtual buttons when the physical button states change. IRTransmitter(PinName pin, int nButtons) : ledPin(pin) { // make sure the protocol singletons are allocated IRProtocol::allocProtocols(); // no command is active curBtnId = -1; // allocate the command list buttons = new ButtonCmd[nButtons]; // the transmitter "thread" isn't yet running txRunning = false; txBtnId = -1; txProtocol = 0; } ~IRTransmitter() { delete[] buttons; } // Program the command code for a virtual button void programButton(int buttonId, int protocolId, bool dittos, uint64_t cmdCode) { ButtonCmd &btn = buttons[buttonId]; btn.pro = protocolId; btn.dittos = dittos; btn.cmd = cmdCode; } // Push a virtual button. // // When this is called, we'll start transmitting the command code // associated with the button immediately if no other transmission // is already in progress. On the other hand, if a transmission of // a prior command code is already in progress, the previous command // isn't interrupted; we always send whole commands, and never // interrupt a command in progress. Instead, the new button is // set as pending. As soon as the prior transmission finishes, // the pending button becomes the current button and we start // transmitting its code - but only if the button is still pressed // when the previous code finishes. This means that if you both // press and release a button during the time that another // transmission is in progress, the new button will never be // transmitted. We operate this way to keep things simple and // consistent when it comes to more than just one pending button. // This way we don't have to consider queues of pending buttons // or create mechanisms for canceling pending commands. // // If the button is still down when its first transmission ends, // and no other button has been pressed in the meantime, the button // will auto-repeat. This continues as long as the button is still // pressed and no other button has been pressed. // // Only one code can be transmitted at a time, obviously. The // semantics for multiple simultaneous button presses are like those // of a PC keyboard. Suppose you press button A, then a while later, // while A is still down, you press B. Then a while later still, // you press C, continuing to hold both A and B down. We transmit // A repeatedly until you press B, at which point we finish sending // the current repeat of A (we never interrupt a code in the middle: // once started, a code is always finished whole) and start sending // B. B continues to repeat until you press C, at which point we // finish the last repetition of B and start sending C. Once A or // B have been superseded, it makes no difference whether you continue // to hold them down or release them. They'll never start repeating // again, even if you then release C while A and B are still down. void pushButton(int id, bool on) { if (on) { // make this the current command curBtnId = id; // start the transmitter txStart(); } else { // if this is the current command, cancel it if (id == curBtnId) curBtnId = -1; } } // Is a transmission in progress? bool isSending() const { return txRunning; } protected: // Start the transmitter "thread", if it's not already running. The // thread is actually just a series of timer interrupts; each interrupt // sets the next interrupt at an appropriate interval, so the effect is // like a thread. void txStart() { if (!txRunning) { // The thread isn't running. Note that this means that there's // no possibility that txRunning will change out from under us // asynchronously, since there's no pending interrupt handler // to change it. Mark the thread as running. txRunning = true; // Directly invoke the thread handler for the first call. It // will normally run in interrupt context, but since there's // no pending interrupt yet that would re-enter it, we can // launch it first in application context. If there's work // pending, it'll kick off the transmission and schedule the // next timer interrupt to continue the thread. txThread(); } } // Transmitter "thread" main. This handles the timer interrupt for each // event in a transmission. void txThread() { // if we're working on a command, process the next step if (txProtocol != 0) { // Determine if the virtual button for the current transmission // is still pressed. It's still pressed if we have a valid // transmitting button ID, and the current pressed button is the // same as the transmitting button. txState.pressed = (txBtnId != -1 && txBtnId == curBtnId); // Perform the next step via the protocol handler. The handler // returns a positive time value for the next timeout if it still // has more work to do. int t = txProtocol->txStep(&txState); // check if the transmission is done if (t > 0) { // The handler returned a positive time value, so it has // more work to do. That means we're done here - just set // the next timeout and exit the interrupt handler. txTimeout.attach_us(this, &IRTransmitter::txThread, t); return; } else { // The transmission is done. Clear the send data. txBtnId = -1; txProtocol = 0; } } // If we made it here, the transmitter is now idle. Check to // see if we have a new virtual button press. if (curBtnId != -1) { // load the command txBtnId = curBtnId; txCmd = buttons[curBtnId]; txProtocol = IRProtocol::senderForId(txCmd.pro); // If we found a protocol handler, start the transmission if (txProtocol != 0) { // fill in the transmission state object with the new command // details txState.cmdCode = txCmd.cmd; txState.protocolId = txCmd.pro; txState.dittos = txCmd.dittos; txState.pin = &ledPin; txState.pressed = true; // reset the transmission step counters txState.step = 0; txState.bit = 0; txState.bitstep = 0; txState.rep = 0; // this is a new transmission, so toggle the toggle bit txState.toggle ^= 1; // Turn off the IR and set the PWM frequency of the IR LED to // the carrier frequency for the chosen protocol ledPin.write(0); ledPin.getUnit()->period(txProtocol->pwmPeriod(&txState)); // start the transmission timer txState.txTime.reset(); txState.txTime.start(); // initiate the transmission int t = txProtocol->txStart(&txState); // set the timer for the next step of the transmission, then // we're done txTimeout.attach_us(this, &IRTransmitter::txThread, t); return; } } // If we made it here, there's no transmission in progress, // so the thread is no longer running. txRunning = false; } // LED output pin controlling the IR LED. The pin must be PWM-capable. // WARNING! Don't connect the IR LED directly to the pin. See wiring // diagram at the top of the file. NewPwmOut ledPin; // Virtual button slots. Each slot represents a virtual remote control // button, containing a preprogrammed IR command code to send when the // button is pressed. Program a button by calling programButton(). // Press a button by calling pushButton(). struct ButtonCmd { uint64_t cmd; // command code uint8_t pro; // protocol ID (IRPRO_xxx) uint8_t dittos : 1; // use "ditto" codes for auto-repeat } __attribute__ ((packed)); ButtonCmd *buttons; // Current active virtual button ID. This is managed in application // context and read in interrupt context. This represents the currently // pushed button. int curBtnId; // Is the transmitter "thread" running? This is true when a timer is // pending, false if not. The timer interrupt handler clears this // before exiting on its last run of a transmission. // // Synchronization: if txRunning is false, no timer interrupt is either // running or pending, so there's no possibility that anyone else will // change it, so it's safe for the application to test and set it. If // txRunning is true, only interrupt context can change it, so application // context can only read it. volatile bool txRunning; // Transmitter thread timeout Timeout txTimeout; // Command ID being transmitted in the background "thread". The thread // loads this from curBtnID whenever it's out of other work to do. int txBtnId; // Protocol for the current transmission IRProtocol *txProtocol; // Command value we're currently transmitting ButtonCmd txCmd; // Protocol state. This is for use by the individual protocol // classes to keep track of their state while the transmission // proceeds. IRTXState txState; }; #endif