## Capture and analyze infrared (IR) signal from remote control

### Capture and analyze infrared (IR) signal from remote control

Capture and analyze infrared (IR) signal from remote controller

IR remote controllers have used and been using popularly in modern life to control devices. However, they have the range limitation. This make them inconvenience in some cases, for example, on my way to work/home, I want to turn on my air-conditioner some minutes before I arrive. It’s impossible for normal IR remote controller. To overcome this, I am going to make the internet IR remote controller which enables to control devices via internet without worrying about distance. Before doing it, we need to know how IR remote controller works. This article shows how to analyze IR signal from remote controller. Internet IR controller will be presented in the next article.

some IR remote controllers.png (208.63 KiB) Viewed 2114 times

If you haven’t had any knowledge about IR communication yet, please refer to this article: Infrared (IR) communication using PHPoC http://www.phpoc.com/forum/viewtopic.php?f=42&t=182

Analysis process goes through four steps:

- Step 1: capture pulses chain from IR receiver and print it out.
- Step 2: analyze the pulses chain to know which protocol is used
- Step 3: basing on protocol used, map the pulses chain into bit chain
- Step 4: basing on the mapped bit chain, determine the address of device and set of command.

Before going into detail, let’s look at NEC protocol, which is widely used for remote controller. This article takes this protocol for example.

NEC protocol is defined as follows:

- Logical ‘0’: a 562.5µs pulse burst followed by a 562.5µs space, with a total transmit time of 1.125ms
- Logical ‘1’: a 562.5µs pulse burst followed by a 1.6875ms space, with a total transmit time of 2.25ms

One data frame:

NEC Frame Structure.png (15.37 KiB) Viewed 2114 times

- a 9ms leading pulse burst (16 times the pulse burst length used for a logical data bit)
- a 4.5ms space
- data:
+ the 8-bit address for the receiving device
+ the 8-bit logical inverse of the address
+ the 8-bit command
+ the 8-bit logical inverse of the command
- a final 562.5µs pulse burst to signify the end of message transmission.

The above is the standard protocol, some company may modify it.

Now, let’s do step by step. I take the remote controller of SHINIL air-conditioner for example.

Shinil Air Conditioner.png (222.38 KiB) Viewed 2114 times

Step 1. Capture pulses chain from IR receiver and print it out.

Things we need to have:
- PHPoC blue.
- Grove expansion board for PHPoC (Optional and if receiver’s type is grove)
items.png (362.5 KiB) Viewed 2114 times

Wiring:

Wiring 1.png (92.64 KiB) Viewed 2114 times

or

Wiring 2.png (356.13 KiB) Viewed 2111 times

Hardware timer 0 is used to capture pulses.

Source code for capturing and print out. <task0.php>
Code: Select all
`<?php if(_SERVER("REQUEST_METHOD"))    exit; // avoid php execution via http request include_once "/lib/sd_340.php";include_once "/lib/vd_nec_infrared.php";\$unit = 562.5 / 5;//µs\$repc = 80; \$lower_bound = 1000; \$upper_bound = 14000;    while(1){        infrared_capture_start(\$unit, \$repc);    sleep (1);        infrared_recv_stop();        if(infrared_available(\$lower_bound, \$upper_bound))    {        echo "\r\n";        for(\$i = 0; \$i <= \$repc; \$i++)        {            //get width of pulses            \$us = infrared_get_count(\$i);                echo "\$us, ";        }    }        } ?>`

Note that: source code of vd_nec_infared.php is located at the end of this article.

Press any buttons on controller, try some time to exclude some cases affected by noise.

Results:

Result Pulse Chain.png (271.64 KiB) Viewed 2111 times

Step 2. Analyze the pulses chain to know which protocol is used

As we can see,
- The first pulse width is 9000 microseconds
- The second pulse width is 4387 microseconds
- The others are around 562 and 1687 microseconds

Note that the value may have a little error, but no problem.

The above values are similar to NEC protocol values. So this controller uses NEC protocol.

Step 3. Basing on protocol used, map the pulses chain into bit chain

Now just map the width of pulses in to bit chain.

Source for capturing signal and mapping into bit chain <task0.php>
Code: Select all
`<?php if(_SERVER("REQUEST_METHOD"))    exit; // avoid php execution via http request include_once "/lib/sd_340.php";include_once "/lib/vd_nec_infrared.php";\$unit = 562.5 / 5;//µs\$repc = 33; // maximum: one for leading, 8+8 for address, 8+8 for data\$lower_bound = 1000; \$upper_bound = 14000;    while(1){        infrared_recv_start(\$unit, \$repc);    sleep (1);        infrared_recv_stop();        if(infrared_available(\$lower_bound, \$upper_bound))    {        echo "\r\n";        for(\$i = 1; \$i <= \$repc; \$i++)        {            //get width of pulses            \$us = infrared_get_count(\$i);                    if(\$us < 2500) // avoid cases of noise            {                if(\$us > 1500)                {                    echo "1, "; // a 562.5µs pulse burst followed by a 1.6875ms space, with a total transmit time of 2.25ms                }                else                {                    echo "0, "; // a 562.5µs pulse burst followed by a 562.5µs space, with a total transmit time of 1.125ms                }            }                    }            }        } ?>`

