Monday, May 27, 2013

IR remote decoding

The goal

Decode IR remote transmission, so we can build IR remote emulator to control our stereo from network.

Usability

High. Can be part of intelligent house project.

Hardware


Description

In this project, you can find answer, how to build IR remote emulator whitout knowing your device's IR communication standard.

Introduction

IR remote controllers communicate with dedicated devices by sending infrared data sequences. Different devices (stereo, TV, CD, DVD) use different IR messages, some may use RC5 which is (was) more popular, other use their own standard developed by manufacturer. Every type of device has it's own code, so is possible to control TV and CD by one standard, because two IR remotes are sending two different device codes and one command code.

Noise

IR sequences are send by turning On and Off IR diode. That kind of change drives IR receiver and on its output we can see voltage changes corresponding to IR diode off/on state. But what with interference of other sources of light, e.g. from your desk lamp or direct sunlight ?.

If you plug a photoresistor into your microphone port (from your computer) and point it at your desk lamp (12v transformer and 12V halogen bulb). Then you can hear buzzing from your speakers, this is your AC frequency. Human eye will not see this, but you could clearly hear that light is generating 'light noise'.
When you put in direct sunlight, there we'll be no noise in speakers. Sunlight will 'block' in one state (conductive).

So, from above, we have two serious noise sources which would make mess in our device's IR receiver. To avoid above problems IR diode is connected to 36-38kHz generator. So when we're sending sequence e.g. On-On-On-Off-Off-On-Off-On, IR diode is sending  36kHz-36kHz-36kHz-Off-Off-36kHz-Off-36kHz in some constant amount of time.

IR receivers have filters on that frequencies and for light wave length so interferences from other sources was significantly reduced.

Decoding any IR signal

To decode IR you must have IR receiver that matches yours IR remote carrier frequency e.g. 36-38Khz.
IR receiver on my Atmega32 board
My receiver gives 5V on output when there is no signal, and 0V when it sees 36-38kHz wave, so we have reverse logic here, it's important when we'll be sending IR codes, because in our remote emulator logic '1' (on Timer enable flag) means 'send 36-38khz'.

Receiver is connected to interrupt pin PD3 which triggers interrupt vector SIG_INTERRUPT1 on every falling edge of PD3 pin.
SIGNAL(SIG_INTERRUPT1) {   
   // here is will be code for IR decoding
   return;
}

No we have to fetch one frame from IR remote and discretize it, this will help us to prepare proper message and 'play it back'. For this we connect our IR receiver output to oscilloscope, set trigger to one shot and press any button on our IR remote.
One IR frame, one command from IR remote
As you can see, message last some time. This time is mostly constant, but in some other protocols messages length (time) can be different.

Now, we've got to find the shortest state '1' (in reverse logic = '0'). This state is very important, it will give us base to calculate proper sampling rate to 'record' and analyse data sent in IR sequence. For finding shortest state, we have to change horizontal resolution in our oscilloscope.

Shortest state is 500us
Shortest state increased
Shortest state is about 500us, so it's safe to assume, that 100us sampling rate will be enough. In below code, we are checking pin for every 100us, and then write its state to array. Next step is to print array into RS232 on our PC. After this we can analyse IR message.
SIGNAL(SIG_INTERRUPT1) {
   char data[512];   // this should be enough
   
   unsigned short i = 0;
  
   for(i=0; i< 512; i++) {
      if( IS(PIND, PD3) )   // check pin PD3 - here is our receiver connected
         data[i] = '0';     // set in reverse logic, because receiver gives
      else                  // 5V at idle and 0V when sees 36-38kHz wave
         data[i] = '1';
         
      delay_us(100);        // sampling delay
   }
      
   data[511] = '\0';        // make sure to end string properly


   // this is fragment from my C++ code for atmega32
   // operator is overloaded and works pretty awesome
   *rs << data << "\r\n";
   
   // make sure, that only one command will be fetched
   delay_ms(500);
   delay_ms(500);
   
   return;
}
Above code will return something like this on our RS232 port. I use putty to connect to my /dev/ttyS0, so this is output from putty.
11111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000111100001111000011111000111110000000000001111000000000000111100000000000011110000000000001111100001111000000000000111100000000000011110000000000001111000011111000111110000111100001111000000000000111100001111000011110000000000001111100000000000011110000000000001111000011110000111100000000000011111000000000000111100000000000011110000111100001111000011110000000000001111100000000000011110000111100000000000000000000
What happened here ?, for every '1' should be five '1', but only if we assume, that delay works ok and check pin state operation is timeless. But it's not, from above we assume that original shortest state was 400us instead of 500us, of course if you have always five '1' then ignore this tip.

