Infrared Remote Control receiver with repeat key handling - Arduino
Blurb
IR remote control is very easy to implement with a cheap IR receiver module. It will work with almost any old remote control that you have lying around - don't throw them away! Instead of just using a bare Infrared photodiode, buy a module that contains the photodiode, preamplifier and carrier wave filters in the same package, like the Vishay TSOP4838 which works on both 3.3V and 5V controllers. There is a very nice Arduino IRremote library which has the code to handle almost all remote controller models.
But the IRremote library doesn't handle "repeat keys" very well - or keys which should not repeat. Here you'll find some simple wrapper code which will do this for you.
IR remote controls work on different carrier frequencies, so it's important to use an IR receiver module that works at the right frequency. Modules are available for 30kHz, 33kHz, 36kHz, 38kHz, 40kHz and 56kHz. You can usually find the frequency of your remote control from an internet search. 38kHz is the most common. Each manufacturer and product uses a different encoding, so the receivers [usually] don't get confused. Manufacturers say that the more recent (e.g. more expensive) IR receiver modules are better at handling interference from lights etc, but I found that the really cheap ones work very well in a normal room situation (unless you are doing arc welding).
This is the typical IR receiver module, with an onboard 'key pressed' LED and a power supply smoothing circuit. VCC can be 3.3 or 5V - use the same VCC as your microcontroller. 'Signal' connects to any digital input, define this input in the code.
How do you find the carrier frequency of an IR remote control?
Before you buy the IR receiver, you need to find out what carrier frequency you need for the IR remote control you want to use. If you have an infrared phototransistor (e.g. a QSD123 or 124) and an oscilloscope, then it's easy. Wire up the phototransistor as shown below, and view the waveform it receives when you press a button on the remote. The carrier wave is only visible when the IR signal is pulled low. Measure the time between the peaks of the carrier wave - that gives you the frequency. If your fancy 'scope has an FFT feature, you can use that to get the frequency. If you don't have a 'scope, another solution is to buy receivers for all the frequencies (30, 33, 36, 38, 40 and 56kHz), and see which one works best.
#pragma once
// Arduino Infrared Remote Control Library Wrapper
// 2023.11.27, info@muman.ch
// All rights reversed.
//
// Uses Arduino's IRremote library
// https://github.com/Arduino-IRremote/Arduino-IRremote
//
// Adds key repeat delays and a callback for when a button is pressed on the remote.
//
// Infrared remote controller knowledgebase, for those who wear anoraks (like me)
// https://www.bestmodulescorp.com/amfile/file/download/file_id/1939/product_id/797/
//
// NOTES
// The IRremote library contains direct references to 'Serial', and writes data to
// 'Serial' in many places if DEBUG is defined. If you get a "buffer full" exception,
// this could be why (the Zero uses Serial1). See "Debug directives" in IRremoteInt.h
// (line 126 in my version).
//
// The IRremote library also turns the built-in LED on and off, so you can't use it
// for anything else. I was unable to disable this, NO_LED_FEEDBACK_CODE did not work.
//
// Use '#define DECODE_xxx' to set the remote controller type, e.g.
// #define DECODE_SAMSUNG
// if not defined, it tries them all - so your app will get messages from ANY remote
// which is probably not what you want - but you need it to get started
// Arduino IRremote library
//#define DECODE_SAMSUNG
#include <IRremote.hpp>
namespace MattLabs
{
class IrRemote1
{
public:
typedef void (*IrKeyPressedCallback)(int irButton);
void begin(int irReceivePin, IrKeyPressedCallback irKeyPressedCallback);
void checkIrReceiver();
void setRepeatKeys(byte* repeatKeyList, int numberOfRepeatKeys);
bool isRepeatKey(int key);
private:
int lastKey;
unsigned long nextKeyTime = 0;
unsigned long lastKeyTime = 0;
IrKeyPressedCallback irKeyPressedCallback;
byte* repeatKeyList;
int numberOfRepeatKeys;
};
// Call this from setup()
void IrRemote1::begin(int irReceivePin, IrKeyPressedCallback irKeyPressedCallback)
{
IrReceiver.begin(irReceivePin, DISABLE_LED_FEEDBACK);
this->irKeyPressedCallback = irKeyPressedCallback;
}
// Call this regularly from loop() to detect key presses on the remote
void IrRemote1::checkIrReceiver()
{
unsigned long t = millis();
if (IrReceiver.decode()) {
lastKeyTime = t;
int key = IrReceiver.decodedIRData.command;
if (key != 0) {
// new key pressed
if (key != lastKey) {
nextKeyTime = t + 500; // delay before repeat starts
irKeyPressedCallback(key);
}
// same key still pressed
// if it's a repeating key then repeat
else if (isRepeatKey(key) && t > nextKeyTime) {
nextKeyTime = t + 100; // repeat delay
irKeyPressedCallback(key);
}
}
lastKey = key;
IrReceiver.resume();
}
else if (t > lastKeyTime + 200) { // key released delay, must be 2 x repeat delay
nextKeyTime = 0;
lastKey = 0;
}
}
// This sets the codes of the keys which repeat
// if none are defined, then all keys repeat by default
// NOTE! 'repeatKeyList' must point to a static byte array, not on the stack!
void IrRemote1::setRepeatKeys(byte* repeatKeyList, int numberOfRepeatKeys)
{
this->repeatKeyList = repeatKeyList;
this->numberOfRepeatKeys = numberOfRepeatKeys;
}
bool IrRemote1::isRepeatKey(int key)
{
// all keys repeat by default
if (repeatKeyList == NULL || numberOfRepeatKeys <= 0)
return true;
for (int i = 0; i < numberOfRepeatKeys; ++i) {
if (repeatKeyList[i] == key)
return true;
}
return false;
}
}
using namespace MattLabs;
/*
Name: IRremote1.ino
Created: 11/27/2023 3:20:05 PM
Author: info@muman.ch
All rights reversed.
*/
// Enables debug output, see debug_print.h
#define DEBUG 1
#include <Arduino.h>
// This defines the debug output via a serial port
// or removes it if DEBUG is not defined
// you may want to change it to 'Serial1'
#include "debug_print.h"
// The IR remote wrapper
#include "IrRemote1.h"
IrRemote1 irRemote;
// The IR receiver module is connected to this pin
#define IR_RECEIVE_PIN 8
// Called when a key is pressed on the remote
void irKeyPressed(int key)
{
// see debug_print.h for these macros
DEBUG_PRINT(key);
DEBUG_PRINT(" 0x");
DEBUG_PRINT2(key, HEX);
DEBUG_PRINTLN("");
}
void setup()
{
// the onboard LED is usually configured as an output
// note: the IRremote library also does this
pinMode(LED_BUILTIN, /*PinMode::*/OUTPUT);
// see debug_print.h for these macros
DEBUG_BEGIN(115200);
//DEBUG_PUTTYCLS(); // clear screen, if using PuTTY
DEBUG_PRINTLN("Ready");
// irKeyPressed is called when a key is pressed on the remote
irRemote.begin(IR_RECEIVE_PIN, irKeyPressed);
// TODO you can define only certain keys to be 'repeat keys',
// else they all repeat
//static byte repeatKeys[2] = { 26, 24 };
//irRemote.setRepeatKeys(repeatKeys, 2);
}
void loop()
{
// this calls irKeyPressed() when a key is pressed on the remote
irRemote.checkIrReceiver();
//...
}
You can use any old remote you have lying around - never throw them away! An old Christmas tree lights controller works quite well. And check under the sofa too.