Monday, July 10, 2017

NEC Protocol decoder with PIC16F887 microcontroller


 
This article shows the decoding of an IR remote control uses NEC protocol. The microcontroller used in this project is PIC16F887 and the compiler is CCS PIC C.
The complete extended NEC protocol message is started by 9ms burst followed by 4.5ms space which is then followed by the Address and Command. The address is 16-bit length and the command is transmitted twice (8 bits + 8 bits) where in the second time all bits are inverted and can be used for verification of the received message. The following drawing shows an extended NEC message example.
NEC Protocol code message
The NEC protocol uses pulse distance encoding of the bits. Each pulse is a 562.5µs long with carrier frequency of 38KHz. Logic bits are transmitted as follows:
Logic 0: 562.5µs pulse burst followed by a 562.5µs space, with a total transmit time of 1125µs (562.5 x 2).
Logic 1: a 562.5µs pulse burst followed by a 1687.5µs (562.5 x 3) space, with a total transmit time of 2250µs (562.5 x 4).
NEC Protocol logic1 and logic0 timing
Hardware Required:
  • PIC16F887 microcontroller
  • IR Receiver
  • 16x2 LCD screen
  • 10K ohm variable resistor
  • 47µF capacitor
  • 0.1µF ceramic capacitor (optional)
  • 10K ohm resistor
  • 5V Power source
  • Protoboard
  • Jumper wires
NEC Protocol decoder with PIC16F887 microcontroller circuit:
Extended NEC protocol decoder using PIC12F1822 circuit schematic is shown below where a 1602 LCD is used to display the NEC protocol parameters (address and command). 
NEC Protocol remote control decoder with PIC16F887 microcontroller circuit
PIC16F887 uses its internal oscillator which is configured in the C code.
As shown in the circuit diagram, the output of the IR receiver is connected to external interrupt pin which is RB0. A 10K ohm resistor is connected between the IR receiver output and +5V in order to minimize noise which comes from it.
NEC Protocol decoder with PIC16F887 microcontroller C code:
The IR receiver output is logic high at idle state and when it receives a burst it changes the output to logic low.
The message of the NEC protocol is 32-bit long, address (16 bits), command (8 bits), and inverted command (8 bits). Before the previous 32 bits there is 9ms burst and 4.5ms space.
A logic 1 is represented by 562.5µs burst and 562.5µs space (total of 1125µs) and a logic 0 is represented by 562.5µs burst and 1687.5µs space (total of 2250µs).
Keep in mind that the IR receiver output is always inverted.
The interval [ 9500µs, 8500µs ] is used for the 9ms pulse and for the 4.5ms space the interval [ 5000µs, 4000µs ] is used.
The 562.5µs pulse is checked with the interval [ 700µs, 400µs ] .
For the 562.5µs or 1687.5µs space I used the interval [ 1800µs, 400µs ], and to know if its a short or long space I used a length of 1000µs.
The output of the IR receiver is connected to the external interrupt pin (RB0) and every change in the pin status generates an interrupt and Timer1 starts calculating, Timer1 value will be used in the next interrupt, this means Timer1 calculates the time  between two interrupts which is pulse time or space time. Also, Timer1 interrupt is used to reset the decoding process in case of very high pulse or space.
Timer1 time step is 1µs (Timer1 increments every 1µs). If you use mcu frequency other than 8MHz, make sure to keep Timer1 time step to 1µs, otherwise time intervals have to be changed.
The decoding results are displayed on 1602 LCD screen connected to PORTD.
////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                            //
//   NEC Protocol IR remote control decoder with PIC16F887 CCS C code.                        //
//   Internal oscillator used @ 8MHz                                                          //
//   http://ccspicc.blogspot.com/                                                             //
//   electronnote@gmail.com                                                                   //
//                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////

//LCD module connections
#define LCD_RS_PIN PIN_D0
#define LCD_RW_PIN PIN_D1
#define LCD_ENABLE_PIN PIN_D2
#define LCD_DATA4 PIN_D3
#define LCD_DATA5 PIN_D4
#define LCD_DATA6 PIN_D5
#define LCD_DATA7 PIN_D6
//End LCD module connections

#include <16F887.h>
#fuses NOMCLR NOBROWNOUT NOLVP INTRC_IO
#use delay(clock = 8MHz)
#include <lcd.c>