Outputs from IR commands:
init + mute:

11111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000111100001111000011111000111110000000000001111000000000000111100000000000011110000000000001111100011111000000000000111100000000000011110000000000001111000011111000111110000111100001111000000000000111100001111000011110000000000001111100000000000011110000000000001111000011110000111100000000000011111000000000000111100000000000011110000111100001111000011110000000000001111100000000000011110000111100000000000000000000

init +VolDown:
11111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000111100001111000011111000111110000000000001111000000000000111100000000000011110000000000001111100001111000000000000111100000000000011110000000000001111000011111000111110000111100001111000000000000111100000000000011110000000000001111100000000000011110000000000001111000000000000111100001111000011111000111110000111100001111000011110000111100001111000000000000111110000000000011111000000000000111100000000000000000000

init + On/Off:
11111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000111100001111000011111000111110000000000001111000000000000111100000000000011110000000000001111100011111000000000000111100000000000011110000000000001111000011111000111110000111100001111000000000000111100000000000011110000000000001111100000000000011110000000000001111000011110000111100001111000011111000111110000111100001111000011110000000000001111000000000000111110000000000011111000000000000111100000000000000000000

As you can see this message is duplicated like 4 times, this is true, because we set sampling rate to 1/4 shortest logic state. Sampling is helpful to reduce errors, because in this sequence every logic state (1 or 0 sequence) should divide by 4 without the rest.

Perfect timing for our loop will give us '1111' for every original '1' (before sampling) , so errors are easy to detect: '111' and '11111' should be '1'.

Below you can see whole IR sequence after removing errors and bringing it back to original state, that means multiplicated by 4.
mute:

111111111111111100000000101010100010001000100010100010001000101010101000101010001000100010101000100010001010101000100010100000
Now sample rate for this message is 400us.

In next step we have to separate constant fragment from command, you must have many sequences to filter this out. It's better when init sequence will be ending on '0', even if you must move '1' to command sequences.
init:

111111111111111100000000101010100010001000100010100010001000101010101000
In this message, we store only '1' and '0', so we can treat it like bit and put 8 bits in one byte, it's more economic like this.
const unsigned char init[]       = { 0b11111111,  0b11111111,  0b00000000,  0b10101010,  0b00100010,  0b00100010,  0b10001000, 0b10001010, 0b10101000 };
We're ending with two tables, one is the constant sequence which will be send every time, second table contains only dedicated command, like 'mute' or 'VolUp'.
const unsigned char init[]       = { 0b11111111,  0b11111111,  0b00000000,  0b10101010,  0b00100010,  0b00100010,  0b10001000, 0b10001010, 0b10101000 };
const unsigned char mute[]       = { 0b10101000,  0b10001000,  0b10101000,  0b10001000,  0b10101010,  0b00100010,  0b10000000 };

About the delay

Sometimes delay is not set properly, or operations can last longer/shorter than you've assumed, so after you write some code to send IR commands, check them on oscilloscope if they match your device's timing standard.

Send sequences

To send sequence to IR device, you have to remember about your IR carrier frequency (36-38kHz). This could be generated by TimerCounter from your microcontroller.

I'm sending IR sequences, by enabling/disabling timer enable flag, to send 36-38kHz carrier.

Working device and full source code you can find in IR remote emulator with attiny45

No comments:

Post a Comment