Exploring STC 8051 Microcontrollers – Coding

NEC IR Remote Decoding with Timer 0 and External Interrupt

In many projects, we need to control devices remotely, for example a remote-controlled appliance and so it becomes necessary to incorporate some form of remote controller. Infrared (IR) remote controllers are cheap and are widely used as remote controllers in many devices.

Data to be sent from a remote IR transmitter to a receiver is transmitted by modulating it with a carrier wave having frequency between 30kHz to 40kHz. Modulation technique ensures safety from data corruption over long-distance transmissions. An IR receiver at the receiving end picks up and demodulates the sent out modulated data and outputs a stream of pulses. These pulses can vary in terms of pulse widths/timing/position/phase and these variable properties carry the data. Pulse properties are defined by a set of rules called protocol. Decoding the pulses as per protocol help in retrieving information. In most micros, there is no dedicated hardware for decoding IR remote protocols. A micro that needs to decode IR remote data also does not know when it will be receiving an IR burst. Thus, a combination of external interrupt and timer is needed for decoding IR data.

In this segment, we will see how we can use an external interrupt and a timer to easily decode a NEC IR remote. This same method can be applied to any IR remote controller. At this stage, I recommend studying NEC IR protocol from here. SB-Project’s website is an excellent page for IR remote-related info.

Code

 #include "STC8xxx.h"
#include "BSP.h"
#include "LCD.c"
#include "lcd_print.c"
 
#define sync_high     5850    //approx 1.3 * 4500us
#define sync_low      3150    //approx 0.7 * 4500us
#define one_high      2200    //approx 1.3 * (2250 - 562.5)us
#define one_low       1180    //approx 0.7 * (2250 - 562.5)us  
#define zero_high     732     //approx 1.3 * (1125 - 562.5)us
#define zero_low      394     //approx 0.7 * (1125 - 562.5)us
 
unsigned char bits = 0x00; 
unsigned char received = 0x00; 
 
unsigned int frames[33];
 
void setup(void);
void erase_frames(void);
unsigned char decode(unsigned char start_pos, unsigned char end_pos);
void decode_NEC(unsigned char *addr, unsigned char *cmd);
 
void EXT_0_ISR(void)        
interrupt 0
{
  frames[bits] = TMR0_get_counter(); 
  bits++; 
  TMR0_start; 
 
  if(bits >= 33) 
  { 
    received = 1; 
    _disable_global_interrupt; 
    TMR0_stop; 
  } 
  
  TMR0_load_counter_16(0x0000); 
}
 
void main(void)
{
  unsigned char addr = 0x00;
  unsigned char cmd = 0x00;
    
  setup();
 
  LCD_goto(1, 0); 
  LCD_putstr("NEC IR Decoder"); 
  LCD_goto(0, 1); 
  LCD_putstr("Adr      Cmd");
 
  while(1)
  {
     if(received != FALSE)
     {
       decode_NEC(&addr, &cmd);
       
       print_C(3, 1, addr); 
       print_C(12, 1, cmd); 
       
       delay_ms(100);
       
       erase_frames();
       _enable_global_interrupt;
     }
  };
}
 
void setup(void)
{
  CLK_set_sys_clk(IRC_24M, 2, MCLK_SYSCLK_div_1, MCLK_SYSCLK_no_output);
  
  erase_frames();
  
  TMR0_setup(TMR0_16_bit_non_auto_reload, \
             TMR0_sysclk, \
             TMR0_clk_prescalar_12T, \
             TMR0_ext_gate, \
             TMR0_no_clk_out);
  
  EXT_0_priority_0;
  EXT_0_falling_edge_detection_only;
  _enable_EXT_0_interrupt;
  _enable_global_interrupt;
 
  LCD_init();
  LCD_clear_home();
}
 
void erase_frames(void)
{
  for(bits = 0; bits < 33; bits++)
  {
    frames[bits] = 0x0000;
  }
 
  TMR0_load_counter_16(0x0000);
  
  received = 0;
  bits = 0;
}
 
unsigned char decode(unsigned char start_pos, unsigned char end_pos)
{
  unsigned char value = 0;
 
  for(bits = start_pos; bits <= end_pos; bits++)
  {
    value <<= 1;
    
    if((frames[bits] >= one_low) && (frames[bits] <= one_high))
    {
      value |= 1;
    }
    
    else if((frames[bits] >= zero_low) && (frames[bits] <= zero_high))
    {
      value |= 0;
    }
    
    else if((frames[bits] >= sync_low) && (frames[bits] <= sync_high))
    {
      return 0xFF;
    }
  }
 
  return value;
}
 
void decode_NEC(unsigned char *addr, unsigned char *cmd)
{
  *addr = decode(2, 9);
  *cmd = decode(18, 25);
}

Schematic

Explanation

