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.

Click image for larger version  Name:	capture_analyze_some IR remote controllers.png Views:	1 Size:	191.6 KB ID:	472

If you haven’t had any knowledge about IR communication yet, please refer to this article: Infrared (IR) communication using PHPoC

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

Logic Level
  • 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
Data FrameClick image for larger version  Name:	capture_analyze_NEC Frame Structure.png Views:	1 Size:	5.2 KB ID:	473
  • 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.

Click image for larger version  Name:	capture_analyze_Shinil Air Conditioner.png Views:	1 Size:	196.3 KB ID:	474



Things Used In This Project
Click image for larger version  Name:	capture_analyze_things.png Views:	1 Size:	329.9 KB ID:	475




Wiring Diagram

Click image for larger version  Name:	capture_analyze_Wiring 1.png Views:	1 Size:	80.9 KB ID:	476

or

Click image for larger version  Name:	capture_analyze_Wiring 2.png Views:	1 Size:	311.5 KB ID:	477



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

Hardware timer 0 is used to capture pulses.

Source code for capturing and print out pulse duration. <task0.php>
PHP Code:
<?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:

Click image for larger version  Name:	capture_analyze_Result Pulse Chain.png Views:	1 Size:	265.7 KB ID:	478



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>

PHP Code:
<?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 IR remote controller!

Click image for larger version  Name:	capture_analyze_Result Bit Chain.png Views:	1 Size:	137.1 KB ID:	479

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.

Click image for larger version  Name:	capture_analyze_Analyzing Bit Chain.PNG Views:	1 Size:	47.3 KB ID:	480

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)

PHP Code:
<?php
function nec_infrared_recv($bit_length)
{    
    
$data 0;
    
$mask << ($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>

PHP Code:
<?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 IR remote controller one by one!

Click image for larger version  Name:	capture_analyze_Result Command Set.png Views:	1 Size:	120.1 KB ID:	481

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.


Note that: 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:
ht0_count_buf_size = 256
ht1_count_buf_size = 256
ht2_count_buf_size = 256
ht3_count_buf_size = 256




Source code <lib/vd_nec_infrared.php>

PHP Code:
<?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 modulation

function 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 microsecond
Return:
    -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(12);    // first value is dummy.
    
$count_buf .= int2bin(162);   // 9ms leading pulse burst (16 * 562.5us)
    
$count_buf .= int2bin(82);    //  4.5ms space (8 * 562.5us)
    
$cnt_buf_len 3;


    
$mask << ($bit_length-1);

    while(
$mask)
    {
        
$count_buf .= int2bin(12);    // 562.5µs pulse burst
        
$cnt_buf_len++;

        if(
$data $mask)
        {
            
$count_buf .= int2bin(32);    // logical 1: 1.6875ms space
        
}
        else
        {
             
$count_buf .= int2bin(12);    // Logical 0 562.5µs space
        
}
        
$cnt_buf_len++;

        
$mask $mask >> 1;
    }

    
$count_buf .= int2bin(12);    // 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 << ($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!