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:
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].
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:
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:
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:
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) :
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:
The zero pulse consists of 24 cycles at 40kHz:
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:
Here's an example of the 20-bit version, shown in the order in which the bits are transmitted (left to right):
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:
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:
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:
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:
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: CPU, B.O.D. Disabled, ATtiny85, 1 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.
- ^ Arduino IRremote on GitHub.
- ^ Mini Remote Control on Adafruit.
- ^ Infrared Remote Control on SparkFun.
- ^ NEC Protocol on SB-Projects.
- ^ Sony SIRC Protocol on SB-Projects.
- ^ Data Formats for IR Remote Control by Vishay Semiconductors.
- ^ IR Receiver Diode - TSOP38238 on SparkFun.
- ^ Infra Red Receiver Module on HobbyTronics.
- ^ Monochrome 128x32 I2C OLED graphic display on Adafruit.
- ^ Adafruit Monochrome 128x32 OLED graphic display on Pimoroni.
- ^ ATTinyCore on GitHub.
blog comments powered by Disqus