Press some buttons on controller

Result Bit Chain.png (140.52 KiB) Viewed 2111 times

Note that: some company may not use full 32-bit of data (16 bits of address and 16 bits of command) or not use the inverse address or command.

Step 4. Basing on the mapped bit chain, determine the address of device and set of command.

Analyzing Bit Chain.PNG (47.35 KiB) Viewed 2111 times

As we can see, the 16 first bits are the same for every buttons pressed. This is device address. The first 8-bit address is not inverse of second 8-bit address. This is not like theory. In this case, 16-bit address is 0x01FF.

The last bits are the command bits and depend on each button. The first 8-bit command is inverse of second 8-bit command and this is like theory.

We already knew the device’s address, let’s get the set of command.

Source code for getting all data (this function is contained in vd_nec_infrared.php)

Code: Select all
`<?phpfunction nec_infrared_recv(\$bit_length){        \$data = 0;    \$mask = 1 << (\$bit_length -1);    for(\$i = 1; \$i <= \$bit_length; \$i++)    {        \$us = infrared_get_count(\$i);        if(\$us < 2500)        if(\$us > 1500)        {            \$data |= (\$mask>> (\$i-1));        }    }    return \$data;}?>`

Source code for getting command <task0.php>

Code: Select all
`<?php if(_SERVER("REQUEST_METHOD"))    exit; // avoid php execution via http request include_once "/lib/sd_340.php";include_once "/lib/vd_nec_infrared.php";\$unit = 562.5 / 5;//µs\$repc = 33; // maximum: one for leading, 8+8 for address, 8+8 for data\$lower_bound = 1000; \$upper_bound = 14000;    while(1){        infrared_recv_start(\$unit, \$repc);    sleep (1);        infrared_recv_stop();        if(infrared_available(\$lower_bound, \$upper_bound))    {        \$data = nec_infrared_recv(32); // get 32 bit.        \$command = (\$data >> 8) & 0xFF;                echo "\r\n command: \$command";    }        } ?>`

Press all button on controller one by one.

Result Command Set.png (123.88 KiB) Viewed 2111 times

Now we have the set of command now. In the next article, I am going to show how to control the air-conditioner through internet.

To run all source code, you need to create the config file (phpoc.ini) and also upload library (vd_nec_infared.php).

Source <phpoc.ini>

Code: Select all
`ht0_count_buf_size = 256ht1_count_buf_size = 256ht2_count_buf_size = 256ht3_count_buf_size = 256`

Source code <lib/vd_nec_infrared.php>