short nec_ok = 0;
unsigned int8 nec_state = 0, command, inv_command, i;
unsigned int16 address;
unsigned int32 nec_code;
#INT_EXT                                         // External interrupt
void ext_isr(void){
  unsigned int16 time;
  if(nec_state != 0){
    time = get_timer1();                         // Store Timer1 value
    set_timer1(0);                               // Reset Timer1
  }
  switch(nec_state){
    case 0 :                                     // Start receiving IR data (we're at the beginning of 9ms pulse)
      setup_timer_1( T1_INTERNAL | T1_DIV_BY_2 );   // Enable Timer1 module with internal clock source and prescaler = 2
      set_timer1(0);                             // Reset Timer1 value
      nec_state = 1;                             // Next state: end of 9ms pulse (start of 4.5ms space)
      i = 0;
      ext_int_edge( L_TO_H );                    // Toggle external interrupt edge
      return;
    case 1 :                                     // End of 9ms pulse
      if((time > 9500) || (time < 8500)){        // Invalid interval ==> stop decoding and reset
        nec_state = 0;                           // Reset decoding process
        setup_timer_1(T1_DISABLED);              // Stop Timer1 module
      }
      else
        nec_state = 2;                           // Next state: end of 4.5ms space (start of 562µs pulse)
      ext_int_edge( H_TO_L );                    // Toggle external interrupt edge
      return;
    case 2 :                                     // End of 4.5ms space
      if((time > 5000) || (time < 4000)){        // Invalid interval ==> stop decoding and reset
        nec_state = 0;                           // Reset decoding process
        setup_timer_1(T1_DISABLED);              // Stop Timer1 module
        return;
      }
      nec_state = 3;                             // Next state: end of 562µs pulse (start of 562µs or 1687µs space)
      ext_int_edge( L_TO_H );                    // Toggle external interrupt edge
      return;
    case 3 :                                     // End of 562µs pulse
      if((time > 700) || (time < 400)){          // Invalid interval ==> stop decoding and reset
        nec_state = 0;                           // Reset decoding process
        setup_timer_1(T1_DISABLED);              // Disable Timer1 module
      }
      else
        nec_state = 4;                           // Next state: end of 562µs or 1687µs space
      ext_int_edge( H_TO_L );                    // Toggle external interrupt edge
      return;
    case 4 :                                     // End of 562µs or 1687µs space
      if((time > 1800) || (time < 400)){         // Invalid interval ==> stop decoding and reset
        nec_state = 0;                           // Reset decoding process
        setup_timer_1(T1_DISABLED);              // Disable Timer1 module
        return;
      }
      if( time > 1000)                           // If space width > 1ms (short space)
        bit_set(nec_code, (31 - i));             // Write 1 to bit (31 - i)
      else                                       // If space width < 1ms (long space)
        bit_clear(nec_code, (31 - i));           // Write 0 to bit (31 - i)
      i++;
      if(i > 31){                                // If all bits are received
        nec_ok = 1;                              // Decoding process OK
        disable_interrupts(INT_EXT);             // Disable the external interrupt
      }
      nec_state = 3;                             // Next state: end of 562µs pulse (start of 562µs or 1687µs space)
      ext_int_edge( L_TO_H );                    // Toggle external interrupt edge
  }
}
#INT_TIMER1                                      // Timer1 interrupt (used for time out)
void timer1_isr(void){
  nec_state = 0;                                 // Reset decoding process
  ext_int_edge( H_TO_L );                        // External interrupt edge from high to low
  setup_timer_1(T1_DISABLED);                    // Disable Timer1 module
  clear_interrupt(INT_TIMER1);                   // Clear Timer1 interrupt flag bit
}
void main(){
  setup_oscillator(OSC_8MHZ);                    // Set internal oscillator to 8MHz
  lcd_init();                                    // Initialize LCD module
  lcd_putc('\f');                                // LCD clear
  enable_interrupts(GLOBAL);                     // Enable global interrupts
  enable_interrupts(INT_EXT_H2L);                // Enable external interrupt
  clear_interrupt(INT_TIMER1);                   // Clear Timer1 interrupt flag bit
  enable_interrupts(INT_TIMER1);                 // Enable Timer1 interrupt
  lcd_gotoxy(1, 1);
  printf(lcd_putc, "Address:0x0000");
  lcd_gotoxy(1, 2);                              // Go to column 1 line 2
  printf(lcd_putc, "Com:0x00 In:0x00");
  while(TRUE){
    if(nec_ok){                                  // If the mcu successfully receives NEC protocol message
      nec_ok = 0;                                // Reset decoding process
      nec_state = 0;
      setup_timer_1(T1_DISABLED);                // Disable Timer1 module
      address = nec_code >> 16;
      command = nec_code >> 8;
      inv_command = nec_code;
      lcd_gotoxy(11, 1);                         // Go to column 11 line 1
      printf(lcd_putc,"%4LX",address);
      lcd_gotoxy(7, 2);                          // Go to column 7 line 2
      printf(lcd_putc,"%2X",command); 
      lcd_gotoxy(15, 2);                         // Go to column 15 line 2
      printf(lcd_putc,"%2X",inv_command);
      enable_interrupts(INT_EXT_H2L);            // Enable external interrupt
    }
  }
}
NEC Protocol decoder with PIC16F887 microcontroller video: