REMOTE CONTROL

This page is part of my Model Remodel series of articles and a subset of my Arduino section.

DISCLAIMER: If you choose to attempt any of these modifications, you assume all risks thereof. I just wanted to share my experiences here. Neither Fanhome, nor myself, are responsible for any damages that may occur.


Now that our Arduino has been programmed to flash a couple of LEDs, I can explain how I added the next piece of functionality to my Enterprise D model –  controlling the Arduino externally. There are many options available, such as: wired switches/pushbuttons, IR (infrared), Bluetooth, wireless (Wi-Fi), and even a digital keypad. However, since I wanted to be able to control the model’s functions from a distance, this ruled out any local switches or the keypad. Furthermore, adding Bluetooth or Wi-Fi means we would need to create at least a basic application for desktop and/or mobile devices.

Instead, I chose the simplest option of using IR (infrared). IR signals use a basic transmitter and receiver setup. With my build, the IR Transmitter is located inside a handheld remote control. The IR Receiver needs to be placed somewhere on or in the model (on my build, I plan to locate it in a custom display base). These two sensors need direct line-of-sight to each other to operate correctly as they use an invisible part of the light spectrum to send data to each other. However, this small limitation is perfectly fine for me.


The IR Hardware

When I bought my first Arduino, I purchased this Elegoo Uno Starter Kit for about $40. Not only did it include the Uno, it provided a lot of bits and pieces to try out different circuits and projects. Two of the included items in this kit are an IR Remote Control and IR Receiver board:

The IR Remote Control (Transmitter)

The IR Remote Control supplied with this particular kit is a 21-button handheld device powered by a button-cell battery. Pressing a button on it causes the remote to transmit a coded signal via a small infrared LED located in the top edge. This signal is modulated at a frequency of 38 Khz. Knowing this frequency value is only important because the IR Receiver needs to ‘listen’ at the same frequency. Matched sets of IR Remotes and Receivers for Arduino projects are inexpensive and can be purchased on their own without the rest of my Starter Kit above:

The IR Receiver

The IR Receiver circuit board supplied with my Starter Kit uses a TSOP-1838 sensor, which is matched to the same 38 Khz band and is designed to work with the IR Remote Control:

My IR Receiver has three connection pins. Two pins are used to supply it with 5 VDC (Volts of Direct Current). The third pin is used to relay the data it receives from the IR Remote Control to our Arduino. These pins are typically labeled as:

  • G or GND = Ground
  • R or VCC = +5 VDC In
  • Y or DAT = Data Out

To connect this IR Receiver to our Uno, we can follow these steps. I have included descriptions of the wire colors I used in the diagram to help explain these connections clearly. Of course, you can use any color wiring you prefer.

  • Connect the Ground pin of the IR Receiver to our negative ‘power rail’ on the breadboard (the short grey wire)
  • Connect the +5 VDC In pin of the IR Receiver to the positive ‘power rail’ on the breadboard (the short red wire)
  • Connect the Data Out pin of the IR Receiver to Pin 2 of the Arduino (the orange wire)
  • Connect the 5V pin socket of the Arduino to the positive ‘power rail’ of the breadboard (the long red wire)

Creating a ‘Capture IR Remote Codes’ Sketch

With the IR Receiver wired in place, we can turn to creating a new sketch for our Arduino that will let us capture the specific command received when we push each button on the IR Remote Control.

/*
  A sketch to capture the codes transmitted by our IR Remote Control
*/
#include <IRremote.hpp>               // Include the IRremote.hpp library
const byte IR_RECEIVE_PIN = 2;        // Receive IR Data Out on Pin 2
void setup() {
  // Enable the Serial Monitor at 9600 baud
  Serial.begin(9600);
  // Activate the IR Receiver
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
}
void loop() {
  // Check to see if an IR code is received
  if (IrReceiver.decode()) {          
    // Print out the IR code data to the Serial Monitor
    IrReceiver.printIRResultShort(&Serial);
    // Print out a blank line to the Serial Monitor
    Serial.println();
    // Tell the IR Receiver to listen for the next code
  }
}

