Topics

► Games

► Sound & Music

► Watches & Clocks

► GPS

► Power Supplies

► Computers

► Graphics

► Thermometers

► Wearables

► Test Equipment

► Tutorials

► Libraries

► PCB-Based Projects

By processor

AVR ATtiny

► ATtiny10

► ATtiny2313

► ATtiny84

► ATtiny841

► ATtiny85

► ATtiny861

► ATtiny88

AVR ATmega

► ATmega328

► ATmega1284

AVR 0-series and 1-series

► ATmega4809

► ATtiny1604

► ATtiny1614

► ATtiny3216

► ATtiny3227

► ATtiny402

► ATtiny404

► ATtiny414

► ATtiny814

AVR DA/DB-series

► AVR128DA28

► AVR128DA32

► AVR128DA48

► AVR128DB28

ARM

► ATSAMD21

► RP2040

► RA4M1

About me

  • About me
  • Twitter
  • Mastodon

Feeds

RSS feed

IR Remote Control Detective [Updated]

11th April 2018

The IR Remote Control Detective decodes the signal from several common types of infrared remote control, such as audio, TV, and hobbyist remote controls. To use it you point a remote control at the receiver and press a key; it will then identify the protocol, and display the address and command corresponding to the key:

IRDetective.jpg

The IR Remote Control Detective displaying the code for a key on a Sony remote control.

It's based on an ATtiny85, an infrared receiver, and a 128x32 I2C OLED display. I originally built it to help design a project based on an IR remote control. It will be useful if you're trying to automate the control of an existing domestic device using its infrared remote control codes, or you want to make a device you can control with an existing remote control.

Once you've used the IR Remote Control Detective to find out what codes your remote control generates you could use it to control your own project using the receive routine in this program. You could also build a replacement remote control to control your equipment; for a transmit routine that caters for the same remote control protocols see IR Remote Wand.

Alternatively there is a comprehensive set of routines from Ken Shirriff and Rafi Khan on their excellent Arduino IRremote site [1].

IRDetectiveRemotes.jpg

Some of the remote controls used to test the IR Remote Control Detective.

Protocols

The IR Remote Control Detective supports the following protocols:

NEC and Samsung protocols

The NEC protocol was originally developed by the consumer electronics firm NEC. It is used by many Japanese and Chinese manufacturers, and by the remote controls available from Adafruit [2] and SparkFun [3]. A variant of it is also used by Samsung. For a full description of this protocol see San Bergmans's excellent SB-Projects site [4].

The data is encoded using pulse distance encoding. Each bit starts with 562.5µs of carrier. A zero has a total width of 1.125ms before the start of the next bit, and a one has a total width of 2.25ms before the next bit:

IRCodes4.png

The carrier pulse consists of 21 cycles at 38kHz. The pulses usually have a mark/space ratio of 1:4, to reduce the current consumption:

IRCodes7.png

Each code sequence starts with a long pulse, known as the AGC pulse, followed by a gap. In the NEC protocol the pulse is 9ms long and the gap 4.5ms long:

IRCodes5.png

In the Samsung version the pulse and gap are both 5ms long.

The data then consists of 32 bits, a 16-bit address followed by a 16-bit command, shown in the order in which they are transmitted (left to right) :

IRCodes6.png

Note that there needs to be one extra pulse at the end to terminate the last bit.

The Address identifies the piece of equipment being controlled, and the Command indicates which key was pressed.

If a key is held down the code is not repeated; instead a repeat code is transmitted every 110ms consisting of a 9ms AGC pulse followed by a 2.25ms space and a 560µs burst.

In the original version of the NEC protocol the high-order 8 bits of the address was just an inverted version of the low-order 8 bits, and likewise for the command (as in the above example). The remote controls I've tested from Adafruit and SparkFun follow this convention, so for these you could ignore the high-order bytes of the command.

Sony SIRC protocol

The Sony SIRC protocol uses a 12, 15, or 20 bit code consisting of a 7-bit command followed by 5, 8, or 13 address bits. For a full description of this protocol see San Bergmans's excellent SB-Projects site [5].

The data is encoded using pulse width encoding. A zero consists of a 600µs pulse of tone, and a one consists of a 1.2ms pulse of tone. In each case they are followed by a 600µs gap:

IRCodes8.png

The zero pulse consists of 24 cycles at 40kHz:

IRCodes11.png

A one pulse is twice as long as this. The pulses usually have a mark/space ratio of 1:4, to reduce the current consumption.

Each code sequence starts with a 2.4ms start pulse, followed by a 600µs gap, and then the data:

IRCodes9.png

Here's an example of the 20-bit version, shown in the order in which the bits are transmitted (left to right):

IRCodes10.png

The above example shows the command 0x2D and the address 0x1E3A.