Code: Select all
`<?php if(_SERVER("REQUEST_METHOD"))    exit; // avoid php execution via http request include_once "/lib/sd_340.php";define("BASIC_CLOCK",   42000000); // basic clock of PHPoC 42MHz\$recv_ht_id = 0; // timer id which connect to an infrared receiver to capture data\$emit_control_ht_id = 1; // timer id which control an infrared emitter\$emit_carrier_ht_id = 2; // timer id which create the carier for infrared modulationfunction infrared_setup(\$rec_ht_id, \$control_ht_id, \$carrier_ht_id){    global \$recv_ht_id;    global \$emit_control_ht_id;    global \$emit_carrier_ht_id;        \$recv_ht_id = \$rec_ht_id;    \$emit_control_ht_id = \$control_ht_id;    \$emit_carrier_ht_id = \$carrier_ht_id;}/*This function make timer to start capturing signal from an infrared receiver. It is used to analyze the pulse chain, so it it set to capture toggle mode.Paramerer:     -\$unit (microsecond);    -\$repc: the number of pulse need to be captured*/function infrared_capture_start(\$unit, \$repc){    global \$recv_ht_id;        \$repc++; // plus one dummy pulse    \$unit = \$unit * BASIC_CLOCK / 1000000;    // setup capture timer    ht_ioctl(\$recv_ht_id, "reset");    ht_ioctl(\$recv_ht_id, "set div \$unit");    ht_ioctl(\$recv_ht_id, "set mode capture toggle");    ht_ioctl(\$recv_ht_id, "set trigger from pin fall");    ht_ioctl(\$recv_ht_id, "set repc \$repc");    ht_ioctl(\$recv_ht_id, "start"); // start trigger pulse    }/*This function make timer to start capturing signal from an infrared receiver. It is used to get data, so it it set to capture fall mode.Paramerer:     -\$unit (microsecond);    -\$repc: the number of pulse need to be captured*/function infrared_recv_start(\$unit, \$repc){    global \$recv_ht_id;        \$repc++; // plus one dummy pulse    \$unit = \$unit * BASIC_CLOCK / 1000000;    // setup capture timer    ht_ioctl(\$recv_ht_id, "reset");    ht_ioctl(\$recv_ht_id, "set div \$unit");    ht_ioctl(\$recv_ht_id, "set mode capture fall");    ht_ioctl(\$recv_ht_id, "set trigger from pin fall");    ht_ioctl(\$recv_ht_id, "set repc \$repc");    ht_ioctl(\$recv_ht_id, "start"); // start trigger pulse    }function infrared_recv_stop(){    global \$recv_ht_id;        ht_ioctl(\$recv_ht_id, "stop");}function infrared_carrier_start(\$freq){    global \$emit_carrier_ht_id;        \$div = BASIC_CLOCK / (\$freq * 2);     ht_ioctl(\$emit_carrier_ht_id, "reset");    ht_ioctl(\$emit_carrier_ht_id, "set div \$div"); // div 13.14us    ht_ioctl(\$emit_carrier_ht_id, "set mode output pwm");    ht_ioctl(\$emit_carrier_ht_id, "set output od");    ht_ioctl(\$emit_carrier_ht_id, "set count 1 1");    ht_ioctl(\$emit_carrier_ht_id, "start");}function infrared_carrier_stop(){    global \$emit_carrier_ht_id;        ht_ioctl(\$emit_carrier_ht_id, "stop");    ht_ioctl(\$emit_carrier_ht_id, "set output high");}/*see nec_infrared_send(\$data, \$bit_length) function to know how to use this function*/function infrared_emit(\$count_buf, \$cnt_buf_len, \$unit){    global \$emit_control_ht_id;        \$unit = \$unit * BASIC_CLOCK / 1000000;        infrared_carrier_start(38000); //enable 38KHz PWM signal - carrier frequency        //ht_ioctl(\$emit_control_ht_id, "reset");    ht_ioctl(\$emit_control_ht_id, "set div \$unit");    ht_ioctl(\$emit_control_ht_id, "set mode output toggle"); // set mode: toggle    ht_ioctl(\$emit_control_ht_id, "set output od");        \$pid_ht = pid_open("/mmap/ht\$emit_control_ht_id");    pid_write(\$pid_ht, \$count_buf);    pid_ioctl(\$pid_ht, "set repc \$cnt_buf_len");    pid_close(\$pid_ht);        ht_ioctl(\$emit_control_ht_id, "start"); // start HT        while(ht_ioctl(\$emit_control_ht_id, "get state"));        ht_ioctl(\$emit_control_ht_id, "stop");        infrared_carrier_stop(); // stop to save energy.}/* Paramerer:     -\$lower_bound: minimum value of the first captured pulse in microsecond    -\$upper_bound: minimum value of the first captured pulse in microsecondReturn:    -true: value of the first captured pulse varies from \$lower_bound to \$upper_bound    -false: otherwise.*/function infrared_available(\$lower_bound, \$upper_bound){    global \$recv_ht_id;        \$unit = ht_ioctl(\$recv_ht_id, "get div"); // in number clock stick    \$unit = \$unit * 1000000 / BASIC_CLOCK; // in microsecond    \$lower_bound /= \$unit;    \$upper_bound /= \$unit;        \$count = ht_ioctl(\$recv_ht_id, "get count 1");        if( (\$count >= \$lower_bound) && (\$count <= \$upper_bound))        return true;        return false;}/* Paramerer: \$count_id start from 0.Return: width of captured pulse in microsecond*/function infrared_get_count(\$count_id){    global \$recv_ht_id;        \$count_id++; // due to the dummy value (first value)    \$unit = ht_ioctl(\$recv_ht_id, "get div");    \$count = ht_ioctl(\$recv_ht_id, "get count \$count_id");    \$reval = \$unit * \$count * 1000000 / BASIC_CLOCK;        return \$reval;}function nec_infrared_send(\$data, \$bit_length){        \$unit = 562.5;// 562.5µs        \$count_buf = int2bin(1, 2);    // first value is dummy.    \$count_buf .= int2bin(16, 2);   // 9ms leading pulse burst (16 * 562.5us)    \$count_buf .= int2bin(8, 2);    //  4.5ms space (8 * 562.5us)    \$cnt_buf_len = 3;                \$mask = 1 << (\$bit_length-1);        while(\$mask)    {        \$count_buf .= int2bin(1, 2);    // 562.5µs pulse burst        \$cnt_buf_len++;                if(\$data & \$mask)        {            \$count_buf .= int2bin(3, 2);    // logical 1: 1.6875ms space        }        else         {             \$count_buf .= int2bin(1, 2);    // Logical 0 562.5µs space        }        \$cnt_buf_len++;                \$mask = \$mask >> 1;    }        \$count_buf .= int2bin(1, 2);    // 562.5µs stop code    \$cnt_buf_len++;        infrared_emit(\$count_buf, \$cnt_buf_len, \$unit);    }function nec_infrared_recv(\$bit_length){        \$data = 0;    \$mask = 1 << (\$bit_length -1);    for(\$i = 1; \$i <= \$bit_length; \$i++)    {        \$us = infrared_get_count(\$i);        if(\$us < 2500)        if(\$us > 1500)        {            \$data |= (\$mask>> (\$i-1));        }    }    return \$data;}?>`

Thank you for reading and have fun!
Khanh

Posts: 72
Joined: Fri Mar 11, 2016 10:57 am