How this Sketch Works

Libraries

Open-source programmers around the world have created an almost endless supply of small programs that can be used in our sketches to make our lives simpler. When they freely share these programs with the Arduino community, we can then easily import them as libraries.

In this particular sketch, I have used the #include command to the grab the IRremote.hpp library and embed it into our code. Many libraries, like this one, are readily available inside the Arduino Editor. This particular library not only does the difficult work of demodulating and decoding IR signals, but it also contains a bunch of premade functions we can use to take full advantage of our new IR circuit.

Setting the IR_RECEIVE_PIN

We need to receive the Data Out signal from our IR Receiver on one of our Arduino’s DIGITAL pins. In this case, I set it to use Pin 2.

Enable the Serial Monitor

When the Arduino is connected to your computer via the USB cable, we can enable the ability to send and receive text-based messages between us and the Arduino using the Serial Monitor. The Serial.begin(9600) command I used enables this Serial Monitor connection and configures it to use a speed of 9600 baud. This command is only needed once, so it has been placed inside the setup() function.

Once the Arduino is programmed, we can then access the Serial Monitor in the Arduino Editor by clicking the ‘Monitor’ button in the left menu. This will open the Serial Monitor window.

NOTE: The Serial Monitor window is only accessible when the Arduino is connected to the computer. Also, there is a baud rate dropdown menu in the Arduino Editor that must be set to match the same speed we programmed in our sketch with the Serial.begin command. In this case, 9600 baud:

Activate the IR Receiver

The IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK) command utilizes one of the embedded functions of our IRremote.hpp library to both activate the IR Receiver using the IR_RECIEVE_PIN (Pin 2) and enable the tiny LED on the IR Receiver board to turn on when any IR signal is received. This command is also only needed once, so it has been placed inside the setup() function as well.

Check if any IR Codes Have Been Received

The IrReceiver.decode() function is also from the IRremote.hpp library. This function will return a ‘true’ value when any IR code is received by the IR Receiver board.  By wrapping this function inside an if statement, we can then execute specific commands when an IR code is received.

Display the Received IR Code in the Serial Monitor

The IrReceiver.printIRResultShort(&Serial) command tells the Arduino to send (output) a formatted response containing the decoded IR signal data to the Serial Monitor window.

The Serial.println() command simply tells the Arduino to send an empty (blank) line to make reading the output easier.

Resume Listening for the next IR Codes

Once we have received an IR code and did something in response to it, we can then tell the IR Receiver to resume ‘listening’ for the next IR code. This is what the IrReceiver.resume() command does.

Download my Sketch

To help others, I have shared this sketch via the Arduino Editor. Simply click the Open Code or Download buttons!

Using this ‘Capture IR Remote Codes’ Sketch

After verifying and uploading this sketch, we should now be able to see our IR capture setup in action!

By pressing a button on the IR Remote Control while aiming the remote at the sensor on the IR Receiver, we should see an corresponding output in the Serial Monitor. First, I pressed the one (1) button on the IR Remote Control and this text appeared in the Serial Monitor window.

NOTE: Your specific model of IR Remote Control may send different command codes for each button. The command codes I show here are based on the IR Remote Control included in my Elegoo Starter Kit:

The important data in this output is the value after ‘Command=’, in this case 0xC. Each button on the IR Remote Control will transmit a different Command= value. For example, this time I pressed the two (2) button and a new line of text that appeared in the Serial Monitor window. We can see that pressing this button resulted in the Command= value of 0x18.

Repeating this process for every button on the IR Remote Control results in a full list of Command= values. I created this list using my own remote, so your list of values may be different:

IR REMOTE CONTROL BUTTONCommand= VALUE
Power0x45
VOL+0x46
FUNC/STOP0x47
|<< (Rewind)0x44
>|| (Play/Pause)0x40
>>| (Fast Forward)0x43
Arrow Down0x7
VOL-0x15
Arrow Up0x9
EQ0x19
ST/REPT0xD
00x16
10xC
20X18
30x5E
40x8
50x1C
60x5A
70x42
80x52
90x4A