If a key is held down the code is repeated at 45ms intervals (start to start), irrespective of the number of bits in the code. The Sony remotes I've tested send at least two repeats of the code, even for a short keypress.

RC-5 protocol

The RC-5 protocol was originally developed by the consumer electronics firm Philips, and is widely used by other manufacturers, especially in the UK and Europe [6].

The data is encoded using bi-phase encoding, also known as Manchester coding. All bits have a length of 1.778ms. A zero is represented as half a bit of carrier followed by half a bit of silence, and a one is half a bit of silence followed by half a bit of carrier:

IRCodes1.png

The half bit of carrier consists of 32 cycles at 36kHz. The pulses usually have a mark/space ratio of 1:4, to reduce the current consumption:

IRCodes3.png

Each code sequence consists of 14 bits: two ones, followed by a toggle bit, followed by a 5-bit address and a 6-bit command; for example:

IRCodes2.png

The Address identifies the piece of equipment being controlled, and the Command indicates which key was pressed. If the same key is pressed repeatedly, the toggle bit changes for each repeat of the code. If a key is held down, that key's code is sent repeatedly, once every 114ms, without changing the toggle bit.

A backwards-compatible extension to the protocol, called Extended RC-5, doubles the number of commands available by using only one start bit. The second start bit is used as a 7th command bit; its value is inverted, ensuring that the first 64 commands remain compatible with the original RC-5 protocol. The IR Remote Control Detective supports Extended RC-5.

The circuit

Here's the circuit of the IR Remote Control Detective:

IRDetective.gif

Circuit of the IR Remote Control Detective based on an ATtiny85.

To decode the infrared codes I used the Vishay TSOP38238 38kHz infrared receiver available from Sparkfun [7], or from HobbyTronics in the UK [8]. It has a wide enough pass band to work with the 36kHz carrier frequency used by the RC-5 protocol or the 40kHz used by the Sony protocol. Note that the infrared receiver gives an inverted output; when the carrier is present the output is low, and when there's no carrier it's high.

For the display I chose the I2C 128x32 OLED display available from Adafruit [9] or Pimoroni in the UK [10]. The 33kΩ resistor and 0.1µF capacitor ensure that the display is reset correctly when power is first applied, although I found I didn't need them in my prototype.

The program

I2C OLED display

The protocol is identified on the display as N for NEC, M for Samsung, R for RC-5, or S12, S15, or S20 for the three Sony variants. The display also shows the address and command as four-digit hexadecimal numbers, displayed in double-sized characters to make the display more readable; the technique is described in my earlier article Big Text for Little Display. The characters are defined by the array CharMap[][].

Timing the pulses

The infrared codes are interpreted using a pin-change interrupt set up on the input from the infrared receiver:

  // Set up pin change interrupt on PB3
  PCMSK = 1<<PCINT3;          // Interrupt on PB3
  GIMSK = 1<<PCIE;            // Enable pin change interrupts

The pulse timing is performed using Timer/Counter1 as a simple 8-bit counter set up with a 1/64 prescaler:

  // Set up Timer/Counter1 (assumes 1MHz clock)
  TCCR1 = 7<<CS10;            // No compare matches ; /64

To get the count we read the TCNT1 register, which gives units of 64µs. The maximum duration that we can measure is 256x64µs or 16ms, which is sufficient as the longest pulse in any of the protocols is 9ms.

Interrupt service routine

All the protocol decoding is performed by the pin-change interrupt service routine, which is called on each transition of the input from the infrared receiver:

ISR(PCINT0_vect) {
  static int Mid;  // Edge: 0 = falling, 1 = rising
  int Time = TCNT1;
  if (TIFR & 1<<TOV1) { IRtype = 0; Edge = 1; }        // Overflow
  else if (Edge != (PINB>>PINB3 & 1));                 // Ignore if wrong edge
  else if (IRtype == 0) {
  
    // End of intro pulse
    if (abs(Time - RC5Half) < 5) {
      IRtype = 'R'; RecdData = 0x2000; Bit = 12; Edge = 0; Mid = 0;
    } else if (abs(Time - RC5Full) < 5) {
      IRtype = 'R'; RecdData = 0x2000; Bit = 11; Edge = 0; Mid = 1;
    } else if ((abs(Time - SonyIntro) < 5) && (Edge == 1)) {
      IRtype = 'S'; RecdData = 0; Bit = 0;
      TIMSK = TIMSK | 1<<TOIE1;                        // Enable overflow interrupt
    } else if (abs(Time - SamsungIntro) < 18) {
      IRtype = 'M'; RecdData = 0; Bit = -1; Edge = 0;  // Ignore first falling edge
    } else if (abs(Time - NECIntro) < 18) { 
      IRtype = 'N'; RecdData = 0; Bit = -1; Edge = 0;  // Ignore first falling edge
    }
  
  // Data bit
  } else if (IRtype == 'R') {
    Edge = !Edge;
    if ((Time < RC5Mean) && Mid) {
      Mid = 0;
    } else {
      Mid = 1;
      RecdData = RecdData | ((unsigned long) Edge<<Bit);
      if (Bit == 0) ReceivedCode('R', RecdData>>6 & 0x1F,
        (~(RecdData>>6) & 0x40) | (RecdData & 0x3F));
      Bit--;
    }
  } else if (IRtype == 'S') {
    if (Time > SonyMean) RecdData = RecdData | ((unsigned long) 1<<Bit);
    Bit++;
  } else if ((IRtype == 'N') || (IRtype == 'M')) {
    if (Time > NECMean && Bit >= 0) RecdData = RecdData | ((unsigned long) 1<<Bit);
    Bit++;
    if (Bit == 32) ReceivedCode(IRtype, RecdData & 0xFFFF, RecdData>>16);
  }
  
  TCNT1 = 0;                  // Clear counter
  TIFR = TIFR | 1<<TOV1;      // Clear overflow
}

