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 BUTTON | Command= VALUE |
Power | 0x45 |
VOL+ | 0x46 |
FUNC/STOP | 0x47 |
|<< (Rewind) | 0x44 |
>|| (Play/Pause) | 0x40 |
>>| (Fast Forward) | 0x43 |
Arrow Down | 0x7 |
VOL- | 0x15 |
Arrow Up | 0x9 |
EQ | 0x19 |
ST/REPT | 0xD |
0 | 0x16 |
1 | 0xC |
2 | 0X18 |
3 | 0x5E |
4 | 0x8 |
5 | 0x1C |
6 | 0x5A |
7 | 0x42 |
8 | 0x52 |
9 | 0x4A |
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