A NEC transmission sends out a total of 33 pulses. The first one is a sync pulse and the rest 32 contain address and command info. These 32 address and command bits can be divided into 2 groups. The first 16 bits is one group that contain address and inverted address while the second group of 16-bits contain command and inverted command. The non-inverted signals can be used against the inverted ones for checking data integrity.

We already know that IR data is received as a steam of pulses. Pulses represent sync bit or ones and zeros. In case of NEC IR protocol, the pulses have variable widths. Sync bit is characterized by a pulse of 9ms high and 4.5ms low – the total pulse time is 13.5ms. Check the timing diagram shown below. Please note that in this timing diagram the blue pulses are from the transmitter and the yellow ones are those received by the receiver. Clearly the pulse streams are inverted. This inversion is done at the IR receiver side.

Logic one is represented by a pulse of 560μs high time and 2.25ms of low time – the total pulse time is 2.81ms. Likewise, logic zero is represented by a pulse of 560μs high time and 1.12ms of low time – the total pulse time is 1.68ms.

These timings can vary up to 30% of their ideal values due to a number of factors like medium, temperature, reflections, etc.

Now let’s look at the coding. Firstly, the system clock is set to 12MHz.

 CLK_set_sys_clk(IRC_24M, 2, MCLK_SYSCLK_div_1, MCLK_SYSCLK_no_output); 

External interrupt is set up to detect falling edges. This is a crucial part in detecting IR pulses because using this interrupt allows us to do other tasks and leave the MCU free for other tasks.

 EXT_0_priority_0;
EXT_0_falling_edge_detection_only;
_enable_EXT_0_interrupt;
_enable_global_interrupt;

Unlike past timer examples, we will need an advanced timer here and so Timer 0 is used. This timer is clocked with the 12MHz system clock that has been divided by a prescalar of 12. Thus, the timer is operating at 1MHz speed, i.e., 1 timer tick = 1µs. We don’t want any output from the timer and so this feature is disabled. The only different thing here that sets this setup apart from the other timer examples is the “gate” feature. Gate tells Timer 0 when and when not to run. There are two possible cases with gating – either the timer runs just like other timers when instructed to run via software or it runs when it is coded to run and INT0 pin is in logic high state. The latter feature is required in this example.   

 TMR0_setup(TMR0_16_bit_non_auto_reload, \
           TMR0_sysclk, \
           TMR0_clk_prescalar_12T, \
           TMR0_ext_gate, \
           TMR0_no_clk_out);

Note timer interrupt is not needed.

As soon as there is an external interrupt, the timer immediately stops because of the falling edge that triggered the interrupt and the subsequent low level at INT0 pin. Timer’s count is captured and stored in an array.

 void EXT_0_ISR(void)        
interrupt 0
{
  frames[bits] = TMR0_get_counter(); 
  bits++; 
  TMR0_start; 
 
  if(bits >= 33) 
1`        { 
    received = 1; 
    _disable_global_interrupt; 
    TMR0_stop; 
  } 
  
  TMR0_load_counter_16(0x0000); 
}

The captured counts are compared against maximum and minimum interval limits because as stated, pulse widths may slightly vary from their ideal figures. From captured counts, we can sort pulses and decode pulses. This decoded information can be used to deduce command and address.

 #define sync_high     5850    //approx 1.3 * 4500us
#define sync_low      3150    //approx 0.7 * 4500us
#define one_high      2200    //approx 1.3 * (2250 - 562.5)us
#define one_low       1180    //approx 0.7 * (2250 - 562.5)us  
#define zero_high     732     //approx 1.3 * (1125 - 562.5)us
#define zero_low      394     //approx 0.7 * (1125 - 562.5)us
 
. . . .
 
unsigned char decode(unsigned char start_pos, unsigned char end_pos)
{
  unsigned char value = 0;
 
  for(bits = start_pos; bits <= end_pos; bits++)
  {
    value <<= 1;
    
    if((frames[bits] >= one_low) && (frames[bits] <= one_high))
    {
      value |= 1;
    }    
    else if((frames[bits] >= zero_low) && (frames[bits] <= zero_high))
    {
      value |= 0;
    }    
    else if((frames[bits] >= sync_low) && (frames[bits] <= sync_high))
    {
      return 0xFF;
    }
  }
  return value;
}

In the main loop, address and command data are displayed on an LCD after receiving and decoding a NEC frame. After that the microcontroller is readied to receive new NEC frame.

 if(received != FALSE)
{
  decode_NEC(&addr, &cmd);
       
  print_C(3, 1, addr); 
  print_C(12, 1, cmd);        
  delay_ms(100);       
  erase_frames();
 _enable_global_interrupt;
}

Demo

Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

Related Posts

13 comments

Leave a Reply

Your email address will not be published. Required fields are marked *