If more than 16ms has elapsed since the last transition the overflow flag will be set, and this indicates the start of a new code sequence.

Identifying the protocol

The next transition is the end of the start pulse. The length of the start pulse is used to identify the protocol: this is either 9ms for the NEC protocol, 5ms for the Samsung protocol, or 2.4ms for the Sony protocol. The RC-5 protocol is trickier because the first pulse could either be 0.889ms if the first two bits are '11', or 1.778ms if the first two bits are '10'. The variable IRtype is set to 'N', 'M', S', or 'R' accordingly.

After identifying the protocol the program reads the appropriate number of bits for that protocol into the variable RecdData. The variable Bit counts which bit is being received in the code. The variable Edge is used to specify which edge we are waiting for; the other edge is ignored, and just resets the timer. When all the bits have been read the routine calls ReceivedCode() with the protocol, and the address and command bits extracted from the appropriate fields in RecdData.

The protocols require slightly different approaches to decoding them:

NEC and Samsung protocols

The NEC and Samsung protocols are the simplest. Edge is set to 0 to respond only to the falling edge of each pulse, corresponding to the end of the gap between pulses of carrier. The length of this gap determines whether the bit is a zero or a one.

Sony protocol

The Sony protocol uses the length of each pulse of carrier to distinguish between zeros and ones, so Edge is set to 1 to respond only to the rising edge of each pulse. Because the number of bits in the Sony code could be 12, 15, or 20, the routine sets up an overflow interrupt, and uses this to detect when a delay of more than 16ms has occurred after the last transition.

RC-5 protocol

The RC-5 protocol is the trickiest to decode as the number of transitions in a code can vary depending on the sequence of zeros and ones. Edge is toggled at each edge to respond to both falling and rising edges, and a variable Mid is used to track whether we have reached the middle of a bit.

Compiling the program

I compiled the program using Spence Konde's ATTiny Core [11]. Choose the ATtiny25/45/85 option under the ATtinyCore heading on the Board menu. Then choose Timer 1 Clock: CPUB.O.D. DisabledATtiny851 MHz (internal) from the subsequent menus. Choose Burn Bootloader to set the fuses appropriately. Then upload the program using ISP (in-system programming); I used SparkFun's Tiny AVR Programmer Board; see ATtiny-Based Beginner's Kit.

Here's the whole IR Remote Control Detective program: IR Remote Control Detective Program.

Updates

8th May 2018: I've updated the program to fix bugs which were giving incorrect codes for the NEC, Samsung, and Sony protocols. Thanks to Michael Bebjak for reporting the problem with NEC codes, and apologies to anyone who encountered problems with the original version. Also, corrected the declaration of the character map from uint32_t to uint8_t; thanks to Larry Bank for pointing this out.

28th January 2024: I've exchanged the order of the calls to InitDisplay() and ClearDisplay() in the program; thanks to Per Thomas Jahr for pointing this out.


  1. ^ Arduino IRremote on GitHub.
  2. ^ Mini Remote Control on Adafruit.
  3. ^ Infrared Remote Control on SparkFun.
  4. ^ NEC Protocol on SB-Projects.
  5. ^ Sony SIRC Protocol on SB-Projects.
  6. ^ Data Formats for IR Remote Control by Vishay Semiconductors.
  7. ^ IR Receiver Diode - TSOP38238 on SparkFun.
  8. ^ Infra Red Receiver Module on HobbyTronics.
  9. ^ Monochrome 128x32 I2C OLED graphic display on Adafruit.
  10. ^ Adafruit Monochrome 128x32 OLED graphic display on Pimoroni.
  11. ^ ATTinyCore on GitHub.

blog comments powered by Disqus