Adding IR Remote Control to our Sketch

Now that we know which command codes are sent by each IR Remote Control button, we can use them to control our two LED circuits. I have removed the previous Timing effects from this sketch to keep it simple and focus on the new IR functionality.

/*
  A sketch that will let us control two LEDs using:
    Variables
    if/else Statements
    Infrared (IR) Remote Control
*/
#include <IRremote.hpp> // Include the IRremote.hpp library
const byte IR_RECEIVE_PIN = 2; // Receive IR Data Out on Pin 2
// Create a LEDPin variable and set its value to 3
const byte LEDPin = 3;
// Create a LEDPin2 variable and set its value to 4
const byte LED2Pin = 4;
void setup() {
  // Activate the IR Receiver
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
  
  // Set LEDPin (Pin 3) as an output pin
  pinMode(LEDPin, OUTPUT);
  // Set LED2Pin (Pin 4) as an output pin
  pinMode(LED2Pin, OUTPUT);
}
void loop() {
  // Check to see if an IR code is received
  if (IrReceiver.decode()) {

    // Check to see if the received IR Command= value is for Button 1
    if (IrReceiver.decodedIRData.command == 0xC) {
      //Change LED 1 State
      if (digitalRead(LEDPin)) {
        digitalWrite(LEDPin, LOW);
      } else {
        digitalWrite(LEDPin, HIGH);
      }
    }

    // Check to see if the received IR Command= value is for Button 2
    if (IrReceiver.decodedIRData.command == 0x18) {
      //Change LED 2 State
      if (digitalRead(LED2Pin)) {
        digitalWrite(LED2Pin, LOW);
      } else {
        digitalWrite(LED2Pin, HIGH);
      }
    }
    // Resume receiving IR signals
    IrReceiver.resume();
  }
}

How this Sketch Works

There is only one new function used in this sketch: IrReceiver.decodedIRData.command . This function retrieves the last Command= value received by the IR Receiver. We can then create an if statement that compares the output of this function against a known Command= value. If there is a match, the condition evaluates to true and we can execute specific commands. In this case, change the state of a LED.

I created two new if statements – one for each LED. Both statements are of the same design in that they look for matches using the  IrReceiver.decodedIRData.command function. When button 1 on the IR Remote Control is pressed, the Command= value of 0xC is received and this triggers the commands to change the state of the first LED. Likewise, when button 2 on the IR Remote Control is pressed, the Command= value of 0x18 is received and this triggers the second LED to change state.

Debouncing the IR Signal

When you press a button on your IR Remote Control, it typically sends the same command over and over again as long as the button is pressed. However, with our current sketch, you may encounter an issue where your IR Remote Control sends commands so fast that the feature you want to control does not seem to respond correctly.

Since our Arduino is very fast, when it receives two or more of the same IR commands back to back, it will execute them all. This could effectively change the state of our LED multiple times, perhaps turning it on and off. It can even happen so fast that we perceive that nothing has happened at all. Or, it may trigger the state change many times in a row, so while you expected the LED to turn on and stay on, instead it turns off. In the electronics world, this is called signal ‘bouncing’.

Luckily, we can negate this problem by adding a bit of ‘debounce’ code to our sketch. Similar to how we added Timing to control our flashing LED rates, we can capture the last time a IR command was processed and refuse to act on any further IR commands until a certain amount of time (in milliseconds) has passed.

To do this, we will first need to declare two more variables. One to capture the last time an IR signal was processed, and one to specify an interval between processing IR signals. Here, I used lastPressed and buttonDelay as the variable names, respectively. Since lastPressed will hold a timestamp, it should be an unsigned long variable type. For the buttonDelay variable, we should use the variable type most suitable for the value. After some testing, I settled on waiting a half second (500ms) between processing button presses, so the int variable type suits this value well. This buttonDelay value will not be changed by the sketch, so I also made it a constant variable (read only).

