Table of Contents
After five years, it’s time we took another look at using infrared remote controls with microcontrollers. We’ll work with some updated libraries and see what we can do to decode those “stubborn” air conditioner remote controls. We’ll also take a closer look at the hardware used to exchange IR signals. And, of course, we’ll build many fun IR remote projects!
Introduction
It’s a safe bet that you own at least one infrared remote control. In fact, it’s likely that you own several, perhaps so many that you would have trouble accounting for them all. Once exclusively used for televisions, remote controls are now included with all varieties of electronic gear.
IR Remote controls offer numerous possibilities for experimenters and microcontroller enthusiasts. We can use microcontrollers to emulate remote controls, placing a wide variety of devices under the control of our programs. We can also get our micros to respond to IR remote controls, providing a convenient way to control our projects.
Microcontrollers also open up several advanced capabilities, such as converting remote control codes from one manufacturer to another or playing back sequences of remote control codes.
Today, we will see how these little gems work and how we can use them in our projects.
IR Remote Controls
Televisions, air conditioners, sound systems, electronic blinds, humidifiers – all of these appliances come equipped with infrared remote controls. Usually, they play nice together, although occasionally, we get into a conflict, and turning up the sound on the TV also changes the humidifier settings.
Let’s learn more about these devices to see if we can understand how they work, how we can use them in our projects, and why we have so many of these critters on our coffee table!
History of Remote Controls
It’s hard to imagine life without remote controls, but believe it or not, there was once a time when you actually had to get out of your chair to change channels or adjust the sound on the TV!
The remote control has a rich history that spans several decades. The first television remote control, named “Lazy Bones,” was developed by Zenith Radio Corporation in the late 1940s. However, it was connected to the TV with a wire, and at 30 dollars (387 dollars in 2023), it was an expensive accessory.
In 1955, Zenith’s Eugene Polley developed the first wireless remote control called the “Flashmatic.” It worked by directing a visible light beam toward photocells on the television set. As you might suspect, this design was prone to interference from other light sources, so it wasn’t very successful.
Just one year later, Robert Adler, also from Zenith, designed the “Zenith Space Command.” This was a true precursor to the modern remote. It was ultrasonic, meaning it emitted high-frequency sounds to control TV functions. When a button was pressed, it would strike a bar and produce a sound, which the television would then interpret as a command. This design eliminated the problems associated with ambient light interference.
Ultrasonic remotes were the dominant form of television remote controls for nearly two decades. They were sometimes difficult to use; they emitted “chirping” sounds and were known to bother dogs and cats. But, on the plus side, they didn’t require any batteries!
By the late 1970s, the shift from ultrasonic to infrared technology had begun. IR remote controls became the standard, primarily because they were cheaper to produce, had a broader range of functionality, and avoided the audible sounds of ultrasonic remotes.
As consumer electronics proliferated, so did the applications of IR remotes. They started being used for VCRs, DVD players, stereos, and eventually, air conditioners, cameras, and more. These days, they are everywhere.
Infrared Remote Control Basics
In its most basic form, an IR remote control sends a pulsed beam of infrared light, which contains the key code for the button you pressed. The receiver extracts the code and performs the desired operation.
Infrared Light
The light these devices use is in the “infrared” range, meaning the range of light below red in the visible spectrum.
Light can be measured by its wavelength, and most infrared remotes operate in wavelengths ranging from 850 to 960 nanometers. By far, the most common wavelength is 940nm.
Carrier Frequency
The pulses of infrared light are also modulated with a carrier wave, just like a radio signal. This technique allows the receiver to distinguish the remote signal from the infrared background noise, which can sometimes be quite high.
Carrier frequencies can vary between 30 and 60 kilohertz, but the most common carrier frequency is 38 kHz.
Modulation Techniques
All remote control data is sent in a serial format. The data is encoded using a number of different modulation techniques. These different techniques determine how the zeros and ones are formatted for transmission over the infrared light beam.
There are three basic modulation techniques used, along with several variations of them.
- Pulse Density Modulation (PDM) – Also called Space Encoding or Pulse Distance Modulation.
- Pulse Width Modulation (PWM) – Also called Pulse Encoding.
- Bi-Phase Modulation – Also called Manchester Encoding.
The following illustrations show how the data is formatted using these three modulation techniques:
IR Remote Protocols
Consider those remotes that are taking over your coffee table. Each of them has numerous functions, yet they seldom interfere with one another. This may be due to different IR wavelengths or carrier frequencies, but 940nm and 38kHz are pretty well the standard. It’s likely every remote you own uses these standards.
So, how do they keep from interfering? One way is by using different protocols.
IR remote protocols define all the parameters for exchanging IR data. Some of these parameters include:
- The modulation technique used.
- The timing and format of the start bit.
- The number of address and data bits.
- The format of any error checking used.
- The format of the EOM (End Of Message) or stop bit(s).
- Whether the data is LSB (Least Significant Bit) or MSB (Most Significant Bit) first.
There are many different protocols in use, common ones as well as propriety ones. The chart below shows only a small sampling of protocols in common use; there are actually hundreds of them.
NEC IR Remote Protocol
The most popular infrared protocol is the NEC protocol. Hundreds of manufacturers use it, and it is easy to work with as it is extensively documented. It’s likely used on most of the remotes you own, except for the “big brand name” ones like Sony, LG, Panasonic, Apple, and Samsung, who use their propriety protocols.
This chart illustrates the structure of the NEC IR protocol, as well as a few example commands. Note that the packet begins with a 9ms leading pulse burst followed by a 4.5ms space.
This is followed by 32 bits of address and command data. Note that both the address and command are 8 bits, and for each, a logical inversion is also sent. The logical inversion serves as a form of error checking.
For our purposes, the only data we need to work with are the Address and Command values. This is true for other protocols, as well as the NEC one.
In this illustration, we can see that using a different address allows two remotes that use the NEC protocol to coexist in the same room without interfering with one another. Although both devices use code 0x0A for power, they use different addresses.
IR Remote Control Components
Working with infrared light remote controls requires special components. These are inexpensive and easy to obtain.
IR Receiver Modules
The IR Receiver module packs a lot of functionality into a small package, as shown below:
These modules are available in different IR wavelengths, with 940nm being the most popular. The wavelength is usually keyed into the part number, as shown here for the popular Vishay TSOP34xx series.
TL1838 Sensors
The TL1838 and many other sensors with “1838” in the part number are probably the most common IR sensors available to hobbyists. These devices have been around for decades, and while they certainly work, they lack a lot of the functionality that more modern sensors have.
In addition, the TL1838 is meant for 5-volt operation, so it is difficult to use with modern 3.3-volt microcontrollers.
Now, if you pick up one of those packages on Amazon or eBay with a handful of IR emitter diodes and unmarked IR sensors, you likely just picked up a bunch of TL1838 sensors. And while you can certainly use them in your experiments, I would not recommend them for new designs.
IR Emitters
An IR Emitter is essentially an LED that emits infrared light. As with infrared receivers, these are available in different wavelengths.
The illustration below shows the part numbers for the OSRAM SFH 45xx series of infrared emitters, with the popular 940nm model highlighted.
Driving IR Emitters
Infrared emitters can consume much more current than a standard visible LED, so driving them directly from a microcontroller’s GPIO pin isn’t practical. Many of these devices can consume 100ma or more, with some devices allowing up to 1 ampere when pulsed.
In this illustration, a bipolar transistor is used to switch the IR emitter LED. As bipolar transistors are current-driven, a limiting resistor is used on the connection from the GPIO pin to the base of the transistor.
There is also a current limiting resistor for the IR LED. This is usually a low-value resistor; sometimes, a resistor is not even necessary.
You can also use a transistor to switch multiple IR emitters for increased range and performance.
Arduino Hookup
Here’s how we will hook up our Arduino Uno, IR emitter diode, and IR sensor. We will also add three pushbutton switches and a visible LED as well.
Here are a few things to note about this hookup:
- The transistor used to drive the IR emitter is a 2N2222 NPN. You could substitute another NPN transistor if you don’t have a 2N2222; make sure you check out the pinout of any substitutions, as they may not match the 2N2222.
- The 2.2uf capacitor is a power supply filter capacitor for the IR receiver module. It should be placed near the sensor’s power connection on your breadboard. The value is not critical, and any electrolytic capacitor from 1uf to 10uf can be substituted.
- The 33-ohm dropping resistor for the IR emitter may need adjustment for different IR emitter diodes. In some cases, it may not even be necessary.
Of course, another thing you’ll need, aside from the Arduino hookup and your computer, is a few remote controls to experiment with. You’ll need to round a few up; the more, the merrier!
Updated Arduino-IRremote Library
The Arduino-IRremote Library is the most popular library for using IR remote controls with microcontrollers. It is constantly being updated, and it was one of those previous updates that made most of the code in the older article obsolete, as the library is now called using another method.
We will install the library in our Arduino IDE and then perform a number of experiments using it.
The easiest way of adding the Arduino-IRremote Library to the Arduino IDE is to install it directly from the library manager.
Once the library is installed, you can access several useful and interesting example sketches.
Arduino-IRremote Library Example Code
There are no less than 25 example sketches included with the Arduino-IRreamote Library. Each one is well documented, and the Arduino we just set up is hooked up using the default pins for most of the examples.
The exception is Pin D5, which is referred to as the APPLICATION_PIN in the examples. It is unused in many examples, but in some examples, it is an output, whereas in others, it is a switch input. Currently, I have it hooked up to an LED. I used pin D5 as it is also PWM capable, a feature that we will need later on. It can also be used with examples like the ControlRelay example instead of the output relay module.
PinDefinitionsAndMore File
Almost all of the 25 IR remote examples use a file named PinDefinitionsAndMore.h. As its rather long name would imply, this file contains pin definitions for a variety of microcontrollers, as well as the definitions of some common constants.
The file starts with a list of common microcontrollers and the default pins used. By wiring your microcontroller as per the list, you’ll make coding simpler. You can, of course, choose to ignore the default wiring and change the pin definitions in the code, but it’s a lot simpler just to follow the list.
Many of the constants we will encounter in the example sketches that follow are contained in the PinDefinitionsAndMore file.
SimpleReceiver Example Code
The SimpleReceiver is the first example sketch that we will run, and I’m pretty sure you can guess what it does! You’ll find it, and all of the other example sketches, in the File/Examples/Examples from Custom Libraries/IRremote folder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
/* * SimpleReceiver.cpp * * Demonstrates receiving NEC IR codes with IRremote * * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. * ************************************************************************************ * MIT License * * Copyright (c) 2020-2023 Armin Joachimsmeyer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is furnished * to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ************************************************************************************ */ /* * Specify which protocol(s) should be used for decoding. * If no protocol is defined, all protocols (except Bang&Olufsen) are active. * This must be done before the #include <IRremote.hpp> */ //#define DECODE_DENON // Includes Sharp //#define DECODE_JVC //#define DECODE_KASEIKYO //#define DECODE_PANASONIC // alias for DECODE_KASEIKYO //#define DECODE_LG #define DECODE_NEC // Includes Apple and Onkyo //#define DECODE_SAMSUNG //#define DECODE_SONY //#define DECODE_RC5 //#define DECODE_RC6 //#define DECODE_BOSEWAVE //#define DECODE_LEGO_PF //#define DECODE_MAGIQUEST //#define DECODE_WHYNTER //#define DECODE_FAST //#define DECODE_DISTANCE_WIDTH // Universal decoder for pulse distance width protocols //#define DECODE_HASH // special decoder for all protocols //#define DECODE_BEO // This protocol must always be enabled manually, i.e. it is NOT enabled if no protocol is defined. It prevents decoding of SONY! //#define DEBUG // Activate this for lots of lovely debug output from the decoders. //#define RAW_BUFFER_LENGTH 180 // Default is 112 if DECODE_MAGIQUEST is enabled, otherwise 100. #include <Arduino.h> /* * This include defines the actual pin number for pins like IR_RECEIVE_PIN, IR_SEND_PIN for many different boards and architectures */ #include "PinDefinitionsAndMore.h" #include <IRremote.hpp> // include the library void setup() { Serial.begin(115200); // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_IRREMOTE)); // Start the receiver and if not 3. parameter specified, take LED_BUILTIN pin from the internal boards definition as default feedback LED IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); Serial.print(F("Ready to receive IR signals of protocols: ")); printActiveIRProtocols(&Serial); Serial.println(F("at pin " STR(IR_RECEIVE_PIN))); } void loop() { /* * Check if received data is available and if yes, try to decode it. * Decoded result is in the IrReceiver.decodedIRData structure. * * E.g. command is in IrReceiver.decodedIRData.command * address is in command is in IrReceiver.decodedIRData.address * and up to 32 bit raw data in IrReceiver.decodedIRData.decodedRawData */ if (IrReceiver.decode()) { /* * Print a short summary of received data */ IrReceiver.printIRResultShort(&Serial); IrReceiver.printIRSendUsage(&Serial); if (IrReceiver.decodedIRData.protocol == UNKNOWN) { Serial.println(F("Received noise or an unknown (or not yet enabled) protocol")); // We have an unknown protocol here, print more info IrReceiver.printIRResultRawFormatted(&Serial, true); } Serial.println(); /* * !!!Important!!! Enable receiving of the next value, * since receiving has stopped after the end of the current received data packet. */ IrReceiver.resume(); // Enable receiving of the next value /* * Finally, check the received data and perform actions according to the received command */ if (IrReceiver.decodedIRData.command == 0x10) { // do something } else if (IrReceiver.decodedIRData.command == 0x11) { // do something else } } } |
The sketch starts with definitions for many different remote protocols, with most of them remarked out (i.e., inactive). Only the NEC protocol is enabled by default.
We include both the library and the PinDefinitionsAndMore file, and then move into the Setup, as the pin definitions file has already defined all the constants we need.
In Setup, we start the serial monitor. After that, this sketch (and most of the others) does a print of the current IR Remote version.
We then enable the IR Receiver with this line:
1 |
IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); |
The IR_RECEIVE_PIN is defined as 2 by the pin definitions file and is correct for our Arduino hookup.
The ENABLE_LED_FEEDBACK is a boolean that is set to true. When enabled, the onboard LED will flicker whenever data is received.
Now, on to the Loop:
We start by looking for a decoded output, which will occur whenever a valid IR packet is received.
Once we receive the packet, we print out its contents to the serial monitor. This will include the address, command value, and protocol type. Note that in its default configuration, it will only decode NEC packets; you need to enable additional ones as you require them.
An important thing to note is that the receiver stops receiving after getting a valid packet. You will need to start it again with the following command:
1 |
IrReceiver.resume(); |
The example also includes a framework for performing an action when a specific command value is received. You can add code here later if you wish.
Running SimpleReceiver
Load the sketch to the Arduino and open the serial monitor. Now aim a remote control at the receiver module and press a button on it.
If it was an NEC protocol remote (the most common), then you will get information regarding the protocol, address, and protocol on the serial monitor.
If it was another protocol, however, you will just get some raw data and a message saying, “Received noise or an unknown (or not yet enabled) protocol”.
Assuming that you know what the protocol should be, you can un-remark its line at the beginning of the sketch. In the video accompanying this article, I show examples of enabling it to recognize Sony and Panasonic remotes.
SimpleSender Example Code
Now we can move on to the SimpleSender sketch, and I’ll bet that you can guess what it does! It’s essentially the sending counterpart of the previous sketch.
The sketch will use the NEC protocol and send out command, address, and repeat codes. It will step through these, so if you leave it in a room with some remote-controlled devices, you may eventually affect one of them!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
/* * SimpleSender.cpp * * Demonstrates sending IR codes in standard format with address and command * An extended example for sending can be found as SendDemo. * * Copyright (C) 2020-2022 Armin Joachimsmeyer * armin.joachimsmeyer@gmail.com * * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. * * MIT License */ #include <Arduino.h> #define DISABLE_CODE_FOR_RECEIVER // Disables restarting receiver after each send. Saves 450 bytes program memory and 269 bytes RAM if receiving functions are not used. //#define SEND_PWM_BY_TIMER // Disable carrier PWM generation in software and use (restricted) hardware PWM. //#define USE_NO_SEND_PWM // Use no carrier PWM, just simulate an active low receiver signal. Overrides SEND_PWM_BY_TIMER definition /* * This include defines the actual pin number for pins like IR_RECEIVE_PIN, IR_SEND_PIN for many different boards and architectures */ #include "PinDefinitionsAndMore.h" #include <IRremote.hpp> // include the library void setup() { pinMode(LED_BUILTIN, OUTPUT); Serial.begin(115200); // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_IRREMOTE)); Serial.print(F("Send IR signals at pin ")); Serial.println(IR_SEND_PIN); /* * The IR library setup. That's all! */ // IrSender.begin(); // Start with IR_SEND_PIN as send pin and if NO_LED_FEEDBACK_CODE is NOT defined, enable feedback LED at default feedback LED pin IrSender.begin(DISABLE_LED_FEEDBACK); // Start with IR_SEND_PIN as send pin and disable feedback LED at default feedback LED pin } /* * Set up the data to be sent. * For most protocols, the data is build up with a constant 8 (or 16 byte) address * and a variable 8 bit command. * There are exceptions like Sony and Denon, which have 5 bit address. */ uint8_t sCommand = 0x34; uint8_t sRepeats = 0; void loop() { /* * Print current send values */ Serial.println(); Serial.print(F("Send now: address=0x00, command=0x")); Serial.print(sCommand, HEX); Serial.print(F(", repeats=")); Serial.print(sRepeats); Serial.println(); Serial.println(F("Send standard NEC with 8 bit address")); Serial.flush(); // Receiver output for the first loop must be: Protocol=NEC Address=0x102 Command=0x34 Raw-Data=0xCB340102 (32 bits) IrSender.sendNEC(0x00, sCommand, sRepeats); /* * Increment send values */ sCommand += 0x11; sRepeats++; // clip repeats at 4 if (sRepeats > 4) { sRepeats = 4; } delay(1000); // delay must be greater than 5 ms (RECORD_GAP_MICROS), otherwise the receiver sees it as one long signal } |
This sketch contains some constants that must be defined before the library is called. One of them, DISABLE_CODE_FOR_RECEIVER, is enabled. It disables the receiver function, restarting after each send, saving about 450 bytes of memory.
In Setup, we define the built-in LED as an output, which may seem odd but is due to its possible use later on in the code. It can be set to flash whenever an IR signal is sent, providing a visual indication of operation.
We start the IR sender with the following line:
1 |
IrSender.begin(DISABLE_LED_FEEDBACK); |
Note that LED feedback is disabled; you can enable it if you wish to see the effect on the built-in LED.
We also define a few variables after Setup, a bit of an odd location, but perfectly valid. These are the “seed” values for the incrementing IR codes and repeat values.
In the Loop, we print out the current values, then send them within this command:
1 |
IrSender.sendNEC(0x00, sCommand, sRepeats); |
The first parameter is the Address, which is always 00 in this example. Feel free to change it.
After sending, we alter the values, delay a second, and do it again.
Load the code up to your Arduino and observe the serial monitor. You can also run a second Arduino, as I did, with SimpleReceiver. That will allow you to monitor the results.
IRMP Library
The IRMP, or Infrared Multi Protocol Decoder + Encoder Library, is an alternative to the Arduino-IRremote library.
This library has a lower memory footprint than the other library, and it supports 50 different protocols. The Arduino-IRremote library only supports 17 protocols as of this writing.
You can install the IRMP Library using the Library Manager in the Arduino IDE. Once it is installed, it will also install several examples; most of these are reworked versions of the Arduino-IRremote library examples. This is great as you can use them to see the differences between the two libraries.
The wiring we hooked up on our Arduino will also work with the IRMP library examples.
AllProtocols LCD Hookup
We are going to run an example that is actually in both libraries, the AllProtocols example. This example reads IR protocols, along with address and command values, and displays them on a two-line LCD.
The example uses a 1602 LCD module, along with an Arduino. You’ll also need a 10k pot to use as a contrast control for the LCD.
Here is the pinout of a standard 1602 display module. Note that your module may have slightly different pin designations for power and ground.
There are three ways you can interface a microcontroller to a 1602 LCD module:
- Parallel data, 8-bits
- Parallel data, 4-bits
- Serial data, I2C adapter (requires additional hardware)
Although the I2C adapter uses the least wiring, we will be hooking up our display in parallel. We will be using 4-bit mode to reduce the number of connections. This method is preferred in the AllProtocols example; a serial hookup would be missing some data (according to the notes in the sketch).
Here is how we will be hooking up our 1602 display. You could also use a 1604 display; the pinout is identical.
AllProtocols Code & Demo
The AllProtocols sketch is one of the example sketches that was installed when you added the IRMP library to your Arduino IDE.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
/* * AllProtocols.cpp * * Accepts 40 protocols concurrently * If you specify F_INTERRUPTS to 20000 at line 86 (default is 15000) it supports LEGO + RCMM protocols, but disables PENTAX and GREE protocols. * if you see performance issues, you can disable MERLIN Protocol at line 88. * * Uses a callback function which is called every time a complete IR command was received. * Prints data to LCD connected parallel at pin 4-9 or serial at pin A4, A5 * * Copyright (C) 2019-2022 Armin Joachimsmeyer * armin.joachimsmeyer@gmail.com * * This file is part of IRMP https://github.com/IRMP-org/IRMP. * * IRMP is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/gpl.html>. * */ #include <Arduino.h> #include "PinDefinitionsAndMore.h" /* * Set input pin and output pin definitions etc. */ #define IRMP_PROTOCOL_NAMES 1 // Enable protocol number mapping to protocol strings - needs some program memory ~ 420 bytes here #define IRMP_USE_COMPLETE_CALLBACK 1 // Enable callback functionality //#define NO_LED_FEEDBACK_CODE // Activate this if you want to suppress LED feedback or if you do not have a LED. This saves 14 bytes code and 2 clock cycles per interrupt. #if __SIZEOF_INT__ == 4 #define F_INTERRUPTS 20000 // Instead of default 15000 to support LEGO + RCMM protocols #else //#define F_INTERRUPTS 20000 // Instead of default 15000 to support LEGO + RCMM protocols, but this in turn disables PENTAX and GREE protocols :-( //#define IRMP_32_BIT 1 // This enables MERLIN protocol, but decreases performance for AVR. #endif #include <irmpSelectAllProtocols.h> // This enables all possible protocols //#define IRMP_SUPPORT_SIEMENS_PROTOCOL 1 /* * After setting the definitions we can include the code and compile it. */ #include <irmp.hpp> IRMP_DATA irmp_data; /* * Activate the type of LCD you use * Default is parallel LCD with 2 rows of 16 characters (1602). * Serial LCD has the disadvantage, that the first repeat is not detected, * because of the long lasting serial communication. */ //#define USE_NO_LCD //#define USE_SERIAL_LCD /* * Define the size of your LCD */ //#define USE_2004_LCD #if defined(USE_2004_LCD) // definitions for a 2004 LCD #define LCD_COLUMNS 20 #define LCD_ROWS 4 #else #define USE_1602_LCD // definitions for a 1602 LCD #define LCD_COLUMNS 16 #define LCD_ROWS 2 #endif #if defined(USE_SERIAL_LCD) #include <LiquidCrystal_I2C.h> // Use an up to date library version, which has the init method LiquidCrystal_I2C myLCD(0x27, LCD_COLUMNS, LCD_ROWS); // set the LCD address to 0x27 for a 16 chars and 2 line display #elif !defined(USE_NO_LCD) #include <LiquidCrystal.h> #define USE_PARALLEL_LCD //LiquidCrystal myLCD(4, 5, 6, 7, 8, 9); LiquidCrystal myLCD(7, 8, 3, 4, 5, 6); #endif #if defined(USE_SERIAL_LCD) || defined(USE_PARALLEL_LCD) #define USE_LCD # if defined(ADC_UTILS_ARE_AVAILABLE) // For cyclically display of VCC #include "ADCUtils.hpp" #define MILLIS_BETWEEN_VOLTAGE_PRINT 5000 uint32_t volatile sMillisOfLastVoltagePrint; # endif void printIRResultOnLCD(); size_t printHex(uint16_t aHexByteValue); #endif void handleReceivedIRData(); bool volatile sIRMPDataAvailable = false; void setup() { Serial.begin(115200); #if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/|| defined(SERIALUSB_PID) || defined(ARDUINO_attiny3217) \ || defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__) delay(4000); // To be able to connect Serial monitor after reset or power on and before first printout #endif // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_IRMP)); irmp_init(); irmp_irsnd_LEDFeedback(true); // Enable receive signal feedback at LED_BUILTIN irmp_register_complete_callback_function(&handleReceivedIRData); Serial.print(F("Ready to receive IR signals of protocols: ")); irmp_print_active_protocols(&Serial); Serial.println(F("at pin " STR(IRMP_INPUT_PIN))); #if defined(USE_SERIAL_LCD) Serial.println(F("With serial LCD connection, the first repeat is not detected, because of the long lasting serial communication!")); #endif #if defined(USE_LCD) && defined(ADC_UTILS_ARE_AVAILABLE) getVCCVoltageMillivoltSimple(); // to initialize ADC mux and reference #endif #if defined(USE_SERIAL_LCD) myLCD.init(); myLCD.clear(); myLCD.backlight(); #endif #if defined(USE_PARALLEL_LCD) myLCD.begin(LCD_COLUMNS, LCD_ROWS); // This also clears display #endif #if defined(USE_LCD) myLCD.setCursor(0, 0); myLCD.print(F("IRMP all v" VERSION_IRMP)); myLCD.setCursor(0, 1); myLCD.print(F(__DATE__)); #endif } void loop() { if (sIRMPDataAvailable) { sIRMPDataAvailable = false; /* * Serial output * takes 2 milliseconds at 115200 */ irmp_result_print(&irmp_data); #if defined(USE_LCD) # if defined(USE_SERIAL_LCD) // This suppresses the receive of the 1. NEC repeat disableIRTimerInterrupt(); // disable timer interrupt, since it disturbs the LCD serial output # endif printIRResultOnLCD(); # if defined(USE_SERIAL_LCD) enableIRTimerInterrupt(); # endif #endif } #if defined(USE_LCD) && defined(ADC_UTILS_ARE_AVAILABLE) /* * Periodically print VCC */ if (millis() - sMillisOfLastVoltagePrint > MILLIS_BETWEEN_VOLTAGE_PRINT) { sMillisOfLastVoltagePrint = millis(); uint16_t tVCC = getVCCVoltageMillivoltSimple(); char tVoltageString[5]; dtostrf(tVCC / 1000.0, 4, 2, tVoltageString); myLCD.setCursor(11, 0); myLCD.print(tVoltageString); myLCD.print('V'); } #endif } /* * Here we know, that data is available. * Since this function is executed in Interrupt handler context, make it short and do not use delay() etc. * In order to enable other interrupts you can call interrupts() (enable interrupt again) after getting data. */ #if defined(ESP8266) || defined(ESP32) void IRAM_ATTR handleReceivedIRData() #else void handleReceivedIRData() #endif { #if defined(USE_LCD) && defined(ADC_UTILS_ARE_AVAILABLE) // reset voltage display timer sMillisOfLastVoltagePrint = millis(); #endif /* * Just print the data to Serial and LCD */ irmp_get_data(&irmp_data); sIRMPDataAvailable = true; } #if defined(USE_LCD) /* * LCD output for 1602 and 2004 LCDs * 40 - 55 Milliseconds per initial output for a 1602 LCD * for a 2004 LCD the initial clearing adds 55 ms. * The expander runs at 100 kHz :-( * 8 milliseconds for 8 bit; 10 ms for 16 bit code output * 3 milliseconds for repeat output * */ void printIRResultOnLCD() { static uint8_t sLastProtocolIndex; static uint16_t sLastProtocolAddress; # if (LCD_ROWS >= 4) static uint8_t sLastCommandPrintPosition = 13; const uint8_t tStartRow = 2; # else static uint16_t sLastCommand; static uint8_t sLastCommandPrintPosition; const uint8_t tStartRow = 0; bool tDisplayWasCleared = false; # endif /* * Print only if protocol or address has changed */ if (sLastProtocolIndex != irmp_data.protocol || sLastProtocolAddress != irmp_data.address) { sLastProtocolIndex = irmp_data.protocol; sLastProtocolAddress = irmp_data.address; # if (LCD_ROWS >= 4) // clear data lines myLCD.setCursor(0, tStartRow); myLCD.print(F(" ")); myLCD.setCursor(0, tStartRow + 1); myLCD.print(F(" ")); # else myLCD.clear(); tDisplayWasCleared = true; # endif /* * Show protocol name */ myLCD.setCursor(0, tStartRow); # if defined(__AVR__) const char *tProtocolStringPtr = (char*) pgm_read_word(&irmp_protocol_names[irmp_data.protocol]); myLCD.print((__FlashStringHelper*) (tProtocolStringPtr)); # else myLCD.print(irmp_protocol_names[irmp_data.protocol]); # endif /* * Show address */ myLCD.setCursor(0, tStartRow + 1); myLCD.print(F("A=")); printHex(irmp_data.address); # if (LCD_COLUMNS > 16) /* * Print prefix of command here, since it is constant string */ myLCD.setCursor(9, tStartRow + 1); myLCD.print(F("C=")); # endif } else { /* * Show or clear repetition flag */ # if (LCD_COLUMNS > 16) myLCD.setCursor(18, tStartRow + 1); # else myLCD.setCursor(15, tStartRow + 1); # endif if (irmp_data.flags & IRMP_FLAG_REPETITION) { myLCD.print('R'); return; // Since it is a repetition, printed data has not changed } else { myLCD.print(' '); } } /* * Command prefix */ uint16_t tCommand = irmp_data.command; # if (LCD_COLUMNS <= 16) // check if prefix position must change if (tDisplayWasCleared || (sLastCommand > 0x100 && tCommand < 0x100) || (sLastCommand < 0x100 && tCommand > 0x100)) { sLastCommand = tCommand; /* * Print prefix for 8/16 bit commands */ if (tCommand >= 0x100) { sLastCommandPrintPosition = 9; } else { myLCD.setCursor(9, tStartRow + 1); myLCD.print(F("C=")); sLastCommandPrintPosition = 11; } } # endif /* * Command data */ myLCD.setCursor(sLastCommandPrintPosition, tStartRow + 1); printHex(tCommand); } size_t printHex(uint16_t aHexByteValue) { myLCD.print(F("0x")); size_t tPrintSize = 2; if (aHexByteValue < 0x10 || (aHexByteValue > 0x100 && aHexByteValue < 0x1000)) { myLCD.print('0'); // leading 0 tPrintSize++; } return myLCD.print(aHexByteValue, HEX) + tPrintSize; } #endif // defined(USE_LCD) |
As with the examples centered around the Arduino-IRremote library, this code (and several of the other IRMP examples) has a pin definition file as well.
A lot of the code is centered around the LCD module. Around line 60, you will see instructions for changing to a serial (I2C) LCD or not using an LCD and just using the serial monitor.
In line 70, you’ll find a definition you can change to use a 4-line LCD module, the 2004 (20 x 4 display).
In Setup, look for the following lines:
1 2 3 |
irmp_init(); irmp_irsnd_LEDFeedback(true); // Enable receive signal feedback at LED_BUILTIN irmp_register_complete_callback_function(&handleReceivedIRData); |
These lines illustrate how to use the IRMP library; it differs from the method used for the Arduino-IRremote library.
A function, printIRResultOnLCD, handles all the LCD formatting.
Load the code onto the Arduino, get out a remote, and try it out. You will likely have to adjust the contrast potentiometer for the best LCD visibility.
This is a handy tool to have around when coding for remote controls.
Emulating Transmitters and Receivers
By now, you should have a pretty good handle on using both libraries with IR receivers and emitters. So we can get to work and design or own remote-controlled devices.
To illustrate this, I have decided to “clone” (or perhaps “emulate” is a better word) an IR-controlled LED lamp. It’s a simple device with a simple IR remote control that uses NEC command codes. The remote has buttons for power, plus controls to increase or decrease the LED lamp brightness. It also has some timer buttons, but I will ignore them for this experiment.
We will use an Arduino to emulate the lamp first, controlling the brightness and status of the “user LED” we wired to pin D5.
After that, we will use the three push buttons to emulate the transmitter.
Between the two projects, you should have enough information and sample code to design your own custom remote-controller devices with an Arduino or other microcontroller. No additional wiring is needed, as we have everything we need on the existing Arduino setup.
Receiver Code
We’ll start with the receiver. We will receive the IR commands from the existing remote control and use them to control our “user LED.” It should operate in a similar fashion to our LED lamp.
We’ll use PWM (Pulse Width Modulation) to control the LED brightness. On the Arduino Uno, six of the I/O pins are PWM-capable, including pin D5, where our User LED is attached. The brightness will be set in increments, and you can adjust the value of these increments to suit your personal taste.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
/* IR Remote Emulator - Receiving ir-remote-receive.ino Control LED brightness with 3-button remote Uses Arduino-IRremote Library - https://github.com/Arduino-IRremote/Arduino-IRremote Use SimpleReceiver example to determine button codes DroneBot Workshop 2023 https://dronebotworkshop.com */ // Specify protocol(s) - Must be listed before IRremote.hpp #define DECODE_NEC // Include required libraries #include <Arduino.h> #include <IRremote.hpp> // IR Receiver pin #define IR_RECEIVE_PIN 2 // LED Pin #define LED_PIN 5 // Data received variable volatile bool irDataReceived = false; // Lamp variables volatile bool lampPower = false; volatile int lampLevel = 255; // Button code variables (change to match your remote control) uint16_t codePower = 0x0A; uint16_t codeMinus = 0x09; uint16_t codePlus = 0x0B; // Receive Callback void ReceiveCallbackHandler() { // Decode IR data IrReceiver.decode(); // See if it matches one of our control codes if (IrReceiver.decodedIRData.command == codePower) { // Power button // Toggle lamp power variable lampPower = !lampPower; } else if (IrReceiver.decodedIRData.command == codeMinus) { // Minus Button // Only change level if lamp is on if (lampPower) { lampLevel = lampLevel - 16; if (lampLevel < 8) { // Don't let level drop to zero lampLevel = 8; } } } else if (IrReceiver.decodedIRData.command == codePlus) { // Plus Button // Only change level if lamp is on if (lampPower) { lampLevel = lampLevel + 16; if (lampLevel > 255) { lampLevel = 255; } } } // Set data received flag true irDataReceived = true; // Resume receiving IrReceiver.resume(); } void setup() { // Serial monitor Serial.begin(115200); // Set LED as an output pinMode(LED_PIN, OUTPUT); // Set LED off digitalWrite(LED_PIN, LOW); // Start IR Receiver with LED feedback on BUILTIN LED IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); // Attach Callback Handler IrReceiver.registerReceiveCompleteCallback(ReceiveCallbackHandler); } void loop() { // See if new data is available if (irDataReceived) { // Print variable values Serial.print("Power: "); Serial.println(lampPower); Serial.print("Level: "); Serial.println(lampLevel); // Set LED Power and Level if (lampPower) { // Lamp is powered, set level analogWrite(LED_PIN, lampLevel); } else { // Lamp is off digitalWrite(LED_PIN, LOW); } // Set data received flag false irDataReceived = false; } } |
We begin by defining the protocol(s) we wish to use. As my lamp is an NEC protocol device, this is what I have defined. If you are building an IR control from scratch, the NEC protocol is a good choice. Just be sure to set the address to a unique value to avoid conflicts with other IR remotes in the vicinity.
If you need to define a different protocol, see the SimpleReceiver example for a list of them.
We then include the library and define the pins for both the IR receiver and the User LED. After that, we define a boolean for the lamp power state and an integer for the level. These are both set volatile, as they are used in a callback function.
The next three lines are the codes for our buttons. If you are emulating another remote control, you will want to change these to match your control.
Next, we come to the Receive Callback function, which is called every time valid IR data is received.
In the callback function, we grab the button code and check it against the three code values we have defined.
If it matches the power key, then we toggle the boolean that represents the lamp state.
If it matches an up or down key, we increment or decrease the value of the LED PWM by 8 (you can change that if you like). We ensure that it does not go above 255 or below 8. I picked eight instead of zero, as I didn’t want the lamp to completely dim. This is the way the lamp we are emulating works.
In Setup, we start the serial monitor, define our LED as an output and turn it off, start the IR receiver, and attach the callback to the function we just described.
In the Loop, we see if there is new data. If it is, we examine the variables for both the LED status (on or off) and LED brightness. We use these to control the LED.
Note that we only change brightness when the LED status is “on”; if it is off, then the controls are recognized but ignored.
Load the sketch and get out your remote. If you coded the values correctly, you should be able to control the status and brightness of the LED.
Also note that if you turn the LED off, it retains its brightness level.
Transmitter Code
Now let’s take a look at the code we will use for our transmitter, or “remote control”. We’ll use our three push buttons to control the power and brightness of our LED lamp. You can also use it to control the previous experiment if you have a second Arduino.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
/* IR Remote Emulator - Sending ir-remote-send.ino Emulates three buttons of remote control Uses TinySender Library packaged with Arduino-IRremote Library -- https://github.com/Arduino-IRremote/Arduino-IRremote -- Uses pin-change interrupts for pushbuttons Use SimpleReceiver example to determine button codes DroneBot Workshop 2023 https://dronebotworkshop.com */ // Include required libraries #include <Arduino.h> #include "TinyIRSender.hpp" // Define IR LED pin #define IR_SEND_PIN 3 // Define the pins for the buttons const byte buttonPin1 = 10; const byte buttonPin2 = 9; const byte buttonPin3 = 8; // Button code variables (change to match your remote control) uint16_t codePower = 0x0A; uint16_t codeMinus = 0x09; uint16_t codePlus = 0x0B; // Variables to hold complete send parameters uint16_t sAddress = 0x00; volatile uint16_t sCommand = 0x0A; uint16_t sRepeats = 0; // Button presed flag volatile bool buttonPressed = false; // Button pin-change service routine ISR(PCINT0_vect) { // Port B Interrupt occured // Only run if buttonPressed is false, as a debounce technique if (!buttonPressed) { // Check if this was D10 (button 1) if (digitalRead(buttonPin1) == LOW) { //Pin D10 triggered the ISR on a Falling pulse sCommand = codePower; } // Check if this was D9 (button 2) if (digitalRead(buttonPin2) == LOW) { //Pin D9 triggered the ISR on a Falling pulse sCommand = codeMinus; } // Check if this was D8 (button 3) if (digitalRead(buttonPin3) == LOW) { //Pin D8 triggered the ISR on a Falling pulse sCommand = codePlus; } // Change button pressed flag buttonPressed = true; } } void setup() { // Serial monitor Serial.begin(115200); // Set switches as inputs with pullups pinMode(buttonPin1, INPUT_PULLUP); pinMode(buttonPin2, INPUT_PULLUP); pinMode(buttonPin3, INPUT_PULLUP); // Built-in LED setup for IR library pinMode(LED_BUILTIN, OUTPUT); // Setup pin-change interrupt masks for port B (pins 8 - 13) // Enable PCIE0 Bit0 = 1 (Port B) PCICR |= B00000001; // Enable PCINT0, PCINT1 & PCINT23 (Pins D8, D9 & D10) PCMSK0 |= B00000111; } void loop() { // See if a button has been pressed if (buttonPressed) { // Send IR code sendNEC(IR_SEND_PIN, sAddress, sCommand, sRepeats); // Print to serial monitor Serial.print("Sent command "); Serial.println(sCommand, HEX); // Short delay to debounce delay(100); // Reset the flag buttonPressed = false; } // Another short delay delay(100); } |
There are a couple of interesting things about this sketch:
- It uses the TinySender library. This small library is included with the Arduino-IRremote library. It has a smaller footprint, but not as many features.
- It uses pin change interrupts to detect pushbutton presses.
We begin by including the Arduino and TinySender libraries.
Next, we define the pins for the IR emitter LED and for the three push buttons. After that are the button code variables; as with the receiver sketch, you can change these if you wish.
The next three variables hold the IR data values that we will be sending. Pay note to the first one, the address. I am using hexadecimal 0x00, as this is what my remote lamp uses, but if you are emulating a different device, you will need to change this.
There is also a boolean that is set when a button has been pressed.
After defining the values, we move into the pin-change interrupt service routine. This will be called whenever one of the buttons in our pin-change group is called. There are three groups, and all of our push buttons are in group (or port) B , so any activity on any group B button triggers this interrupt.
In the interrupt service routine, we scan to see which button was pressed. We then set the value of the command variable to match the button, and we set the buttonPresed flag to true.
In Setup, we activate the Serial Monitor and define the buttons as inputs with internal pull-up resistors. We also define the built-in LED as an output.
After that, we set up pin-change interrupt masks. This enables the three pins we are using for our buttons. If you want to add buttons, you must change the mask. It’s a simple binary pattern; look at it, and you will see the three I/O pins we are using.
In the Loop, we see if a button was pressed by checking the buttonPressed boolean. If it has, then we send the IR code using the current command value.
We add a few short delays to debounce and then look for a pressed button again.
Load the sketch and try it out! If you have coded the buttons with the correct values, you should be able to use them to control your IR device.
This can obviously be expanded upon to build bigger remotes.
Capturing IR Remote Codes
Up to this point, we have been decoding IR remote control signals and sending them with a known protocol. But what do you do if you don’t know the protocol, or if you don’t have a setting for it in your library?
Instead of decoding the IR signal, another option is to capture it and save the raw data. This data can then be used to resend the IR signal or to recognize it again.
There are a couple of examples included with the IRremote library that illustrate how to capture and work with raw IR data.
Both of the following examples perform the same function; however, they use different techniques to achieve it.
The functionality is as follows:
- The sketch sends and receives IR codes.
- By default, it is in receive mode.
- When an IR transmission is detected, it captures it and saves it. How it does this is different in each sketch.
- After saving the transmission, you can use a pushbutton to send it again. You can keep using the button to send it.
- If you are not sending data, then you are in receive mode.
- If a new IR packet is captured, then it is now the one sent when the button is pressed.
ReceiveAndSend Code
In this sketch, we attempt to decode the incoming packet first, using known libraries. If we succeed, then we just store the protocol type, address, and command code for the button.
If we can’t determine what protocol the incoming data is, we store the raw data for retransmission.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
/* * ReceiveAndSend.cpp * * Record and play back last received IR signal at button press. * The logic is: * If the button is pressed, send the IR code. * If an IR code is received, record it. * If the protocol is unknown or not enabled, store it as raw data for later sending. * * An example for simultaneous receiving and sending is in the UnitTest example. * * An IR detector/demodulator must be connected to the input IR_RECEIVE_PIN. * * A button must be connected between the input SEND_BUTTON_PIN and ground. * A visible LED can be connected to STATUS_PIN to provide status. * * * Initially coded 2009 Ken Shirriff http://www.righto.com * * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. * ************************************************************************************ * MIT License * * Copyright (c) 2009-2023 Ken Shirriff, Armin Joachimsmeyer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is furnished * to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ************************************************************************************ */ #include <Arduino.h> #include "PinDefinitionsAndMore.h" // Define macros for input and output pin etc. /* * Specify which protocol(s) should be used for decoding. * If no protocol is defined, all protocols (except Bang&Olufsen) are active. * This must be done before the #include <IRremote.hpp> */ //#define DECODE_DENON // Includes Sharp //#define DECODE_JVC //#define DECODE_KASEIKYO //#define DECODE_PANASONIC // alias for DECODE_KASEIKYO //#define DECODE_LG #define DECODE_NEC // Includes Apple and Onkyo //#define DECODE_SAMSUNG //#define DECODE_SONY //#define DECODE_RC5 //#define DECODE_RC6 //#define DECODE_BOSEWAVE //#define DECODE_LEGO_PF //#define DECODE_MAGIQUEST //#define DECODE_WHYNTER //#define DECODE_FAST // #if !defined(RAW_BUFFER_LENGTH) # if RAMEND <= 0x4FF || RAMSIZE < 0x4FF #define RAW_BUFFER_LENGTH 120 # elif RAMEND <= 0xAFF || RAMSIZE < 0xAFF // 0xAFF for LEONARDO #define RAW_BUFFER_LENGTH 400 // 600 is too much here, because we have additional uint8_t rawCode[RAW_BUFFER_LENGTH]; # else #define RAW_BUFFER_LENGTH 750 # endif #endif //#define EXCLUDE_UNIVERSAL_PROTOCOLS // Saves up to 1000 bytes program memory. //#define EXCLUDE_EXOTIC_PROTOCOLS // saves around 650 bytes program memory if all other protocols are active //#define NO_LED_FEEDBACK_CODE // saves 92 bytes program memory //#define RECORD_GAP_MICROS 12000 // Default is 5000. Activate it for some LG air conditioner protocols //#define SEND_PWM_BY_TIMER // Disable carrier PWM generation in software and use (restricted) hardware PWM. //#define USE_NO_SEND_PWM // Use no carrier PWM, just simulate an active low receiver signal. Overrides SEND_PWM_BY_TIMER definition // MARK_EXCESS_MICROS is subtracted from all marks and added to all spaces before decoding, // to compensate for the signal forming of different IR receiver modules. See also IRremote.hpp line 142. #define MARK_EXCESS_MICROS 20 // Adapt it to your IR receiver module. 20 is recommended for the cheap VS1838 modules. //#define DEBUG // Activate this for lots of lovely debug output from the decoders. #include <IRremote.hpp> int SEND_BUTTON_PIN = APPLICATION_PIN; int DELAY_BETWEEN_REPEAT = 50; // Storage for the recorded code struct storedIRDataStruct { IRData receivedIRData; // extensions for sendRaw uint8_t rawCode[RAW_BUFFER_LENGTH]; // The durations if raw uint8_t rawCodeLength; // The length of the code } sStoredIRData; bool sSendButtonWasActive; void storeCode(); void sendCode(storedIRDataStruct *aIRDataToSend); void setup() { pinMode(SEND_BUTTON_PIN, INPUT_PULLUP); Serial.begin(115200); #if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/|| defined(SERIALUSB_PID) || defined(ARDUINO_attiny3217) delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor! #endif // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_IRREMOTE)); // Start the receiver and if not 3. parameter specified, take LED_BUILTIN pin from the internal boards definition as default feedback LED IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); Serial.print(F("Ready to receive IR signals of protocols: ")); printActiveIRProtocols(&Serial); Serial.println(F("at pin " STR(IR_RECEIVE_PIN))); IrSender.begin(); // Start with IR_SEND_PIN as send pin and enable feedback LED at default feedback LED pin Serial.print(F("Ready to send IR signals at pin " STR(IR_SEND_PIN) " on press of button at pin ")); Serial.println(SEND_BUTTON_PIN); } void loop() { // If button pressed, send the code. bool tSendButtonIsActive = (digitalRead(SEND_BUTTON_PIN) == LOW); // Button pin is active LOW /* * Check for current button state */ if (tSendButtonIsActive) { if (!sSendButtonWasActive) { Serial.println(F("Stop receiving")); IrReceiver.stop(); } /* * Button pressed -> send stored data */ Serial.print(F("Button pressed, now sending ")); if (sSendButtonWasActive == tSendButtonIsActive) { Serial.print(F("repeat ")); sStoredIRData.receivedIRData.flags = IRDATA_FLAGS_IS_REPEAT; } else { sStoredIRData.receivedIRData.flags = IRDATA_FLAGS_EMPTY; } Serial.flush(); // To avoid disturbing the software PWM generation by serial output interrupts sendCode(&sStoredIRData); delay(DELAY_BETWEEN_REPEAT); // Wait a bit between retransmissions } else if (sSendButtonWasActive) { /* * Button is just released -> activate receiving */ // Restart receiver Serial.println(F("Button released -> start receiving")); IrReceiver.start(); } else if (IrReceiver.decode()) { /* * Button is not pressed and data available -> store received data and resume */ storeCode(); IrReceiver.resume(); // resume receiver } sSendButtonWasActive = tSendButtonIsActive; delay(100); } // Stores the code for later playback in sStoredIRData // Most of this code is just logging void storeCode() { if (IrReceiver.decodedIRData.rawDataPtr->rawlen < 4) { Serial.print(F("Ignore data with rawlen=")); Serial.println(IrReceiver.decodedIRData.rawDataPtr->rawlen); return; } if (IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT) { Serial.println(F("Ignore repeat")); return; } if (IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_AUTO_REPEAT) { Serial.println(F("Ignore autorepeat")); return; } if (IrReceiver.decodedIRData.flags & IRDATA_FLAGS_PARITY_FAILED) { Serial.println(F("Ignore parity error")); return; } /* * Copy decoded data */ sStoredIRData.receivedIRData = IrReceiver.decodedIRData; if (sStoredIRData.receivedIRData.protocol == UNKNOWN) { Serial.print(F("Received unknown code and store ")); Serial.print(IrReceiver.decodedIRData.rawDataPtr->rawlen - 1); Serial.println(F(" timing entries as raw ")); IrReceiver.printIRResultRawFormatted(&Serial, true); // Output the results in RAW format sStoredIRData.rawCodeLength = IrReceiver.decodedIRData.rawDataPtr->rawlen - 1; /* * Store the current raw data in a dedicated array for later usage */ IrReceiver.compensateAndStoreIRResultInArray(sStoredIRData.rawCode); } else { IrReceiver.printIRResultShort(&Serial); IrReceiver.printIRSendUsage(&Serial); sStoredIRData.receivedIRData.flags = 0; // clear flags -esp. repeat- for later sending Serial.println(); } } void sendCode(storedIRDataStruct *aIRDataToSend) { if (aIRDataToSend->receivedIRData.protocol == UNKNOWN /* i.e. raw */) { // Assume 38 KHz IrSender.sendRaw(aIRDataToSend->rawCode, aIRDataToSend->rawCodeLength, 38); Serial.print(F("raw ")); Serial.print(aIRDataToSend->rawCodeLength); Serial.println(F(" marks or spaces")); } else { /* * Use the write function, which does the switch for different protocols */ IrSender.write(&aIRDataToSend->receivedIRData); printIRResultShort(&Serial, &aIRDataToSend->receivedIRData, false); } } |
The sketch starts like many of the other examples, with a list of protocols where only the NEC protocol is not remarked out.
We then define a buffer whose size is dependent upon the memory available. This is determined using constants in the pin definitions file.
After including the library, we define the pin for our send push button. This is where we need to change the code to match our wiring.
Change the line
1 |
int SEND_BUTTON_PIN = APPLICATION_PIN; |
to read
1 |
int SEND_BUTTON_PIN = 10; |
This will have us use PB1 as our push button.
We then build a data structure to store the recorded code. It has elements for the decoded IR data, as well as raw IR data, which we use for devices that we can’t decode. We also define a boolean to indicate that the send button was active.
In Setup, we set the button pin to an input with an internal pull-up resistor, and we set up the serial monitor. We start both the IR receiver and IR sender and print the status to the serial monitor.
In the Loop, we check to see if the button was pressed. If it was, then we stop receiving and send the stored code.
There is also a function named storeCode, which handles most of the receiver tasks. The function checks to see if it can decode the incoming data. If it can’t decide the protocol, then it stores the timing entries as raw data.
Load the code to the Arduino. If you happen to still have the AllProtocols LCD demo assembled, it can be handy to test this with. Otherwise, you can test with a remote and its target device.
Aim the remote control at the demo and press a button. You should observe the captured data on the serial monitor. If it is a known protocol, you will see the decoded data; if it is unknown, then you’ll see some raw data and statistics.
Now, the data has been captured. Press button PB1. This should send the captured data out via the IR emitter LED.
This technique could be the basis of a number of advanced experiments.
ReceiveAndSendDistanceWidth Code
This sketch performs the same function as the previous one, but this time, we don’t bother trying to decode the signal. We also use a different method of capturing raw data, one that can work with many more controls.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
/* * ReceiveAndSendDistanceWidth.cpp * * Record and play back last received distance width IR signal at button press. * Using DistanceWidthProtocol covers a lot of known and unknown IR protocols, * and requires less memory than raw protocol. * * The logic is: * If the button is pressed, send the IR code. * If an IR code is received, record it. * * An example for simultaneous receiving and sending is in the UnitTest example. * * An IR detector/demodulator must be connected to the input IR_RECEIVE_PIN. * * A button must be connected between the input SEND_BUTTON_PIN and ground. * A visible LED can be connected to STATUS_PIN to provide status. * * * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. * ************************************************************************************ * MIT License * * Copyright (c) 2023 Armin Joachimsmeyer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is furnished * to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ************************************************************************************ */ #include <Arduino.h> #include "PinDefinitionsAndMore.h" // Define macros for input and output pin etc. #if !defined(IR_SEND_PIN) #define IR_SEND_PIN 3 #endif /* * Specify DistanceWidthProtocol for decoding. This must be done before the #include <IRremote.hpp> */ #define DECODE_DISTANCE_WIDTH // Universal decoder for pulse distance width protocols // #if !defined(RAW_BUFFER_LENGTH) # if RAMEND <= 0x4FF || RAMSIZE < 0x4FF #define RAW_BUFFER_LENGTH 120 # elif RAMEND <= 0xAFF || RAMSIZE < 0xAFF // 0xAFF for LEONARDO #define RAW_BUFFER_LENGTH 400 // 600 is too much here, because we have additional uint8_t rawCode[RAW_BUFFER_LENGTH]; # else #define RAW_BUFFER_LENGTH 750 # endif #endif //#define NO_LED_FEEDBACK_CODE // saves 92 bytes program memory //#define RECORD_GAP_MICROS 12000 // Default is 5000. Activate it for some LG air conditioner protocols //#define SEND_PWM_BY_TIMER // Disable carrier PWM generation in software and use (restricted) hardware PWM. //#define USE_NO_SEND_PWM // Use no carrier PWM, just simulate an active low receiver signal. Overrides SEND_PWM_BY_TIMER definition //#define DEBUG // Activate this for lots of lovely debug output from the decoders. #include <IRremote.hpp> #define SEND_BUTTON_PIN APPLICATION_PIN #define DELAY_BETWEEN_REPEATS_MILLIS 70 // Storage for the recorded code, pre-filled with NEC data IRRawDataType sDecodedRawDataArray[RAW_DATA_ARRAY_SIZE] = { 0x7B34ED12 }; // address 0x12 command 0x34 DistanceWidthTimingInfoStruct sDistanceWidthTimingInfo = { 9000, 4500, 560, 1690, 560, 560 }; // NEC timing uint8_t sNumberOfBits = 32; bool sSendButtonWasActive; void setup() { pinMode(SEND_BUTTON_PIN, INPUT_PULLUP); Serial.begin(115200); #if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/|| defined(SERIALUSB_PID) || defined(ARDUINO_attiny3217) delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor! #endif // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_IRREMOTE)); // Start the receiver and if not 3. parameter specified, take LED_BUILTIN pin from the internal boards definition as default feedback LED IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); Serial.println(F("Ready to receive pulse distance/width coded IR signals at pin " STR(IR_RECEIVE_PIN))); IrSender.begin(); // Start with IR_SEND_PIN as send pin and enable feedback LED at default feedback LED pin Serial.print(F("Ready to send IR signals at pin " STR(IR_SEND_PIN) " on press of button at pin ")); Serial.println(SEND_BUTTON_PIN); } void loop() { // If button pressed, send the code. bool tSendButtonIsActive = (digitalRead(SEND_BUTTON_PIN) == LOW); // Button pin is active LOW /* * Check for current button state */ if (tSendButtonIsActive) { if (!sSendButtonWasActive) { Serial.println(F("Stop receiving")); IrReceiver.stop(); } /* * Button pressed -> send stored data */ Serial.print(F("Button pressed, now sending ")); Serial.print(sNumberOfBits); Serial.print(F(" bits 0x")); Serial.print(sDecodedRawDataArray[0], HEX); Serial.print(F(" with sendPulseDistanceWidthFromArray timing=")); IrReceiver.printDistanceWidthTimingInfo(&Serial, &sDistanceWidthTimingInfo); Serial.println(); Serial.flush(); // To avoid disturbing the software PWM generation by serial output interrupts IrSender.sendPulseDistanceWidthFromArray(38, &sDistanceWidthTimingInfo, &sDecodedRawDataArray[0], sNumberOfBits, #if defined(USE_MSB_DECODING_FOR_DISTANCE_DECODER) PROTOCOL_IS_MSB_FIRST #else PROTOCOL_IS_LSB_FIRST #endif , 100, 0); delay(DELAY_BETWEEN_REPEATS_MILLIS); // Wait a bit between retransmissions } else if (sSendButtonWasActive) { /* * Button is just released -> activate receiving */ // Restart receiver Serial.println(F("Button released -> start receiving")); IrReceiver.start(); } else if (IrReceiver.decode()) { /* * Button is not pressed and data available -> store received data and resume * DistanceWidthTimingInfo and sNumberOfBits should be constant for all keys of the same IR remote / protocol */ IrReceiver.printIRResultShort(&Serial); if (IrReceiver.decodedIRData.protocol != UNKNOWN) { IrReceiver.printIRSendUsage(&Serial); if (memcmp(&sDistanceWidthTimingInfo, &IrReceiver.decodedIRData.DistanceWidthTimingInfo, sizeof(sDistanceWidthTimingInfo)) != 0) { Serial.print(F("Store new timing info data=")); IrReceiver.printDistanceWidthTimingInfo(&Serial, &IrReceiver.decodedIRData.DistanceWidthTimingInfo); Serial.println(); sDistanceWidthTimingInfo = IrReceiver.decodedIRData.DistanceWidthTimingInfo; // copy content here } else { Serial.print(F("Timing did not change, so we can reuse already stored timing info.")); } if (sNumberOfBits != IrReceiver.decodedIRData.numberOfBits) { Serial.print(F("Store new numberOfBits=")); sNumberOfBits = IrReceiver.decodedIRData.numberOfBits; Serial.println(IrReceiver.decodedIRData.numberOfBits); } if (sDecodedRawDataArray[0] != IrReceiver.decodedIRData.decodedRawDataArray[0]) { *sDecodedRawDataArray = *IrReceiver.decodedIRData.decodedRawDataArray; // copy content here Serial.print(F("Store new sDecodedRawDataArray[0]=0x")); Serial.println(IrReceiver.decodedIRData.decodedRawDataArray[0], HEX); } } IrReceiver.resume(); // resume receiver } sSendButtonWasActive = tSendButtonIsActive; delay(100); } |
The sketch is similar to the previous one in some respects, with one important consideration. Line 56 has a constant DECODE_DISTANCE_WIDTH. This puts the IR receiver into distance width protocol mode, which allows it to capture the distance between raw data pulses. It is important to put this line before the call to the IRremote library.
Once again, you need to change the button pin constant SEND_BUTTON_PIN. Change it from APPLICATION_PIN to 10.
The key to understanding this sketch is on lines 81-84. The three variables here are what we store every time we receive a valid IR packet, and it is what we transmit when the button is pressed. We store an array of the data, a data structure with timing info, and an integer with the number of bits.
In the Setup, we do the usual serial monitor setup, as well as setting the pin mode of the pushbutton to an input with pull-up. We also start the IR receiver and sender.
In the Loop, we check to see if the button was pressed. If it was, we send the packet using the IrSender.sendPulseDistanceWidthFromArray function.
Otherwise, we are in receive and store mode. We look for a valid packet, and when we find one, we extract its timing and data information and calculate the number of bits. Then, we store those values to be used for the next send.
Load the sketch to the Arduino and test it out. If you observe the serial monitor, you’ll see that every IR signal is treated as raw data.
I have some projects coming up in a future article and video that use this technique. It’s a great way to handle those remotes you can’t decode otherwise.
ESP32 Remote Control
We’ll finish everything up with a small project, a remote control based on the ESP32.
This remote will actually mimic the functionality of the 3-button remote we constructed with the Arduino, except we won’t have any physical buttons. Instead, we will use the WiFi capabilities of the ESP32 to have a web-based interface. This will allow you to expand upon the design to create a custom remote with as many buttons as you desire.
Our ESP32 will be set up as a WiFi access point, so this design doesn’t need to connect to a WiFi network.
One feature we will implement in this design that was missing from the previous one is the repeat function when a button is held down. As we can’t implement it exactly, we will be “faking” it!
ESP32 Remote Control Hookup
The hookup for the ESP32 remote control is very simple. All we really need is an IR emitter LED and a method of driving it.
We can just drive the IR LED with the 2N2222 we used earlier; that will work fine. You may wish to drop the 1k resistor on the base to 680 ohms, but otherwise, the hookup is the same. We still use 5-volts for the LED, even though the ESP32 is a 3.3-volt microcontroller.
Another method of driving the infrared emitter LED is to use a FET (Field Effect Transistor). This has efficiency advantages and also eliminates the need for a resistor on the GPIO pin. This connection is illustrated here:
Here is how we will hook up our ESP32. Any ESP32 module will work for this design. The TN0702 FET specified in the diagram is capable of 20 volts at about half an ampere, so it can be used with multiple emitter LEDs if you wish to experiment.
ESP32 Remote Control Code
Here is the code we will use for the ESP32 remote control. You can use it as the basis for designing your own remote.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
/* ESP32 IR Remote ir-remote-esp32.ino IR Remote control with web interface Uses Arduino-IRremote Library - https://github.com/Arduino-IRremote/Arduino-IRremote Uses ESPAsyncWebServer Library - https://github.com/me-no-dev/ESPAsyncWebServer Uses AsyncTCP Library - https://github.com/me-no-dev/AsyncTCP DroneBot Workshop 2023 https://dronebotworkshop.com */ // Include required libraries #include <Arduino.h> #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> #include "TinyIRSender.hpp" // WiFi Access Point credentials (use NULL for password if none required) const char *ssid = "ESP32-REMOTE"; // Enter SSID here const char *password = "12345678"; //Enter Password here // Async web server at port 80 AsyncWebServer server(80); // Define IR LED pin on GPIO4 #define IR_SEND_PIN 4 // Button code variables (change to match your remote control) uint16_t codePower = 0x0A; uint16_t codeMinus = 0x09; uint16_t codePlus = 0x0B; // Variables to hold complete send parameters (initialized with NEC 00 00) uint16_t sAddress = 0x00; volatile uint16_t sCommand = 0x00; uint16_t sRepeats = 0; // Flag for button held down volatile bool buttonDown = false; // HTML web page const char index_html[] PROGMEM = R"rawliteral( <!DOCTYPE HTML><html> <head> <title>Remote Control</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body { font-family: Times New Roman; text-align: center; margin:0px auto; padding-top: 30px;} .button { padding: 10px 20px; font-size: 24px; text-align: center; outline: none; color: #fff; background-color: #ff0522; border: none; border-radius: 5px; cursor: pointer; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-tap-highlight-color: rgba(0,0,0,0); } .button:hover {background-color: #ff0522 } .button:active { background-color: #1fe036; transform: translateY(2px); } </style> </head> <body> <h1>Lamp Control</h1> <p><button class="button" onmousedown="toggleCheckbox('pON');" ontouchstart="toggleCheckbox('pON');" onmouseup="toggleCheckbox('pOFF');" ontouchend="toggleCheckbox('pOFF');">Power</button></p> <p><button class="button" onmousedown="toggleCheckbox('uON');" ontouchstart="toggleCheckbox('uON');" onmouseup="toggleCheckbox('uOFF');" ontouchend="toggleCheckbox('uOFF');">Up</button></p> <p><button class="button" onmousedown="toggleCheckbox('dON');" ontouchstart="toggleCheckbox('dON');" onmouseup="toggleCheckbox('dOFF');" ontouchend="toggleCheckbox('dOFF');">Down</button></p> <script> function toggleCheckbox(x) { var xhr = new XMLHttpRequest(); xhr.open("GET", "/" + x, true); xhr.send(); } </script> </body> </html>)rawliteral"; void setup() { // Start serial monitor Serial.begin(115200); WiFi.mode(WIFI_AP); WiFi.softAP(ssid, password); Serial.println(); IPAddress IP = WiFi.softAPIP(); Serial.print("Access Point IP address: "); Serial.println(IP); // Send web page to client server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send_P(200, "text/html", index_html); }); // Receive an HTTP GET request from power button pressed server.on("/pON", HTTP_GET, [](AsyncWebServerRequest *request) { Serial.println("PWR ON"); sCommand = codePower; buttonDown = true; request->send(200, "text/plain", "OK"); }); // Receive an HTTP GET request from power button released server.on("/pOFF", HTTP_GET, [](AsyncWebServerRequest *request) { Serial.println("PWR OFF"); sCommand = 0x00; buttonDown = false; request->send(200, "text/plain", "OK"); }); // Receive an HTTP GET request from up button pressed server.on("/uON", HTTP_GET, [](AsyncWebServerRequest *request) { Serial.println("UP ON"); sCommand = codePlus; buttonDown = true; request->send(200, "text/plain", "OK"); }); // Receive an HTTP GET request from up button released server.on("/uOFF", HTTP_GET, [](AsyncWebServerRequest *request) { Serial.println("UP OFF"); sCommand = 0x00; buttonDown = false; request->send(200, "text/plain", "OK"); }); // Receive an HTTP GET request from down button pressed server.on("/dON", HTTP_GET, [](AsyncWebServerRequest *request) { Serial.println("DOWN ON"); sCommand = codeMinus; buttonDown = true; request->send(200, "text/plain", "OK"); }); // Receive an HTTP GET request from down button released server.on("/dOFF", HTTP_GET, [](AsyncWebServerRequest *request) { Serial.println("DOWN OFF"); sCommand = 0x00; buttonDown = false; request->send(200, "text/plain", "OK"); }); // Start the server server.begin(); } void loop() { // Ignore if command value is zero if (sCommand != 0x00) { // See if a button was pressed if (buttonDown) { // Send IR code sendNEC(IR_SEND_PIN, sAddress, sCommand, sRepeats); // Print send code to serial monitor Serial.print("Sent command "); Serial.println(sCommand, HEX); // Short delay for repeat delay(110); // See if button is still held down while (buttonDown && sCommand != 0x00) { // Same value, Send IR code with repeats sendNEC(IR_SEND_PIN, sAddress, sCommand, 1); Serial.print("Sent repeat command "); Serial.println(sCommand, HEX); delay(240); } } } } |
Before you can use this code, you will need to install a couple of libraries, ones that are not in the Arduino IDE Library Manager. You can get both of them on GitHub
Both of these libraries are required to allow asynchronous communications between the web client and the server. This is necessary, as we don’t want to have to refresh our web page every time we press a button.
We’ll include all of the required libraries and then set up the credentials for the access point that the ESP32 will create. You can change these to any value you want; the password should be at least eight characters. You can also use a null value if you don’t want a password.
Next, we start the asynchronous web server on port 80.
The next bit of the code might look familiar, as it’s essentially the same variables we used in the sketch with the tree pushbuttons. These define our IR emitter pin, the codes we are using, and some variables to hold the transmitted data values. There is also a flag to indicate that a button is being held down.
The next constant is actually the entire HTML code for the web page, including CSS and JavaScript. It is easy to read and modify.
The JavaScript is the key to making things work. Each button looks for events caused by buttons being either clicked or pressed (you need both for web pages and phones). Those events call a JavaScript function (toggleCheckbox) that sends data to the server using an asynchronous method.
In Setup, we start the serial monitor and then set the ESP32 WiFi up as an Access Point. Once the access point is established, we print our server’s IP address to the serial monitor. Our client will need this to connect.
We then define how the server should respond to specific asynchronous transactions. As most of the transactions are for buttons, we will use them to determine which button was pressed and if it is being held down. We set the buttonDown boolean and command value according to the button and its state.
After defining the responses, we start the server.
In the Loop, we test to see if a button was pressed. If it was, we send the values for the address and command using sendNEC (check the documentation if you use another protocol).
We also delay and see if the button is still held down. If it is, we need to send a repeat code.
As the TinyIRSender library does not have a function to only send a repeat code, we actually send a command again, followed by a repeat code. So, it is not a “true” repeat, but it accomplishes the same thing!
Load the code and give it a try. You will have to connect to the WiFi access point first with a phone, tablet, or computer. Afterward, go to the address in the serial monitor using a web browser.
The page does not have a security certificate, so if your browser gives you grief, try another one. Firefox accepts pages without certificates.
Once you are there, the page should display.
Try it out; it should duplicate the function of your remote control
Of course, you can extend this technique to have more buttons and functions.
ESP32 Receiver Hookup
One final hookup I will leave you with is for the ESP32 and an IR receiver.
Unlike the Arduino, which operates at 5 volts, the ESP32 will require a more modern IR Sensor that can work with lower voltages. The TSOP38438 we used earlier will work great, as will most 3.3-volt IR sensor modules.
As with the previous hookups, we are placing a 2.2uf capacitor near the IR sensor to reduce any power supply noise.
With this hookup, you can run the example code included with both libraries.
Conclusion
Interfacing a microcontroller with infrared remote controls and IR-controlled devices opens up dozens of project possibilities. You could, for example, have some lights that are controlled by your television remote, coming on when the TV is switched on.
I have three more IR remote projects that I started working on while I was researching this article; you’ll be seeing them soon. And I’m sure you have several ideas for adapting this technology to your own needs.
So get out there and use invisible light beans to control your world. All you need is a microcontroller and a few IR devices, and you’re set to take command.
Just remember to get off the couch once in a while!
Parts List
Here are some components you might need to complete the experiments in this article.
Vishay TSOP34438 DigiKey
Vishay TSOP38238 DigiKey
OSRAM SFH 4544 DigiKey
Resources
Code Samples – All of the code used in the article in one easy-to-use ZIP file!
Article PDF – A PDF version of this article in a ZIP file.
Arduino IRremote Library – Arduino IRremote Library on GitHub
IRMP Library – Infrared Multi-Protocol Decoder + Encoder Library on GitHub
ESPAsyncWebServer Library – ESPAsyncWebServer Library on GitHub
AsyncTCP Library – AsyncTCP Library on GitHub
Vishay TSOP34438 – IR Receiver Module Specs
Vishay TSOP38238 – IR Receiver Module Specs
TL1838 – IR Receiver Module Specs
OSRAM SFH 4544 – 940nm IR Emitter LED
Another GREAT article! sIn the Infrared Light secstion you say Light can be measured by its wavelength, and most infrared remotes operate in wavelengths ranging from 850 to 960 nanometers. By far, the most common wavelength is 940nm.
Also in the Carrier Frequency the most common carrier frequency is 38 kHz.
Can you expand on both?
Is there a relationship between the two?
Personal note: You provide so many great videos. THANK YOU!
I saw no one responded. Hope this might help. As frequency increases, wavelength decreases. Ever toss a stone in the water and see the waves? The closer each wave is to each other the frequency increases. In this case think of light in different colors. Like the rainbow. Infrared is beyond visible range, just beyond red. The carrier frequency is basically giving the light a variation in amount of power. So what you have is an infrared light that if visible would increase and decrease as a wave in itself, but always infrared. That is the carrier wave. From there… Read more »
Hi Bill.
Fantastic Video. Just a note you mentioned a 2N3906 transistor it should be a 2N3904. The 2N3906 in a PNP. Please disregard if you are already aware.
Kindest Regards
After dropping out of the ESP32 scene for a break I’m back. I had tried to create a simple mute button for the TV. With the idea of using like a big red button. We’re always fumbling to find the mute button all the time. I wanted one big one you couldn’t miss. lol However, all the code online seemed to work on older TVs, but not our Samsung. Even bought an IR Remote development board. Another bust. At that point I had spent more time than it was worth. This may make me give it a quick try again.