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
- 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.
Things Used In This Project
- PHPoC blue.
- One Grove IR receiver
- Grove expansion board for PHPoC (Optional and if receiver’s type is grove)
- Jumper wires
Wiring Diagram
or
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:
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!
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.
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 = 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;
}
?>
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!
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(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!