...
#include <IRremote.hpp> // Include the IRremote.hpp library
const byte IR_RECEIVE_PIN = 2; // Receive IR Data Out on Pin 2
// IR Button Debounce
unsigned long lastPressed = 0;
const int buttonDelay = 500;
// Create a LEDPin variable and set its value to 3
const byte LEDPin = 3;
...

Then, all we need to do is wrap all of the IR command processing code within another if statement that checks to see if the buttonDelay interval has passed since the last button was pressed. If it has, we reset the lastPressed value to the current time and execute the IR command processing code.

TIP: You can adjust the value of buttonDelay (in milliseconds) to suit your needs. A bigger value will help reduce unwanted behavior, but it will also slow down how quickly the sketch will respond to the next IR command. Find the balance that works for you!

...
void loop() {
  // Check to see if an IR code is received
  if (IrReceiver.decode()) {
    // Debounce - Check to see if the buttonDelay interval has passed
    if (millis() >= (lastPressed + buttonDelay)) {
      // Reset lastPressed time
      lastPressed = millis();
      // Check to see if the received IR Command= value is for Button 1
      if (IrReceiver.decodedIRData.command == 0xC) {
        //Change LED 1 State
        if (digitalRead(LEDPin)) {
          digitalWrite(LEDPin, LOW);
        } else {
          digitalWrite(LEDPin, HIGH);
        }
      }
      // Check to see if the received IR Command= value is for Button 2
      if (IrReceiver.decodedIRData.command == 0x18) {
        //Change LED 2 State
        if (digitalRead(LED2Pin)) {
          digitalWrite(LED2Pin, LOW);
        } else {
          digitalWrite(LED2Pin, HIGH);
        }
      }
    }
  }
  // Resume receiving IR signals
  IrReceiver.resume();
  }
}

Download my Sketch

To help others, I have shared this sketch via the Arduino Editor. Simply click the Open Code or Download buttons!

Using Switch/Case Statements

For my Enterprise D Model Remodel, I plan on using every button on the IR Remote Control to activate a particular function:

If we continued to use individual if statements to check for IrReceiver.decodedIRData.command each Command= code, we would create a lot of repetitive code. Thankfully, there is an optional alternative within C++ programing we can use to simplify this type of situation.

By using a switch/case statement we can check the IrReceiver.decodedIRData.command only once (the switch), and then have separated sections of code to execute per matching result (the case). There is no limit to the number of cases we can have within a switch statement. The syntax for using switch and case is shown here. It is important to remember to end each case section with a break command, as shown.

TIP: The default case is optional and will be executed if there are no matches to any case. It should be placed last in the case order.

switch (condition) {
  case 1:
    // Some code
  break;

  case 2:
    // Some other code
  break;
  default:
    // Code to run in case of no matches
  break;
...
}

If we take our sketch and use a single switch/case statement instead of multiple if statements, it looks like this:

...
void loop() {
  // Check to see if an IR code is received
  if (IrReceiver.decode()) {
    // Check to see if the buttonDelay interval has passed
    if (millis() >= (lastPressed + buttonDelay)) {
      // Reset lastPressed time
      lastPressed = millis();
      // Check the received IR code
      switch (IrReceiver.decodedIRData.command) {
        // If the received IR Command= value is for Button 1
        case 0xC:
          //Change LED 1 State
          if (digitalRead(LEDPin)) {
            digitalWrite(LEDPin, LOW);
          } else {
            digitalWrite(LEDPin, HIGH);
          }
        break; 
        // If the received IR Command= value is for Button 2
        case 0x18:
          //Change LED 2 State
          if (digitalRead(LED2Pin)) {
            digitalWrite(LED2Pin, LOW);
          } else {
            digitalWrite(LED2Pin, HIGH);
          }
        break;
      }
    }
    // Resume receiving IR signals
    IrReceiver.resume();
  }
}

Download my Sketch

To help others, I have shared this sketch via the Arduino Editor. Simply click the Open Code or Download buttons!

Next Arduino Page


CUSTOM FUNCTIONS – Reducing repetitive code in our sketch with our own functions

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.